0%

Crypto趣题-剪枝

该文章主要记录一些深搜剪枝相关的趣题

首尾剪枝

题目来源:暂不明确

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Crypto.Util.number import *
from secret import flag

m = bytes_to_long(flag)
p = getPrime(256)
q = getPrime(256)
n = p * q
e = 65537
_q = int(bin(q)[2:][::-1] , 2)
c = pow(m,e,n)

print(p ^ _q)
print(n)
print(c)

'''
47761879279815109356923025519387920397647575481870870315845640832106405230526
10310021142875344535823132048350287610122830618624222175188882916320750885684668357543070611134424902255744858233485983896082731376191044874283981089774677
999963120986258459742830847940927620860107164857685447047839375819380831715400110131705491405902374029088041611909274341590559275004502111124764419485191
'''

已知条件:

  • p 与 q 的反方向二进制的异或值,共256bit,记为pxorq

搜索方式:

  • 从两端向中间搜索

  • 每一次搜索,需利用当前 pxorq 两端的bit位。这是因为,pxorq 的当前最高位对应p的最高位及q的最低位,pxorq 的当前最低位对应p的最低位及q的最高位 (其中最高、最低均是对于当前搜索而言)

  • 如果当前需搜索的最高位为”1”,则对应两种可能:p该位为1,q对应低位为0;p该位为0,q对应低位为1。剩下依此类推

剪枝条件:

  • 将p、q未搜索到的位全填0,乘积应小于n
  • 将p、q未搜索到的位全填1,乘积应大于n
  • p、q 低 k 位乘积再取低 k 位,应与 n 的低 k 位相同

如此进行剪枝即可:

exp.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from Crypto.Util.number import *
import sys
sys.setrecursionlimit(1500)

pxorq = 47761879279815109356923025519387920397647575481870870315845640832106405230526
n = 10310021142875344535823132048350287610122830618624222175188882916320750885684668357543070611134424902255744858233485983896082731376191044874283981089774677
c = 999963120986258459742830847940927620860107164857685447047839375819380831715400110131705491405902374029088041611909274341590559275004502111124764419485191
e = 65537
pxorq = str(bin(pxorq)[2:]).zfill(256)

def find(ph,qh,pl,ql):
l = len(ph)
tmp0 = ph + (256-2*l)*"0" + pl
tmp1 = ph + (256-2*l)*"1" + pl
tmq0 = qh + (256-2*l)*"0" + ql
tmq1 = qh + (256-2*l)*"1" + ql
if(int(tmp0,2)*int(tmq0,2) > n):
return
if(int(tmp1,2)*int(tmq1,2) < n):
return
if(int(pl,2)*int(ql,2) % (2**(l-1)) != n % (2**(l-1))):
return

if(l == 128):
pp0 = int(tmp0,2)
if(n % pp0 == 0):
pf = pp0
qf = n//pp0
phi = (pf-1)*(qf-1)
d = inverse(e,phi)
m1 = pow(c,d,n)
print(long_to_bytes(m1))
exit()

else:
if(pxorq[l] == "1" and pxorq[255-l] == "1"):
find(ph+"1",qh+"0","1"+pl,"0"+ql)
find(ph+"0",qh+"0","1"+pl,"1"+ql)
find(ph+"1",qh+"1","0"+pl,"0"+ql)
find(ph+"0",qh+"1","0"+pl,"1"+ql)
elif(pxorq[l] == "1" and pxorq[255-l] == "0"):
find(ph+"1",qh+"0","0"+pl,"0"+ql)
find(ph+"0",qh+"0","0"+pl,"1"+ql)
find(ph+"1",qh+"1","1"+pl,"0"+ql)
find(ph+"0",qh+"1","1"+pl,"1"+ql)
elif(pxorq[l] == "0" and pxorq[255-l] == "1"):
find(ph+"0",qh+"0","1"+pl,"0"+ql)
find(ph+"0",qh+"1","0"+pl,"0"+ql)
find(ph+"1",qh+"0","1"+pl,"1"+ql)
find(ph+"1",qh+"1","0"+pl,"1"+ql)
elif(pxorq[l] == "0" and pxorq[255-l] == "0"):
find(ph+"0",qh+"0","0"+pl,"0"+ql)
find(ph+"1",qh+"0","0"+pl,"1"+ql)
find(ph+"0",qh+"1","1"+pl,"0"+ql)
find(ph+"1",qh+"1","1"+pl,"1"+ql)

find("1","1","1","1")

#flag{f55a2740-c15d-af88-1815-a1b4aab19ccf}



特殊剪枝

题目来源:暂不明确

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import sympy
from Crypto.Util.number import *
from secret import flag
e = 65537
p1 = sympy.randprime(2 ** 1023,2 ** 1024)
q1 = sympy.randprime(2 ** 1023,2 ** 1024)
a1 = p1 ^ q1
b1 = p1 * q1
c1 = pow(bytes_to_long(flag[:19]),e,p1*q1)
p2 = sympy.randprime(2 ** 511, 2 ** 512)
q2 = sympy.randprime(2 ** 511, 2 ** 512)
a2 = (p2 * q2) ^ (p2 + q2)
b2 = (p2 * q2) ^ (p2 - q2)
c2 = pow(bytes_to_long(flag[19:]),e,p2*q2)
f= open('output.txt','w')
f.write(str(a1)+'\n')
f.write(str(b1)+'\n')
f.write(str(c1)+'\n')
f.write(str(a2)+'\n')
f.write(str(b2)+'\n')
f.write(str(c2)+'\n')

题目txt:

1
2
3
4
5
6
a1=67739512154277162085770157687437441198363095490607019903179640765859289435128844487312739643781929328039885340492248268381181927215444058044731882600621443249379470235583032722854561171610662253187419453432598163528304052508578209017561499836803166110456130462444164049945234353225230736363194196935115979960
b1=17185396829856546439605443867156437815015135756541052637907770783830686534153389303291740769607944691156059669175157827203495395745826694347428694508457493991041224390283763876476601200114028282946724348906485066220181559142937065978299071246507281834301352443856315199896106182934770582627129779923357891915723961923663378398066801894395956482176730300442901078199030200112352639266103862753546370851947797706641058966862813099369195689336228579744994641830699890792017097474275824545664085264972274642572927392940910981115837831275773192989084712813373293435228956787629490757407431010258942490818726318175944867633
c1=2180773316568266715369209198734610509148388893757598741330158376506447322216176787253641696053169188685408469718202047474660716095850135317790263924418449270019680259700945680062960717565507426032265137192689118286560945331123730529355709043463330231284484658907466172538703301303440062783852136344472063837313195697915205569416630439851250171277336484771753816776835527532090668694986220968152676688392975798850738947165707984817923309381811015047150056144403783079156300625762879231698942313672034730244627530962258121618021680413439757194393609777357848156392150372631861473658135778661768208071991812674187273360
a2=102834527596695950719979111423985349726489864165244791755647652205679952999516919199218636781810880771255724153293007819995198831162629014290926266777774940370836206596205967641213842702547665263659933022253549718321445029287279257463914991950587622466780705329578580061019164231870445205566240956950369224751
b2=102834527596695950719979111423985349726489864165244791755647652205679952999516919199218636781810880771255724153293007819995198831162629014290926266777774949520538413350277489291427420271328741830415622921056457371226207219443304838109001023043838810016379140438034881290332449739051404396455209891630254998985
c2=46285230821397377383998198689981002335902850753318921384068480704506522918467396194184971163720421808774010121239873784436865080818119851642074388303787396280596526597467664310187113430990219486840906481260493087443528880139543560763852844535689852804877233056126591516506599561944164619603448246607830867682

可以明显看出,题目分为两部分,均是利用剪枝:

第一部分:

已知条件:

  • p1 与 q1 的异或值,共1024bit,记为a1

搜索方式:

  • 从高位向低位搜索

  • 每一次搜索,需利用当前 a1 的最高位

  • 若当前 a1 的最高位为”1”,则对应两种可能:p该位为1,q该位为0;p该位为0,q该位为1。剩下依此类推

剪枝条件:

  • 将p1、q1未搜索到的位全填0,乘积应小于n
  • 将p1、q1未搜索到的位全填1,乘积应大于n
  • p1 > q1(这是因为,p1和q1肯定一个比另一个大,因此可以减少一半复杂度)

第二部分:

已知条件:

  • (p2 * q2)与(p2 + q2)的异或值,记为a2
  • (p2 * q2)与(p2 - q2)的异或值,记为b2

搜索方式:

  • 从低位向高位搜索

  • 每一次搜索,需利用当前 a2、b2 的最低位

  • 硬搜索当前位的所有四种可能:00、01、10、11

剪枝条件:

  • 若当前已搜索了 k 位,则需满足:
1
2
3
4
5
6
t1 = (int(p,2)*int(q,2))
t2 = (int(p,2)+int(q,2))
t3 = (int(p,2)-int(q,2))
mask = 2**k-1

((t1^t2)&mask) == (a2&mask) and ((t1^t3)&mask) == (b2&mask)

按照此方式就可以递归深搜出p2、q2

exp.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
from Crypto.Util.number import *
import sys
sys.setrecursionlimit(1500)

#part1,剪枝
a1=67739512154277162085770157687437441198363095490607019903179640765859289435128844487312739643781929328039885340492248268381181927215444058044731882600621443249379470235583032722854561171610662253187419453432598163528304052508578209017561499836803166110456130462444164049945234353225230736363194196935115979960
b1=17185396829856546439605443867156437815015135756541052637907770783830686534153389303291740769607944691156059669175157827203495395745826694347428694508457493991041224390283763876476601200114028282946724348906485066220181559142937065978299071246507281834301352443856315199896106182934770582627129779923357891915723961923663378398066801894395956482176730300442901078199030200112352639266103862753546370851947797706641058966862813099369195689336228579744994641830699890792017097474275824545664085264972274642572927392940910981115837831275773192989084712813373293435228956787629490757407431010258942490818726318175944867633
c1=2180773316568266715369209198734610509148388893757598741330158376506447322216176787253641696053169188685408469718202047474660716095850135317790263924418449270019680259700945680062960717565507426032265137192689118286560945331123730529355709043463330231284484658907466172538703301303440062783852136344472063837313195697915205569416630439851250171277336484771753816776835527532090668694986220968152676688392975798850738947165707984817923309381811015047150056144403783079156300625762879231698942313672034730244627530962258121618021680413439757194393609777357848156392150372631861473658135778661768208071991812674187273360
e = 65537
a1 = "0" + str(bin(a1)[2:])

def find(p,q):
l = len(p)
tmp0 = p + (1024-l)*"0"
tmp1 = p + (1024-l)*"1"
tmq0 = q + (1024-l)*"0"
tmq1 = q + (1024-l)*"1"
if(int(tmp0,2) < int(tmq0,2)):
return
if(int(tmp0,2)*int(tmq0,2) > b1):
return
elif(int(tmp1,2)*int(tmq1,2) < b1):
return

if(l == 1024):
pp = int(tmp0,2)
qq = int(tmq0,2)
d = inverse(e,(pp-1)*(qq-1))
m = long_to_bytes(pow(c1,d,pp*qq))
print(str(m)[2:-1],end = "")

else:
if(a1[l] == "1"):
find(p+"1",q+"0")
find(p+"0",q+"1")
else:
find(p+"0",q+"0")
find(p+"1",q+"1")

tempp = ""
tempq = ""
find(tempp,tempq)


#part2 硬剪枝
a2=102834527596695950719979111423985349726489864165244791755647652205679952999516919199218636781810880771255724153293007819995198831162629014290926266777774940370836206596205967641213842702547665263659933022253549718321445029287279257463914991950587622466780705329578580061019164231870445205566240956950369224751
b2=102834527596695950719979111423985349726489864165244791755647652205679952999516919199218636781810880771255724153293007819995198831162629014290926266777774949520538413350277489291427420271328741830415622921056457371226207219443304838109001023043838810016379140438034881290332449739051404396455209891630254998985
c2=46285230821397377383998198689981002335902850753318921384068480704506522918467396194184971163720421808774010121239873784436865080818119851642074388303787396280596526597467664310187113430990219486840906481260493087443528880139543560763852844535689852804877233056126591516506599561944164619603448246607830867682
e = 65537

def find(p,q,k):
mask = 2**k-1

t1 = (int(p,2)*int(q,2))
t2 = (int(p,2)+int(q,2))
t3 = (int(p,2)-int(q,2))

if(len(bin(int(p,2))[2:]) == 512 and len(bin(int(q,2))[2:]) == 512):
pp = int(p,2)
qq = int(q,2)
d = inverse(e,(pp-1)*(qq-1))
m = long_to_bytes(pow(c2,d,pp*qq))
if(len(m) < 20):
print(str(m)[2:-1])
exit()

if(((t1^t2)&mask) == (a2&mask) and ((t1^t3)&mask) == (b2&mask)):
find("0"+p,"0"+q,k+1)
find("0"+p,"1"+q,k+1)
find("1"+p,"0"+q,k+1)
find("1"+p,"1"+q,k+1)
else:
return

p = "1"
q = "1"

find(p,q,1)

#flag{u2w6tnettv2a9fbo5qh73k8082h2q9j3}

大概需要十几秒。



Bruce Lee

题目来源:ASISCTF 2023

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/usr/bin/env python3

from Crypto.Util.number import *
from gmpy2 import *
from flag import flag

def gen_prime(nbit, density):
while True:
rar = [2 ** getRandomRange(1, nbit - 1) for _ in range(density)]
p = sum(rar) + 2**(nbit - 1) + 2**(nbit - 2) + 1
if isPrime(p):
return p

nbit, density = 1024, getRandomRange(63, 114)
p, q = [gen_prime(nbit, density) for _ in '01']

e = next_prime(p ^ q + 2**density + 1)

pkey = [e, p * q]

def encrypt(m, pkey):
e, n = pkey
c = pow(m, e, n)
return c

m = bytes_to_long(flag)
c = encrypt(m, pkey)

print(f'n = {p * q}')
print(f'c = {c}')

n = 19042917245789328268024249517392303861887373664474441360674093577617594523782885058709343879401657124298022562721995653950270614217603930670764126782865036502154047261401171062657178276787635173906697276422525562715069109091501193780784958008191861568721460864408361601124668085967409083116374971414489379673468063438466425320645860142765443874701498536334594328335342706939515818475050026693783000449709010679046903447358124485986124890735138491884491082019915560959561941669419617897455859720474743922846167049088101109199553225480054404447207355994986764032824048061541145194505785194843937114939573262115796287539
c = 12549196689373685270672610531491442091469622511804463799798629059280184173137012014970185503674336195900590283390690711267667938868065732941567593693932465371257616881698093095760694499230102241428430013225854565751657204653838191871998202484729697198151346446252253530883223897685933292845099814645178493898965555524871968812109760091328918223322996658046586225147883361819096268210696283619755082081147998952237444863198048776218127798381292028370132987810799018096057686442593856363460538111774040112861083669519451284125771334192618156574950311119691018195236103447442203243056757031705242872859869326165594122418

本题基于RSA,甚至加密指数也与p、q有关,因此要解密必须得到n的分解,要分解n就要找到p、q生成方式的漏洞。

p、q基于如下方式生成:

  • 随机生成密度density,是一个63-114之间的值
  • density的作用就是,在素数的1024个比特位中,随机选择density个位置填上1,剩下的均为0
  • 最后,在最高两位填上1,保证p、q均为1024比特

那么很明显的一点是,p、q中只有很少的比特为1,绝大部分均为0。我们可以利用这一特点进行搜索得到n的分解。

搜索思路如下:

已知条件:

  • p、q的乘积n

搜索方式:

  • 广度优先搜索,注意这个是重点,前些题目都是深搜的

  • 从低位向高位搜索

  • 每一次搜索,硬搜索当前位的所有四种可能:00、01、10、11

剪枝条件:

  • 若当前已搜索了 k 位,则需满足:p、q乘积的低 k 位应与 n 的低 k 位相同

显然,只利用上面这些条件是不足以让我们在有限时间内搜索出结果的,因为我们还没有利用上p、q绝大部分比特为0这一特点。而这也是我们为什么这道题要使用广搜而不是深搜。利用广搜,我们可以每一次获得p、q低k位的全部可能结果,然后将结果按照0的总数量降序排列,这样就可以舍弃后面的1的数量较多的结果。

当然,到底舍弃多少,才能既不舍弃掉正确结果,又能尽可能的减少时间,就是个比较复杂的问题了。这里经我测试取30000个比较合适。分解出p、q后爆破e就好。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
from Crypto.Util.number import *
from tqdm import *
from gmpy2 import *

n = 19042917245789328268024249517392303861887373664474441360674093577617594523782885058709343879401657124298022562721995653950270614217603930670764126782865036502154047261401171062657178276787635173906697276422525562715069109091501193780784958008191861568721460864408361601124668085967409083116374971414489379673468063438466425320645860142765443874701498536334594328335342706939515818475050026693783000449709010679046903447358124485986124890735138491884491082019915560959561941669419617897455859720474743922846167049088101109199553225480054404447207355994986764032824048061541145194505785194843937114939573262115796287539
c = 12549196689373685270672610531491442091469622511804463799798629059280184173137012014970185503674336195900590283390690711267667938868065732941567593693932465371257616881698093095760694499230102241428430013225854565751657204653838191871998202484729697198151346446252253530883223897685933292845099814645178493898965555524871968812109760091328918223322996658046586225147883361819096268210696283619755082081147998952237444863198048776218127798381292028370132987810799018096057686442593856363460538111774040112861083669519451284125771334192618156574950311119691018195236103447442203243056757031705242872859869326165594122418

def count_zeros(item):
return item[0].count('0') + item[1].count('0')

def order(pqlist):
temp = sorted(pqlist,key=count_zeros, reverse=True)
return temp

def check(n,plow,qlow,k):
mask = 2**k - 1
tempp = int(plow,2)
tempq = int(qlow,2)
if(tempp*tempq&mask == n&mask):
return True
else:
return False

def cut(n,pqlist,pq_low,k):
p_low = pq_low[0]
q_low = pq_low[1]
if(check(n,"1" + p_low , "1" + q_low , k)):
pqlist.append(("1" + p_low,"1" + q_low))
if(check(n,"1" + p_low , "0" + q_low , k)):
pqlist.append(("1" + p_low,"0" + q_low))
if(check(n,"0" + p_low , "1" + q_low , k)):
pqlist.append(("0" + p_low,"1" + q_low))
if(check(n,"0" + p_low , "0" + q_low , k)):
pqlist.append(("0" + p_low,"0" + q_low))

def factor(n,bits):
pqlist = [("1","1")]
for k in trange(1,bits):
temp = len(pqlist)
for j in range(temp):
cut(n,pqlist,pqlist[j],k)
pqlist = pqlist[temp:]
pqlist = order(pqlist)[:30000]
for i in pqlist:
p = int(i[0],2)
q = int(i[1],2)
if(p*q == n and isPrime(p) and isPrime(q)):
return p,q

#p,q = factor(n,2048)
p = 141147000382329436324839386148961624079399320499304886665636379949161644687654188092764705324969930200700711941712849798517208128114762376005243573600174539204364615294702226133204126734036441034212322267625427380220691855056189765229234647649981954493648863403315253723106382907789300869823965529091998744627
q = 134915493734951246550425724736802413326299434614440396487261350271033494971726925510331950208998377594163020601895385101353536057620032018002596733163371716321686993658651005366744093863575459477025544857109115504270122611318990813810485203599552658155676894200283243996095998138247551397147375424469031059457
phi = (p-1)*(q-1)

for density in range(63, 114):
e = next_prime(p ^ q + 2**density + 1)
d = inverse(e,phi)
if(b"ASIS" in long_to_bytes(pow(c,d,n))):
print(long_to_bytes(pow(c,d,n)))
break

#ASIS{5pAr3_Pr!MeS_aR3_3A5Y_7O_fAcT0r?!!!}



nextprime剪枝

题目来源:暂不明确

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from sympy import *
from Crypto.Util.number import *
from secret import flag

def gen():
p = getPrime(512)
q = nextprime(int(str(p)[::-1]))
a = p&(10**6)
b = q%(10**6)
return p, q, a, b

p, q, a, b = gen()
n = p * q
e = 65537
c = pow(flag, e, n)

print('a =', a)
print('b =', b)
print('n =', n)
print('c =', c)

'''
a = 393792
b = 657587
n = 369861662178631957928245949770898273807810914183024109983427665445735262653137205864716795669578458854257293391751966280054435051199001813062804877555180285022533020724584142987601931508644551710279816508694942797383860411279321090090880184911983657558416043011288974643282183209071299542379616927857228873411
c = 71672159337725868237152385281240906653912972455699529924728534161923598107229667985188160316648005550663968313537597184857765083556920840254397949219027087098695062021613177519248117588571374167137534818793726427056016629301262986053118989821488864074425276527050110912787300008659230186841168308795745942580
'''

本题重点在于由gen函数获得n的分解:

1
2
3
4
5
6
def gen():
p = getPrime(512)
q = nextprime(int(str(p)[::-1]))
a = p&(10**6)
b = q%(10**6)
return p, q, a, b

注意到p和q具有如下关系:

  • q是p的十进制逆序的nextprime

同时,题目还给了两个额外信息a、b,分别是:

1
2
a = p&(10**6)
b = q%(10**6)

而这一题的p、q生成方式其实和本篇第一题首尾剪枝有异曲同工之妙,区别在于两点:一是由二进制逆序换成十进制逆序;二是q在取逆序后还作了一次nextprime操作。

那么首先,因为p、q乘积的低六位一定与n的低六位相等。因此我们可以爆破出p的低六位,相应的,我们也就有了q的高六位。

而由于给出的额外信息b暴露了q的低六位,并且我们知道,nextprime一般最多也就产生不超过两千的偏移,因此我们可以对偏移量进行爆破,从而得到正确的p的高六位的逆序。爆破的依据为:

  • 对可能的p高六位后面填充全0,与q高六位后面填充全0,乘积应小于n
  • 对可能的p高六位后面填充全9,与q高六位后面填充全9,乘积应大于n

这一步解决后,问题就变成了:已知p、q的高六位和低六位以及p、q的乘积n,要求还原p、q,并且他们之间存在逆序关系,因此用与第一题相似的方法进行深搜,具体如下:

已知条件:

  • p、q的高六位与低六位(十进制位)

搜索方式:

  • 从两端向中间搜索

  • 每一次搜索10x10种可能,分别对应高位部分的一个十进制位和低位部分的一个十进制位

剪枝条件:

  • 将p、q未搜索到的位全填0,乘积应小于n
  • 将p、q未搜索到的位全填9,乘积应大于n
  • p、q 低 k 位乘积再取低 k 位,应与 n 的低 k 位相同

当然要注意一些细节,比如本题中自己生成几组数据会发现,p、q可能为154或155位,因此返回条件在位数不同时会有差别。不过到底是154位还是155位在爆破p高六位时就可以确定了。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
from Crypto.Util.number import *
import sys
sys.setrecursionlimit(100000)

def dec(p,q,c,n):
phi = (p-1)*(q-1)
d = inverse(65537,phi)
print(long_to_bytes(pow(c,d,n)))


def find(ph,qh,pl,ql):
#check1
padding0 = (dec_len-len(ph)-len(pl))*"0"
padding9 = (dec_len-len(ph)-len(pl))*"9"
if(int(qh+padding0+ql)*int(ph+padding0+pl) > n or int(qh+padding9+ql)*int(ph+padding9+pl) < n):
return

#check2
mask = 10**len(pl)
if(int(ql)*int(pl) % mask != n % mask):
return

#return(因为p、q长度为奇数,才这样,否则可以直接判断)
if(len(ph+pl) == dec_len-1):
for i in range(10):
if(n % int(ph+str(i)+pl) == 0):
p = int(ph+str(i)+pl)
q = n // p
dec(p,q,c,n)
exit()

#search
for i in range(10):
for j in range(10):
find(ph + str(i), qh + str(j), str(j) + pl, str(i) + ql)

a = 393792
b = 657587
n = 369861662178631957928245949770898273807810914183024109983427665445735262653137205864716795669578458854257293391751966280054435051199001813062804877555180285022533020724584142987601931508644551710279816508694942797383860411279321090090880184911983657558416043011288974643282183209071299542379616927857228873411
c = 71672159337725868237152385281240906653912972455699529924728534161923598107229667985188160316648005550663968313537597184857765083556920840254397949219027087098695062021613177519248117588571374167137534818793726427056016629301262986053118989821488864074425276527050110912787300008659230186841168308795745942580
dec_len = 155

#find plow
qlow = str(b)
for i in range(1000000):
if(i*b % (10**6) == n % (10**6)):
plow = str(i)

qhigh = plow[::-1]

#find phigh
for i in range(2000):
phigh = str(b-i)[::-1]
if(int(phigh + "0" * (dec_len-6)) * int(qhigh + "0" * (dec_len-6)) < n and int(phigh + "9" * (dec_len-6)) * int(qhigh + "9" * (dec_len-6)) > n):
break

find(phigh,qhigh,plow,qlow)

#flag{7495268d-9987-497e-be6d-2abd1ad91496}

做完了其实还可以思考一个问题,我们真的需要q的低六位这个信息吗?其实并不用,因为可以发现深搜速度很快,那么我们不用q的低六位这个信息,而直接爆破q的低四位,做法也是一样的(之所以爆破低四位是因为偏移量最多也就几千,低四位完全足够)

然后脚本需要注意两个细节问题:

  • 要填充至4个字符
  • b-i可能会小于0,这种情况需要对应加上10000变成四位正数

不用信息b的exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from Crypto.Util.number import *
import sys
from tqdm import *
sys.setrecursionlimit(100000)


def dec(p,q,c,n):
phi = (p-1)*(q-1)
d = inverse(65537,phi)
print(long_to_bytes(pow(c,d,n)))

def find(ph,qh,pl,ql):
#check1
padding0 = (dec_len-len(ph)-len(pl))*"0"
padding9 = (dec_len-len(ph)-len(pl))*"9"
if(int(qh+padding0+ql)*int(ph+padding0+pl) > n or int(qh+padding9+ql)*int(ph+padding9+pl) < n):
return

#check2
mask = 10**len(pl)
if(int(ql)*int(pl) % mask != n % mask):
return

#return(因为p、q长度为奇数,才这样,否则可以直接判断)
if(len(ph+pl) == dec_len-1):
for i in range(10):
if(n % int(ph+str(i)+pl) == 0):
p = int(ph+str(i)+pl)
q = n // p
dec(p,q,c,n)
exit()

#search
for i in range(10):
for j in range(10):
find(ph + str(i), qh + str(j), str(j) + pl, str(i) + ql)

n = 369861662178631957928245949770898273807810914183024109983427665445735262653137205864716795669578458854257293391751966280054435051199001813062804877555180285022533020724584142987601931508644551710279816508694942797383860411279321090090880184911983657558416043011288974643282183209071299542379616927857228873411
c = 71672159337725868237152385281240906653912972455699529924728534161923598107229667985188160316648005550663968313537597184857765083556920840254397949219027087098695062021613177519248117588571374167137534818793726427056016629301262986053118989821488864074425276527050110912787300008659230186841168308795745942580
dec_len = 155

#find plow
for b in trange(10000):
try:
qlow = str(b).zfill(4)
for i in range(10000):
if(i*b % (10**4) == n % (10**4)):
plow = str(i).zfill(4)

qhigh = plow[::-1]

#find phigh
for i in range(2000):
if(b-i>=0):
phigh = str(b-i)[::-1].zfill(4)
else:
phigh = str(b-i+10000)[::-1].zfill(4)
if(int(phigh + "0" * (dec_len-4)) * int(qhigh + "0" * (dec_len-4)) < n and int(phigh + "9" * (dec_len-4)) * int(qhigh + "9" * (dec_len-4)) > n):
break

find(phigh,qhigh,plow,qlow)
except:
pass

#flag{7495268d-9987-497e-be6d-2abd1ad91496}



hackmd5

题目来源:暂不明确

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Util.number import *
from hashlib import md5

flag = "XXX"
assert len(flag) == 14
pad = bytes_to_long(md5(flag).digest())

hack = 0

for char in flag:
hack+= pad
hack*= ord(char)

print hack
#hack = 64364485357060434848865708402537097493512746702748009007197338675
#flag_to_submit = "flag{" + flag + "}"

题目保证flag串为长度14的串,然后计算出他的md5值当作pad,并赋hack初值0。之后利用flag的每一个字符ci,对hack进行如下操作:

给出hack的最终值,要求还原flag。

首先hack并不大,因此可以factordb分解出所有素因子。并且有一点很显然:hack是pad的整数倍。并且由于md5值为128bit数量级的数值,因此我们可以遍历所有可能的乘积组合,找到所有128bit附近的值当作可能的pad。

有了多组可能的pad以后,我们对每一个pad进行深搜尝试,深搜方式由hack的计算过程确定如下:

已知条件:

  • hack值与pad值,flag长度为14,应该均为可见字符

搜索方式:

  • 从flag末尾向前方逐字符搜索

剪枝条件:

  • 每一次爆破可见字符范围,如果字符正确的话,会满足:1、hack当前值模该字符的ASCII码为0;2、hack当前值除以该字符ASCII码之后依然是pad的倍数。
  • 满足上述两个条件,就可以继续向前深搜
  • 当搜索到的字符长度为14时,就可以核验md5值是否等于pad,等于则搜索成功。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from Crypto.Util.number import *
from factordb.factordb import FactorDB
from itertools import combinations
from functools import reduce
from hashlib import md5
import operator

#part1 get all possible_pad
def calculate_all_products(input_list):
result_list = []

for r in range(1, len(input_list) + 1):
for combo in combinations(input_list, r):
product = reduce(operator.mul, combo)
result_list.append(product)

return result_list

hack = 64364485357060434848865708402537097493512746702748009007197338675
f = FactorDB(hack)
f.connect()
fs = f.get_factor_list()
result = calculate_all_products(fs)
possible_pad = []
for i in set(result):
if(len(bin(i)) > 128 and len(bin(i)) <= 130):
possible_pad.append(i)


#part2 dfs to get flag
def check(flag,pad):
if(pad == bytes_to_long(md5(flag.encode()).digest())):
return True
else:
return False

def dfs(tempnum,pad,l,flag):
if(l == 14 and check(flag,pad)):
print(flag)
exit()
for i in range(32,128):
if((tempnum//i)%pad == 0):
dfs(tempnum//i-pad,pad,l+1,chr(i)+flag)

for i in possible_pad:
dfs(hack,i,0,"")

#flag{d0y0ul1keM@TH?}



SuperPrime

题目来源:HITCON CTF 2022

题目描述:

1
Yet another cool prime generation method.

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from Crypto.Util.number import getPrime, isPrime, bytes_to_long


def getSuperPrime(nbits):
while True:
p = getPrime(nbits)
pp = bytes_to_long(str(p).encode())
if isPrime(pp):
return p, pp


p1, q1 = getSuperPrime(512)
p2, q2 = getSuperPrime(512)
p3, q3 = getSuperPrime(512)
p4, q4 = getSuperPrime(512)
p5, q5 = getSuperPrime(512)

n1 = p1 * q1
n2 = p2 * p3
n3 = q2 * q3
n4 = p4 * q5
n5 = p5 * q4

e = 65537
c = bytes_to_long(open("flag.txt", "rb").read().strip())
for n in sorted([n1, n2, n3, n4, n5]):
c = pow(c, e, n)

print(f"{n1 = }")
print(f"{n2 = }")
print(f"{n3 = }")
print(f"{n4 = }")
print(f"{n5 = }")
print(f"{e = }")
print(f"{c = }")

output:

1
2
3
4
5
6
7
n1 = 132240475872174910020944408159013384770525986234801028624784519134365862704105251340824787510945183963356820885920367304711310957365047441178683686926111455575911962257698539064559510444855775549001648292058855493337857073693460061212792795075263084221929517773754699732129582794061997056827998985829666251060653380798686353779510948629280009197715644814616657550158740378670095210797455828266323922570838468050733341304227070902756780052159113811360169205531739117518635829837403006788948761106892086004133969899267757339921
n2 = 95063555614573541810575593850289506856925979977609991181205616159125089261546784721154599484613262518469706157215924537125160406418217535531993036958388330505871763176297570429533467696205928686731756713059807727405313286020007347211892135226632724359291407913539632339885950358476265995466145680878334722001
n3 = 59077122528757446350604269162079270359932342538938986760275099248290981958441838384256597839386787448447136083450980256330743221155636885358548541847176342745397373638152767362253944731433134474562146358334422503033973244936835557423934491676324297243326613498319086748647812745223753746779568080090592100960499863677438403657325762852705171109382084916335379889394829715777901290096314487661584614712488781379507151355301063123233880909931925363322846957197537676660047824476129446066149857051131731840540157488590899311381370266592295206055792990886734933291304077440476730373491475852882163732120626849448728573574411786320772125534383707413572678316508826450778346723441956945169297689138799298561759843280317867927205551400065163199599457
n4 = 24589423756497058585126900932611669798817346035509889383925628660158156567930038333401661451846451875869437263666365776498658699865323180836374906288949824205543130261556051807217164348291174483234810669420041361857307271050079366739157441129916338485838528114129985080841445467007786565727910355311119650431197742495274527401569906785121880408809802492383216836691265423297722021017515667257863302820657924121913047547741420413553737917632809270380269758313556777803344394624408862183672919570479289614998783678080936272369083
n5 = 185885020243714550225131939334004568560534422416697599024696590344782893162219788079305752632863387855011040772104676488940707663157284194641170875157382507202789029285286466326803699701161968587945867047101502048926016515139728368809523009828247173096909917611001113266938209226483162533302629909322412013492978440863258135181226831155024690292336026753678424906151360739424148666951389956182136072508650529271179749569637083537783283360860102371562796635391549934474381821125255176073752645643893294533330184238070085333427
e = 65537
c = 44836759088389215801662306050375432910426695023654894661152471598197009644316944364461563733708795401026569460109604554622161444073404474265330567406370705019579756826106816505952084633979876247266812002057927154389274998399825703196810049647324831928277737068842860115202258059693760003397831075633707611377854322143735834890385706873765241863615950449707047454133596389612468366465634011925228326638487664313491916754929381088396403448356901628825906815317934440495749705736715790281606858736722493438754469493049523175471903946974639097168758949520143915621139415847104585816466890751841858540120267543411140490236193353524030168152197407408753865346510476692347085048554088639428645948051171829012753631844379643600528027854258899402371612

题目生成素数的方式为:

1
2
3
4
5
6
def getSuperPrime(nbits):
while True:
p = getPrime(nbits)
pp = bytes_to_long(str(p).encode())
if isPrime(pp):
return p, pp

可以看到p是随机生成的素数,而q是依赖p对应的字符串生成的,假设p的字符串共有k+1个十进制字符记为ai,如下:

则p和q的数值可以分别记作:

其中,加48代表加上”0”的ASCII码值,而乘2^8i则是bytes_to_long的计算方式,也就是逐字节累加计算出对应大整数。

而既然存在依赖关系,那么我们就可以通过这个关系对p的每个字符进行搜索剪枝,从而还原p获得每个n的分解。以此为基础,题目分成了三类剪枝。

1

1
n1 = p1 * q1

首先我们可以把:bytes_to_long(str(p).encode())记作一个关于p的函数f(p),那么n1就可以写成如下形式:

而之所以写成这样,是因为通过观察可以知道,f(p)是一个关于p的单调递增函数,因此把n1也看做关于p的函数g(p)的话,那么容易发现g(p)也是一个关于p的单调递增函数。而我们拥有函数值n1 = g(p1)以及p1的上界2^512,因此可以直接二分查找p。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
#task1
def task1(l,r,n):
while(l < r):
p = (l + r) // 2
if(p * f(p) > n):
r = p
elif(p * f(p) < n):
l = p
else:
return p,n//p

p1,q1 = task1(0,2**512,n1)

2

第二部分中,n的生成方式为:

1
2
n2 = p2*p3
n3 = q2*q3

这个时候就可以利用我们刚才分析的点进行逐字符搜索:

可以看到,既然n2和n3分别是p和q相乘,那么我们就可以分别从模10和模2^8下,从末尾向前逐字符剪枝,从而搜索出p2和p3。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#task2
def task2(n2,n3,p2,p3,length):
if(length == 0):
for i in range(10):
for j in range(10):
task2(n2,n3,str(i),str(j),1)
else:
if(int(p2)*int(p3) > n2):
return
if(int(p2)*int(p3) == n2):
print(p2)
print(p3)
exit()
else:
if(int(p2)*int(p3) % 10**length == n2 % 10**length and
f(p2)*f(p3) % 256**length == n3 % 256**length):
for i in range(10):
for j in range(10):
task2(n2,n3,str(i)+p2,str(j)+p3,length+1)

task2(n2,n3,"","",0)

3

第三部分对p4、p5、q4、q5做了个交叉,n的生成方式为:

1
2
n4 = p4 * q5
n5 = p5 * q4

爆破的思路也很直白,我们直接从高位向低位爆破,剪枝条件就是后面全部填0乘积小于n;全部填9乘积大于n。而有一点需要注意。512比特的素数十进制可能是154位或155位,p4和p5长度可能不等,因此需要都尝试一下。最后爆破出来发现分别是154位和155位。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#task3
def task3(n4,n5,p4,p5,length):
if(length == 0):
for i in range(10):
for j in range(10):
task3(n4,n5,str(i),str(j),1)
else:
if(int(p4) != 0 and int(p4) != 1):
if(n4 % int(p4) == 0):
print(p4)
p5 = n5 // f(p4)
print(p5)
exit()
p4_pad0 = p4 + (154-length)*"0"
p4_pad9 = p4 + (154-length)*"9"
p5_pad0 = p5 + (155-length)*"0"
p5_pad9 = p5 + (155-length)*"9"
if(int(p4_pad0)*f(p5_pad0) > n4 or int(p5_pad0)*f(p4_pad0) > n5):
return
if(int(p4_pad9)*f(p5_pad9) < n4 or int(p5_pad9)*f(p4_pad9) < n5):
return
for i in range(10):
for j in range(10):
task3(n4,n5,p4+str(i),p5+str(j),length+1)

task3(n4,n5,"","",0)

Final

全部分解完毕后,逐次解密RSA就可以恢复flag。

完整exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
from Crypto.Util.number import *
import sys
sys.setrecursionlimit(15000)

n1 = 132240475872174910020944408159013384770525986234801028624784519134365862704105251340824787510945183963356820885920367304711310957365047441178683686926111455575911962257698539064559510444855775549001648292058855493337857073693460061212792795075263084221929517773754699732129582794061997056827998985829666251060653380798686353779510948629280009197715644814616657550158740378670095210797455828266323922570838468050733341304227070902756780052159113811360169205531739117518635829837403006788948761106892086004133969899267757339921
n2 = 95063555614573541810575593850289506856925979977609991181205616159125089261546784721154599484613262518469706157215924537125160406418217535531993036958388330505871763176297570429533467696205928686731756713059807727405313286020007347211892135226632724359291407913539632339885950358476265995466145680878334722001
n3 = 59077122528757446350604269162079270359932342538938986760275099248290981958441838384256597839386787448447136083450980256330743221155636885358548541847176342745397373638152767362253944731433134474562146358334422503033973244936835557423934491676324297243326613498319086748647812745223753746779568080090592100960499863677438403657325762852705171109382084916335379889394829715777901290096314487661584614712488781379507151355301063123233880909931925363322846957197537676660047824476129446066149857051131731840540157488590899311381370266592295206055792990886734933291304077440476730373491475852882163732120626849448728573574411786320772125534383707413572678316508826450778346723441956945169297689138799298561759843280317867927205551400065163199599457
n4 = 24589423756497058585126900932611669798817346035509889383925628660158156567930038333401661451846451875869437263666365776498658699865323180836374906288949824205543130261556051807217164348291174483234810669420041361857307271050079366739157441129916338485838528114129985080841445467007786565727910355311119650431197742495274527401569906785121880408809802492383216836691265423297722021017515667257863302820657924121913047547741420413553737917632809270380269758313556777803344394624408862183672919570479289614998783678080936272369083
n5 = 185885020243714550225131939334004568560534422416697599024696590344782893162219788079305752632863387855011040772104676488940707663157284194641170875157382507202789029285286466326803699701161968587945867047101502048926016515139728368809523009828247173096909917611001113266938209226483162533302629909322412013492978440863258135181226831155024690292336026753678424906151360739424148666951389956182136072508650529271179749569637083537783283360860102371562796635391549934474381821125255176073752645643893294533330184238070085333427
e = 65537
c = 44836759088389215801662306050375432910426695023654894661152471598197009644316944364461563733708795401026569460109604554622161444073404474265330567406370705019579756826106816505952084633979876247266812002057927154389274998399825703196810049647324831928277737068842860115202258059693760003397831075633707611377854322143735834890385706873765241863615950449707047454133596389612468366465634011925228326638487664313491916754929381088396403448356901628825906815317934440495749705736715790281606858736722493438754469493049523175471903946974639097168758949520143915621139415847104585816466890751841858540120267543411140490236193353524030168152197407408753865346510476692347085048554088639428645948051171829012753631844379643600528027854258899402371612

def f(p):
return bytes_to_long(p.encode())

#task1
def task1(l,r,n):
while(l < r):
p = (l + r) // 2
if(p * f(str(p)) > n):
r = p
elif(p * f(str(p)) < n):
l = p
else:
return p,n//p

#task2
def task2(n2,n3,p2,p3,length):
if(length == 0):
for i in range(10):
for j in range(10):
task2(n2,n3,str(i),str(j),1)
else:
if(int(p2)*int(p3) > n2):
return
if(int(p2)*int(p3) == n2):
print(p2)
print(p3)
exit()
else:
if(int(p2)*int(p3) % 10**length == n2 % 10**length and
f(p2)*f(p3) % 256**length == n3 % 256**length):
for i in range(10):
for j in range(10):
task2(n2,n3,str(i)+p2,str(j)+p3,length+1)

#task3
def task3(n4,n5,p4,p5,length):
if(length == 0):
for i in range(10):
for j in range(10):
task3(n4,n5,str(i),str(j),1)
else:
if(int(p4) != 0 and int(p4) != 1):
if(n4 % int(p4) == 0):
print(p4)
p5 = n5 // f(p4)
print(p5)
exit()
p4_pad0 = p4 + (154-length)*"0"
p4_pad9 = p4 + (154-length)*"9"
p5_pad0 = p5 + (155-length)*"0"
p5_pad9 = p5 + (155-length)*"9"
if(int(p4_pad0)*f(p5_pad0) > n4 or int(p5_pad0)*f(p4_pad0) > n5):
return
if(int(p4_pad9)*f(p5_pad9) < n4 or int(p5_pad9)*f(p4_pad9) < n5):
return
for i in range(10):
for j in range(10):
task3(n4,n5,p4+str(i),p5+str(j),length+1)


#get flag
if(0):
p1,q1 = task1(0,2**512,n1)
print(p1)
if(0):
task2(n2,n3,"","",0)
if(0):
task3(n4,n5,"","",0)
p1 = 8146548592442976266345996123132853490697005499246649457977706700220974227149533573761967281334961993159106889103915430835029497970085237349414587895387361
p2 = 10685750878049478986600454022422733804784834227531623991827538970867377925593354382775253050419846972347584519245766235538419501021140939003899401773087821
p3 = 8896291584885417569104339023688932443326753617531842711599106401724528341937087331194574622599888534531753509912922572906401573640791655490141830263538581
p4 = 6759224678814800913204473280361658486772650199941114505283409645622497866148765146601841353932633241398607040792961131806556756943022446601137737571037341
p5 = 11868750061342011267437975338788313730068511026231254554820987614699325962867540061083226545014725808842166519144882448332395484715124334891963558447955747

modlist = [(n1,p1,f(str(p1))),(n2,p2,p3),(n3,f(str(p2)),f(str(p3))),(n4,p4,f(str(p5))),(n5,p5,f(str(p4)))]
for i in sorted(modlist)[::-1]:
phi = (i[1]-1)*(i[2]-1)
d = inverse(e,phi)
c = pow(c,d,i[0])
print(long_to_bytes(c))

#hitcon{using_different_combinations_of_primes_still_doesn't_make_this_bad_prime_generation_method_any_safer...}



middlersa

题目来源:暂不明确

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from Crypto.Util.number import *
import random

FLAG = b'xxx'

def leak(dp: int, dq: int):
leak_a = []
leak_b = []
for i in range(max(dp.bit_length(), dq.bit_length())):
r = random.getrandbits(32) % 2
if r == 0:
leak_a.append('1' if dp&(2**i) else '0')
leak_b.append('?')
elif r == 1:
leak_b.append('1' if dq&(2**i) else '0')
leak_a.append('?')
print('dp:', ''.join(leak_a[::-1]))
print('dq:', ''.join(leak_b[::-1]))

p = getPrime(512)
q = getPrime(512)
e = 0x10001
d = inverse(e, (p-1)*(q-1))
dp = d % (p-1)
dq = d % (q-1)

assert 55000 <= (e*dp-1)//(p-1) <= 56000
assert 49000 <= (e*dq-1)//(q-1) <= 50000

leak(dp, dq)
print('N:', p*q)
print('c:', pow(bytes_to_long(FLAG), e, p*q))

'''
dp: ??101101110001101000??????0???????1111?0??10?0??0?00?0??????01??1?00??1?011?11?0?10110????010??00?1?10?000????0??0?01??0???00??01?01?100?00110?0??0111?1???0110??00??????1?10?00?0?1????10110??0??110?0?001?????0110??00?01??11?00????000???0?????1?0???1?1??1?10????000?1??????01?10??1?0?0?11????1?00?001???1??1?????1011?0?1?????????0?1???111?01?010?0???10?00?0???00?1??1???00?10????111?0?1???11?110?0?????00110??000100?1????0???1?111010001?1?1???00?11???0?0?1?011?0??011?0???????1??0011100?1???1?1??1??00???0101???0?
dq: 10??????????????????111011?1101100????0?01??1?01?0??0?000110??10?0??11?0???0??0?0?????1110???11??0?1??0???1101?10?0??10?010??11??0??0???0?????1?00????1?110????01??110101?0??1??1?1?0000?????01?11???1?0???11110????10??1??10??0??1011???000?11000?1?001?0?00?0??0000???0?010011??1??10?1?0?1??1101?1??1???111?00?00011????1?1?000000010?1?000???0??1???1?011??0??1?100??0?00?111??1??0010???1?0?110??1???1?00100?????10??????1?1101?111?0?????????1?1?001??1??110?1?1?1???1?10???0?0111000?10???????0?100?1?10?00??111????010?1
N: 77956713622979317783761595054903981958011751094595501314491177382757729657611095418895882363723936522303701334485645032785352259197556402990449635873300021669658762637882700434079432155768745388920298118803003827231120408576526258417243630568234643852227224535788863216313553066059331134379012412201880568839
c: 6373402478226200743899445696426483055555702880465073264671889716957511631194331218010680752909232089491297245558959594952484242066243487601985639038704149982643910363538066540137001662216910771688217243126636396444036372309819771474215211720377034865803003448233174429246078453845273054750113438198122744673

'''

题目基于RSA,给出公钥n、e及密文的同时,还泄露了dp、dq的部分比特位,泄露的比特满足:

  • 对于相同位置的比特,dp、dq有且仅有一个泄露该bit的值

同时我们知道dp、dq具有如下关系:

题目还将k1、k2的取值范围从(1,e)限定在了一个更小的范围内,此举显然是为了减少爆破时间。

到这里就是题目提供的所有信息了,要解出flag就要从泄露的dp、dq部分比特去还原出p、q来解密。

而由于dp、dq是按比特泄露的,对于每个位置,由于dp或dq一定有一个泄露了该bit,所以每个位置都只需要爆破两种可能性,这就自然联想到了剪枝。那么剪枝的思路如下:

已知条件:

  • dp、dq泄露的部分比特(每个位置有且仅有其中一个泄露)

搜索方式:

由于有关于dp、dq的式子:

因为要逐bit搜索,所以要想办法往n上靠,因此移项整理一下有:

这就给从低位剪枝提供了方法。因为对于当前搜索到的dp、dq低i位,应该满足:

剪枝条件:

  • 令:
  • temp低 i 位应与 k1k2n 的低 i 位相同

如此进行剪枝即可。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from Crypto.Util.number import *
from tqdm import *

e = 0x10001
dp = "??101101110001101000??????0???????1111?0??10?0??0?00?0??????01??1?00??1?011?11?0?10110????010??00?1?10?000????0??0?01??0???00??01?01?100?00110?0??0111?1???0110??00??????1?10?00?0?1????10110??0??110?0?001?????0110??00?01??11?00????000???0?????1?0???1?1??1?10????000?1??????01?10??1?0?0?11????1?00?001???1??1?????1011?0?1?????????0?1???111?01?010?0???10?00?0???00?1??1???00?10????111?0?1???11?110?0?????00110??000100?1????0???1?111010001?1?1???00?11???0?0?1?011?0??011?0???????1??0011100?1???1?1??1??00???0101???0?"
dq = "10??????????????????111011?1101100????0?01??1?01?0??0?000110??10?0??11?0???0??0?0?????1110???11??0?1??0???1101?10?0??10?010??11??0??0???0?????1?00????1?110????01??110101?0??1??1?1?0000?????01?11???1?0???11110????10??1??10??0??1011???000?11000?1?001?0?00?0??0000???0?010011??1??10?1?0?1??1101?1??1???111?00?00011????1?1?000000010?1?000???0??1???1?011??0??1?100??0?00?111??1??0010???1?0?110??1???1?00100?????10??????1?1101?111?0?????????1?1?001??1??110?1?1?1???1?10???0?0111000?10???????0?100?1?10?00??111????010?1"
n = 77956713622979317783761595054903981958011751094595501314491177382757729657611095418895882363723936522303701334485645032785352259197556402990449635873300021669658762637882700434079432155768745388920298118803003827231120408576526258417243630568234643852227224535788863216313553066059331134379012412201880568839
c = 6373402478226200743899445696426483055555702880465073264671889716957511631194331218010680752909232089491297245558959594952484242066243487601985639038704149982643910363538066540137001662216910771688217243126636396444036372309819771474215211720377034865803003448233174429246078453845273054750113438198122744673
dp = dp[::-1]
dq = dq[::-1]

def dfs(k1,k2,i,dpl,dql):
temp1 = (e*(int(dpl,2))-1+k1)*(e*(int(dql,2))-1+k2) % 2**i
temp2 = k1*k2*n % 2**i
if(temp1 != temp2):
return
if(i == len(dp)):
dp_true = int(dpl,2)
dq_true = int(dql,2)
p = (e*dp_true-1)//k1+1
q = (e*dq_true-1)//k2+1
if(p*q == n):
phi = (p-1)*(q-1)
d = inverse(e,phi)
print(long_to_bytes(pow(c,d,n)))
exit()
return

if(dp[i] == "?"):
for j in range(2):
dfs(k1,k2,i+1,str(j)+dpl,dq[i]+dql)
elif(dq[i] == "?"):
for j in range(2):
dfs(k1,k2,i+1,dp[i]+dpl,str(j)+dql)

for k1 in trange(55000,56001):
for k2 in range(49000,50001):
dfs(k1,k2,1,"1","1")

#55295,49415
#DASCTF{41bdc8d11439ebf705c53bc10c64fe97}

运气最差的话要花一个多小时,但是题目数据实际跑起来只需要20min。



leak_rsa

题目来源:祥云杯 2022

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from Crypto.Util.number import getPrime, inverse, bytes_to_long
from secret import flag
from random import randint
from math import floor

def hint(p, length, upper):
gift = {}
pstr = bin(p)[2:].zfill(upper + 1)
for i in range(length):
idx = randint(1, upper)
if idx not in gift:
gift[idx] = pstr[idx]
return gift

p = getPrime(512)
q = getPrime(512)
n = p * q
e = getPrime(32)
d = inverse(e, (p - 1) * (q - 1))
c = pow(bytes_to_long(flag), e, n)
hint1 = hint(p, floor(0.42 * p.bit_length()), 511)
hint2 = hint(q, floor(0.42 * q.bit_length()), 511)
hint3 = hint(d, floor(0.42 * d.bit_length()), 1023)
print("n =", n)
print("e =", e)
print("c =", c)
print("hint1 =", hint1)
print("hint2 =", hint2)
print("hint3 =", hint3)

数据:

1
2
3
4
5
6
n = 73380160475470842653695210816683702314062827937540324056880543809752271506601290265975543542548117392788987830919581511428492717214125296973338501980504384307279414528799452106399062576988406269897425829853390463840834798274139351938197666753546672052277640048588091137812362810008344723302886421059831149393
e = 3116872133
c = 69574121902821459446683688068339366486115223765903849266663001038736496551168104587683366853482649748413400537793260948337629422998336301256862519087984048032673577462034223842650399943613576577927825123830629917138007035313624847266208032195071033675853881447717750353382112841885318776240405314057286867952
hint1 = {120: '0', 401: '0', 58: '1', 420: '0', 192: '1', 164: '0', 100: '0', 425: '1', 227: '0', 497: '0', 284: '0', 110: '1', 257: '0', 31: '1', 68: '0', 2: '1', 206: '0', 174: '1', 326: '0', 320: '0', 498: '1', 50: '1', 7: '0', 128: '1', 54: '1', 15: '1', 222: '0', 166: '1', 496: '1', 151: '1', 317: '0', 449: '1', 181: '1', 288: '1', 311: '1', 80: '1', 69: '1', 410: '1', 127: '1', 308: '1', 39: '0', 435: '0', 258: '0', 235: '1', 94: '1', 93: '1', 412: '0', 427: '0', 352: '1', 123: '0', 25: '0', 316: '1', 3: '0', 88: '1', 390: '0', 72: '1', 450: '1', 397: '0', 309: '1', 487: '1', 207: '0', 234: '0', 144: '1', 229: '1', 48: '1', 506: '0', 253: '1', 86: '0', 384: '0', 428: '0', 359: '1', 104: '0', 339: '0', 142: '0', 452: '1', 480: '0', 224: '1', 310: '1', 98: '1', 508: '0', 133: '0', 90: '1', 170: '0', 146: '0', 101: '1', 416: '1', 460: '1', 387: '0', 67: '0', 285: '0', 213: '1', 162: '1', 14: '0', 485: '1', 413: '1', 312: '1', 458: '0', 75: '0', 242: '1', 177: '1', 30: '1', 501: '0', 434: '1', 456: '0', 264: '0', 407: '0', 135: '1', 84: '0', 476: '0', 471: '1', 430: '1', 191: '0', 176: '0', 29: '1', 156: '0', 26: '0', 322: '1', 388: '1', 364: '1', 321: '1', 351: '0', 230: '1', 345: '0', 432: '1', 36: '0', 296: '1', 79: '0', 23: '0', 290: '1', 117: '0', 507: '1', 421: '0', 274: '0', 6: '1', 327: '1', 204: '1', 383: '0', 305: '1', 113: '0', 334: '0', 85: '1', 511: '1', 464: '1', 491: '0', 370: '0', 92: '0', 495: '0', 279: '1', 346: '1', 16: '1', 44: '1', 24: '0', 466: '1', 87: '0', 243: '0', 461: '0', 379: '0', 256: '0', 473: '1', 17: '0', 276: '1', 147: '1', 187: '0', 112: '1', 218: '1', 78: '1', 411: '1', 343: '0', 10: '1', 271: '1', 378: '0', 492: '0', 269: '1', 291: '0', 289: '0', 132: '1', 9: '1', 408: '0', 398: '1', 468: '1', 124: '1', 236: '0', 377: '1', 83: '0'}
hint2 = {125: '0', 86: '1', 8: '0', 498: '1', 311: '0', 93: '0', 385: '0', 315: '1', 300: '1', 454: '0', 152: '0', 205: '0', 400: '1', 348: '1', 18: '1', 154: '0', 51: '1', 435: '0', 25: '1', 430: '0', 72: '1', 136: '0', 294: '0', 466: '0', 388: '0', 428: '0', 440: '1', 250: '1', 506: '0', 48: '0', 270: '1', 318: '0', 107: '0', 327: '1', 474: '0', 325: '0', 281: '0', 392: '0', 473: '1', 13: '1', 90: '0', 278: '0', 425: '0', 109: '1', 423: '1', 412: '1', 190: '1', 171: '0', 475: '1', 441: '1', 336: '0', 371: '0', 323: '0', 22: '1', 469: '0', 451: '0', 438: '0', 203: '1', 121: '0', 52: '1', 494: '1', 399: '0', 314: '0', 24: '1', 183: '0', 492: '1', 246: '1', 108: '1', 379: '0', 460: '1', 56: '0', 372: '1', 313: '1', 44: '0', 237: '1', 12: '0', 6: '0', 204: '1', 80: '1', 339: '1', 296: '0', 483: '0', 402: '0', 67: '0', 338: '1', 116: '0', 406: '1', 218: '0', 115: '0', 301: '0', 490: '1', 502: '0', 343: '1', 46: '1', 321: '0', 231: '1', 88: '0', 404: '1', 426: '0', 344: '0', 123: '1', 463: '0', 45: '1', 461: '1', 1: '0', 229: '0', 28: '1', 274: '1', 134: '1', 104: '1', 21: '0', 256: '0', 471: '1', 157: '0', 217: '1', 158: '0', 307: '1', 26: '0', 255: '0', 386: '1', 373: '0', 114: '1', 360: '0', 148: '1', 383: '1', 63: '0', 19: '1', 472: '0', 201: '1', 262: '1', 47: '0', 221: '0', 310: '0', 352: '1', 224: '1', 185: '0', 214: '1', 285: '1', 410: '0', 455: '0', 445: '0', 464: '0', 284: '1', 503: '1', 298: '1', 449: '0', 477: '0', 376: '0', 16: '0', 133: '0', 177: '1', 210: '0', 364: '1', 163: '1', 213: '1', 295: '1', 111: '1', 458: '0', 146: '0', 244: '0', 261: '1', 508: '1', 106: '0', 112: '1', 120: '0', 156: '1', 303: '0', 259: '1', 35: '0', 444: '0', 215: '1', 304: '0', 140: '0', 351: '0', 443: '0'}
hint3 = {891: '0', 74: '0', 129: '0', 477: '0', 880: '1', 57: '0', 473: '0', 289: '1', 361: '1', 1012: '0', 529: '0', 294: '1', 174: '1', 500: '0', 257: '1', 392: '1', 405: '1', 11: '0', 763: '1', 637: '1', 564: '0', 941: '1', 923: '1', 1014: '1', 670: '1', 558: '0', 304: '1', 444: '1', 716: '0', 208: '0', 130: '1', 634: '1', 661: '0', 862: '0', 412: '1', 796: '1', 761: '1', 113: '1', 752: '0', 818: '0', 797: '1', 390: '1', 337: '0', 133: '1', 367: '1', 470: '1', 345: '1', 170: '1', 312: '0', 624: '1', 53: '1', 75: '1', 281: '1', 522: '1', 100: '0', 554: '1', 583: '1', 16: '1', 836: '0', 715: '1', 450: '0', 484: '0', 876: '0', 165: '0', 842: '0', 62: '0', 442: '1', 927: '0', 586: '1', 399: '1', 227: '0', 886: '1', 663: '0', 947: '0', 906: '1', 377: '0', 246: '1', 365: '0', 177: '1', 59: '1', 63: '0', 936: '1', 144: '0', 416: '1', 228: '1', 366: '0', 117: '0', 78: '0', 717: '1', 14: '0', 800: '1', 47: '0', 80: '0', 34: '0', 662: '1', 970: '0', 986: '1', 287: '1', 597: '0', 783: '0', 805: '1', 112: '1', 671: '1', 540: '1', 153: '1', 577: '1', 543: '0', 414: '0', 123: '1', 626: '1', 452: '1', 810: '1', 30: '0', 905: '0', 602: '1', 537: '1', 374: '0', 408: '1', 434: '0', 137: '1', 532: '0', 397: '0', 333: '1', 258: '1', 359: '1', 134: '1', 322: '1', 653: '0', 1018: '0', 639: '1', 40: '1', 826: '1', 489: '0', 5: '0', 858: '0', 44: '1', 516: '0', 149: '0', 945: '0', 106: '1', 694: '0', 221: '0', 207: '0', 186: '1', 316: '0', 449: '1', 297: '1', 276: '0', 103: '0', 437: '0', 802: '0', 108: '1', 921: '1', 427: '0', 728: '1', 879: '0', 953: '0', 51: '1', 459: '0', 37: '0', 559: '0', 610: '1', 341: '0', 299: '0', 952: '0', 201: '0', 327: '0', 741: '1', 253: '1', 310: '1', 946: '1', 696: '0', 398: '1', 266: '1', 829: '0', 908: '0', 469: '0', 873: '1', 658: '0', 798: '1', 54: '0', 621: '0', 238: '0', 654: '1', 205: '0', 925: '0', 391: '1', 480: '0', 4: '0', 598: '0', 677: '0', 142: '1', 606: '0', 118: '0', 164: '0', 973: '1', 347: '0', 159: '1', 307: '1', 83: '1', 668: '1', 675: '0', 924: '1', 191: '1', 890: '0', 352: '1', 965: '1', 692: '1', 782: '1', 817: '1', 889: '1', 515: '1', 433: '0', 356: '0', 845: '1', 104: '0', 18: '0', 979: '0', 426: '0', 785: '1', 546: '0', 52: '0', 55: '0', 824: '1', 704: '1', 510: '1', 710: '0', 1022: '0', 647: '0', 465: '1', 245: '0', 850: '1', 657: '0', 1007: '0', 807: '1', 158: '1', 328: '0', 292: '1', 355: '1', 596: '0', 275: '1', 371: '0', 1004: '0', 594: '0', 384: '1', 446: '1', 7: '0', 994: '1', 616: '1', 317: '0', 305: '0', 151: '1', 400: '0', 900: '1', 203: '0', 563: '1', 745: '1', 536: '1', 726: '0', 751: '1', 402: '1', 116: '0', 781: '1', 988: '0', 768: '1', 688: '1', 954: '1', 976: '1', 868: '1', 723: '1', 131: '1', 794: '0', 513: '0', 914: '1', 641: '1', 319: '0', 629: '1', 620: '1', 711: '0', 601: '0', 531: '0', 393: '0', 168: '0', 132: '0', 17: '0', 950: '0', 488: '0', 679: '0', 568: '0', 43: '1', 545: '1', 217: '0', 680: '1', 501: '1', 1008: '0', 514: '0', 746: '0', 187: '0', 436: '1', 336: '1', 139: '1', 338: '0', 695: '1', 300: '0', 584: '1', 152: '0', 828: '1', 251: '0', 691: '1', 296: '1', 128: '0', 394: '1', 655: '1', 544: '1', 58: '0', 313: '1', 565: '1', 685: '1', 720: '0', 178: '1', 667: '0', 403: '1', 697: '1', 138: '1', 659: '0', 960: '0', 454: '0', 271: '0', 33: '0', 295: '0', 600: '0', 579: '1', 68: '1', 211: '1', 82: '1', 114: '1', 209: '0', 226: '0', 753: '0', 874: '0', 903: '1', 358: '0', 141: '0', 236: '1'}

简单来说,题目给了以下数据:

  • RSA公钥n、e的值,n由两个512bit的素数组成,e为32bit的素数
  • 密文c的值
  • d、p、q的部分随机比特泄露,泄露的比特位占比约为各自比特数的42%

这种题是有成熟的求解步骤的,具体是:

  • 先根据e、n以及d的高位泄露部分找到k
  • 结合d、q、p的泄露部分从低位开始剪枝

那么下面就分成两个步骤来讲。

找k

找k的依据是如下等式:

由于有:

所以有:

这样做得到的d’和真实的d的前半部分高位会完全一样,因此d的高位泄露部分就可以成为判断依据。而由于k一定小于e(因为d < phi(n)),所以可以在1-e的范围爆破k,并用这个判断依据去判断k是否正确。由于e本身有32bit之多,所以综合考虑正确性和时间花销,就以d的前两百位高位来判断就比较合适,用python即使不加多进程也只需要2h左右。这样做就可以找到k的准确值:

1
k = 1972411342


结合d、q、p的泄露部分开始剪枝

这一部分的剪枝思路是利用以下两个等式:

这两个等式显然在任意模2^h下均成立:

所以把d、p、q一起深搜,每次搜一位并用上述两个式子剪枝即可恢复d、p、q,大概需要20min左右。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
from Crypto.Util.number import *
from tqdm import *

n = 73380160475470842653695210816683702314062827937540324056880543809752271506601290265975543542548117392788987830919581511428492717214125296973338501980504384307279414528799452106399062576988406269897425829853390463840834798274139351938197666753546672052277640048588091137812362810008344723302886421059831149393
e = 3116872133
c = 69574121902821459446683688068339366486115223765903849266663001038736496551168104587683366853482649748413400537793260948337629422998336301256862519087984048032673577462034223842650399943613576577927825123830629917138007035313624847266208032195071033675853881447717750353382112841885318776240405314057286867952
hint1 = {120: '0', 401: '0', 58: '1', 420: '0', 192: '1', 164: '0', 100: '0', 425: '1', 227: '0', 497: '0', 284: '0', 110: '1', 257: '0', 31: '1', 68: '0', 2: '1', 206: '0', 174: '1', 326: '0', 320: '0', 498: '1', 50: '1', 7: '0', 128: '1', 54: '1', 15: '1', 222: '0', 166: '1', 496: '1', 151: '1', 317: '0', 449: '1', 181: '1', 288: '1', 311: '1', 80: '1', 69: '1', 410: '1', 127: '1', 308: '1', 39: '0', 435: '0', 258: '0', 235: '1', 94: '1', 93: '1', 412: '0', 427: '0', 352: '1', 123: '0', 25: '0', 316: '1', 3: '0', 88: '1', 390: '0', 72: '1', 450: '1', 397: '0', 309: '1', 487: '1', 207: '0', 234: '0', 144: '1', 229: '1', 48: '1', 506: '0', 253: '1', 86: '0', 384: '0', 428: '0', 359: '1', 104: '0', 339: '0', 142: '0', 452: '1', 480: '0', 224: '1', 310: '1', 98: '1', 508: '0', 133: '0', 90: '1', 170: '0', 146: '0', 101: '1', 416: '1', 460: '1', 387: '0', 67: '0', 285: '0', 213: '1', 162: '1', 14: '0', 485: '1', 413: '1', 312: '1', 458: '0', 75: '0', 242: '1', 177: '1', 30: '1', 501: '0', 434: '1', 456: '0', 264: '0', 407: '0', 135: '1', 84: '0', 476: '0', 471: '1', 430: '1', 191: '0', 176: '0', 29: '1', 156: '0', 26: '0', 322: '1', 388: '1', 364: '1', 321: '1', 351: '0', 230: '1', 345: '0', 432: '1', 36: '0', 296: '1', 79: '0', 23: '0', 290: '1', 117: '0', 507: '1', 421: '0', 274: '0', 6: '1', 327: '1', 204: '1', 383: '0', 305: '1', 113: '0', 334: '0', 85: '1', 511: '1', 464: '1', 491: '0', 370: '0', 92: '0', 495: '0', 279: '1', 346: '1', 16: '1', 44: '1', 24: '0', 466: '1', 87: '0', 243: '0', 461: '0', 379: '0', 256: '0', 473: '1', 17: '0', 276: '1', 147: '1', 187: '0', 112: '1', 218: '1', 78: '1', 411: '1', 343: '0', 10: '1', 271: '1', 378: '0', 492: '0', 269: '1', 291: '0', 289: '0', 132: '1', 9: '1', 408: '0', 398: '1', 468: '1', 124: '1', 236: '0', 377: '1', 83: '0'}
hint2 = {125: '0', 86: '1', 8: '0', 498: '1', 311: '0', 93: '0', 385: '0', 315: '1', 300: '1', 454: '0', 152: '0', 205: '0', 400: '1', 348: '1', 18: '1', 154: '0', 51: '1', 435: '0', 25: '1', 430: '0', 72: '1', 136: '0', 294: '0', 466: '0', 388: '0', 428: '0', 440: '1', 250: '1', 506: '0', 48: '0', 270: '1', 318: '0', 107: '0', 327: '1', 474: '0', 325: '0', 281: '0', 392: '0', 473: '1', 13: '1', 90: '0', 278: '0', 425: '0', 109: '1', 423: '1', 412: '1', 190: '1', 171: '0', 475: '1', 441: '1', 336: '0', 371: '0', 323: '0', 22: '1', 469: '0', 451: '0', 438: '0', 203: '1', 121: '0', 52: '1', 494: '1', 399: '0', 314: '0', 24: '1', 183: '0', 492: '1', 246: '1', 108: '1', 379: '0', 460: '1', 56: '0', 372: '1', 313: '1', 44: '0', 237: '1', 12: '0', 6: '0', 204: '1', 80: '1', 339: '1', 296: '0', 483: '0', 402: '0', 67: '0', 338: '1', 116: '0', 406: '1', 218: '0', 115: '0', 301: '0', 490: '1', 502: '0', 343: '1', 46: '1', 321: '0', 231: '1', 88: '0', 404: '1', 426: '0', 344: '0', 123: '1', 463: '0', 45: '1', 461: '1', 1: '0', 229: '0', 28: '1', 274: '1', 134: '1', 104: '1', 21: '0', 256: '0', 471: '1', 157: '0', 217: '1', 158: '0', 307: '1', 26: '0', 255: '0', 386: '1', 373: '0', 114: '1', 360: '0', 148: '1', 383: '1', 63: '0', 19: '1', 472: '0', 201: '1', 262: '1', 47: '0', 221: '0', 310: '0', 352: '1', 224: '1', 185: '0', 214: '1', 285: '1', 410: '0', 455: '0', 445: '0', 464: '0', 284: '1', 503: '1', 298: '1', 449: '0', 477: '0', 376: '0', 16: '0', 133: '0', 177: '1', 210: '0', 364: '1', 163: '1', 213: '1', 295: '1', 111: '1', 458: '0', 146: '0', 244: '0', 261: '1', 508: '1', 106: '0', 112: '1', 120: '0', 156: '1', 303: '0', 259: '1', 35: '0', 444: '0', 215: '1', 304: '0', 140: '0', 351: '0', 443: '0'}
hint3 = {891: '0', 74: '0', 129: '0', 477: '0', 880: '1', 57: '0', 473: '0', 289: '1', 361: '1', 1012: '0', 529: '0', 294: '1', 174: '1', 500: '0', 257: '1', 392: '1', 405: '1', 11: '0', 763: '1', 637: '1', 564: '0', 941: '1', 923: '1', 1014: '1', 670: '1', 558: '0', 304: '1', 444: '1', 716: '0', 208: '0', 130: '1', 634: '1', 661: '0', 862: '0', 412: '1', 796: '1', 761: '1', 113: '1', 752: '0', 818: '0', 797: '1', 390: '1', 337: '0', 133: '1', 367: '1', 470: '1', 345: '1', 170: '1', 312: '0', 624: '1', 53: '1', 75: '1', 281: '1', 522: '1', 100: '0', 554: '1', 583: '1', 16: '1', 836: '0', 715: '1', 450: '0', 484: '0', 876: '0', 165: '0', 842: '0', 62: '0', 442: '1', 927: '0', 586: '1', 399: '1', 227: '0', 886: '1', 663: '0', 947: '0', 906: '1', 377: '0', 246: '1', 365: '0', 177: '1', 59: '1', 63: '0', 936: '1', 144: '0', 416: '1', 228: '1', 366: '0', 117: '0', 78: '0', 717: '1', 14: '0', 800: '1', 47: '0', 80: '0', 34: '0', 662: '1', 970: '0', 986: '1', 287: '1', 597: '0', 783: '0', 805: '1', 112: '1', 671: '1', 540: '1', 153: '1', 577: '1', 543: '0', 414: '0', 123: '1', 626: '1', 452: '1', 810: '1', 30: '0', 905: '0', 602: '1', 537: '1', 374: '0', 408: '1', 434: '0', 137: '1', 532: '0', 397: '0', 333: '1', 258: '1', 359: '1', 134: '1', 322: '1', 653: '0', 1018: '0', 639: '1', 40: '1', 826: '1', 489: '0', 5: '0', 858: '0', 44: '1', 516: '0', 149: '0', 945: '0', 106: '1', 694: '0', 221: '0', 207: '0', 186: '1', 316: '0', 449: '1', 297: '1', 276: '0', 103: '0', 437: '0', 802: '0', 108: '1', 921: '1', 427: '0', 728: '1', 879: '0', 953: '0', 51: '1', 459: '0', 37: '0', 559: '0', 610: '1', 341: '0', 299: '0', 952: '0', 201: '0', 327: '0', 741: '1', 253: '1', 310: '1', 946: '1', 696: '0', 398: '1', 266: '1', 829: '0', 908: '0', 469: '0', 873: '1', 658: '0', 798: '1', 54: '0', 621: '0', 238: '0', 654: '1', 205: '0', 925: '0', 391: '1', 480: '0', 4: '0', 598: '0', 677: '0', 142: '1', 606: '0', 118: '0', 164: '0', 973: '1', 347: '0', 159: '1', 307: '1', 83: '1', 668: '1', 675: '0', 924: '1', 191: '1', 890: '0', 352: '1', 965: '1', 692: '1', 782: '1', 817: '1', 889: '1', 515: '1', 433: '0', 356: '0', 845: '1', 104: '0', 18: '0', 979: '0', 426: '0', 785: '1', 546: '0', 52: '0', 55: '0', 824: '1', 704: '1', 510: '1', 710: '0', 1022: '0', 647: '0', 465: '1', 245: '0', 850: '1', 657: '0', 1007: '0', 807: '1', 158: '1', 328: '0', 292: '1', 355: '1', 596: '0', 275: '1', 371: '0', 1004: '0', 594: '0', 384: '1', 446: '1', 7: '0', 994: '1', 616: '1', 317: '0', 305: '0', 151: '1', 400: '0', 900: '1', 203: '0', 563: '1', 745: '1', 536: '1', 726: '0', 751: '1', 402: '1', 116: '0', 781: '1', 988: '0', 768: '1', 688: '1', 954: '1', 976: '1', 868: '1', 723: '1', 131: '1', 794: '0', 513: '0', 914: '1', 641: '1', 319: '0', 629: '1', 620: '1', 711: '0', 601: '0', 531: '0', 393: '0', 168: '0', 132: '0', 17: '0', 950: '0', 488: '0', 679: '0', 568: '0', 43: '1', 545: '1', 217: '0', 680: '1', 501: '1', 1008: '0', 514: '0', 746: '0', 187: '0', 436: '1', 336: '1', 139: '1', 338: '0', 695: '1', 300: '0', 584: '1', 152: '0', 828: '1', 251: '0', 691: '1', 296: '1', 128: '0', 394: '1', 655: '1', 544: '1', 58: '0', 313: '1', 565: '1', 685: '1', 720: '0', 178: '1', 667: '0', 403: '1', 697: '1', 138: '1', 659: '0', 960: '0', 454: '0', 271: '0', 33: '0', 295: '0', 600: '0', 579: '1', 68: '1', 211: '1', 82: '1', 114: '1', 209: '0', 226: '0', 753: '0', 874: '0', 903: '1', 358: '0', 141: '0', 236: '1'}

p_ = list("1" + 510*"*" + "1")
q_ = list("1" + 510*"*" + "1")
d_ = list("0" + 1022*"*" + "1")

for i in hint1:
p_[i] = hint1[i]
for i in hint2:
q_[i] = hint2[i]
for i in hint3:
d_[i] = hint3[i]
p_ = "".join(p_)
q_ = "".join(q_)
d_ = "".join(d_)


########################################################## part1 find k
if(0):
for k in trange(1,e):
tt = bin(k*n // e)[2:].zfill(1024)
FOUND = 1
for j in range(200):
if(d_[j] == "*"):
continue
if(tt[j] != d_[j]):
FOUND = 0
break
if(FOUND == 1):
print(k)
break
k = 1972411342


########################################################## part2 find p,q,d
P,Q,D = p_[::-1],q_[::-1],d_[::-1]
def find(pl,ql,dl,h):
if(h == 512):
pp = int(pl,2)
if(n % pp == 0):
print(pp)
print(n // pp)
exit()
return

######## prune
if(h > 0):
pi = int(pl,2)
qi = int(ql,2)
di = int(dl,2)
mask = 2**h

if(pi*qi % mask != n % mask):
return
if(e*di % mask != (k*(n - pi - qi + 1) + 1) % mask):
return

######## search
pos_p = ["0","1"] if P[h] == "*" else [P[h]]
pos_q = ["0","1"] if Q[h] == "*" else [Q[h]]
pos_d = ["0","1"] if D[h] == "*" else [D[h]]

for ii in pos_p:
for jj in pos_q:
for kk in pos_d:
find(ii+pl,jj+ql,kk+dl,h+1)

#find("","","",0)

p = 9133917064511781922031258437152327261693143304454013549549758156666324513465089691034313787446294509129449327989019056217376060978961891599469362333006483
q = 8033810681353399639534641829067934203193783188961178150445992214367649502764412203925061359540792375365156063944961787518643928146665931290500178482197771
phi = (p-1)*(q-1)
d = inverse(e,phi)
print(long_to_bytes(pow(c,d,n)))


#flag{022db473-bd93-4c64-8e6f-a8f45205f364}