0%

2024-GEEKCTF-wp-crypto

记录一下这次GEEKCTF做出的其中三个赛题(剩下三个我连弄清楚题目要干嘛都做不到)。

babypairing

题目描述:

1
2
3
I believe a larger space is good for security. I am testing my PKE instance with a short paragraph. The flag is of the format

flag{a_sentence_replacing_all_spaces_with_underlines}

题目:

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
import os
from Crypto.Util.number import long_to_bytes,bytes_to_long
p = 74952228632990620596507331669961128827748980750844890766694540917154772000787
a = 7527668573755289684436690520541820188297794210531835381305219764577170135028
b = 23620438740221081546022174030845858657033205037887915215836562897142269481377
F_p = GF(p)
F_p2 = GF(p^2)
a,b = F_p2(a),F_p2(b)
E=EllipticCurve(F_p2,[a,b])
g=E.random_element()
sk=F_p.random_element()
pk=g*sk


def load_element(x1,x2):
is_positive=(x1<p)
x = F_p2([x1,x2])
y2 = x^3+a*x+b
if y2^((p^2-1)//2)==F_p2(-1):
return None
else :
tmp = y2.square_root()
#print((int(tmp)>(p-1)//2),is_positive)
if (int(tmp[0])>(p-1)//2) ^^ is_positive : return E(x,tmp)
else: return E(x,-tmp)

def compress_element(P):
x,y=P.xy()
return (int(x[0]),int(x[1])) if int(y[0])<=(p-1)//2 else (int(x[0])+p,int(x[1]))

def int_to32(x,l=17):
b32dict="0123456789abcdefghijklmnopqrstuv"
ret = ""
while x>0:
ret = b32dict[x%32]+ret
x=x//32
return "0"*max(0,l-len(ret))+ret

def enc_element(P):
r = F_p.random_element()
c2 = g*r
c1 = pk*r+P
ce1,ce2 = compress_element(c1),compress_element(c2)
return "%065x%065x%065x%065x\n"%(*compress_element(c1),*compress_element(c2))
#return int_to32(ce1[0])+int_to32(ce1[1])+int_to32(ce2[0])+int_to32(ce2[1])

def enc_str(s):
enc_map,ret = {},""
for c in s :
if c in enc_map:
cF_p2=enc_map[c]
else :
prefix = os.urandom(29)+bytes(c,encoding='ascii')
x1 = int(F_p.random_element())
for i in range(256):
cF_p2 = load_element(x1,bytes_to_long(prefix+bytes([i])))
if cF_p2!=None:
enc_map[c]=cF_p2
break
ret = ret+enc_element(cF_p2)
return ret

def dec_element(ct):
c11,c12,c21,c22=[ct[i*65:i*65+65] for i in range(4)]
C1,C2=load_element(int("0x"+c11,16),int("0x"+c12,16)),load_element(int("0x"+c21,16),int("0x"+c22,16))
P=C1-C2*sk
return (compress_element(P)[1]&0xffff)>>8

print("g = load_element(*"+str(compress_element(g))+")")
print("pk = load_element(*"+str(compress_element(pk))+")")
#g = load_element(*(29278822809335293856257839032454656006652898948724335358857767737708161772420, 4396426956433009559948787995869502944693089612343852188342374458334039665950))
#pk = load_element(*(148673571405127568767045322546948264768210305253661849398897382818523458361167, 23902769016595610010920651447268476259469689719718232568266731055385481225779))


with open("test_passage.txt","r") as f:
s = f.readline().strip()


with open("ciphertext",'w') as f:
f.write(enc_str(s))

看到题目描述知道这是个基于线性配对的题目,这里先介绍一下双线性配对。简单来说,双线性配对可以理解成一个函数,其输入为椭圆曲线上的两个点,输出为椭圆曲线所在的域上的一个值。如果把这个函数记为e,那么这个概念可以写为:

其中F是椭圆曲线所在域。

而对于一个配对良好的曲线来说,其双线性配对函数满足一个很重要的性质:

同时有如下定义:

以上只是很简单的描述了一下双线性配对以及它的性质,不过对于这个题目来说已经够用了,那么接下来我们回到题目来看看究竟怎么使用。

首先,题目生成一个特定的二次扩域上的特定椭圆曲线E,并取其上一个随机点g,然后题目从Fp中选取一个随机数sk当作私钥,并计算点skg作为公钥pk,并给出我们g和公钥pk的值。

之后就是加密过程,具体来说他的加密方法是:

  • 对于输入的明文串,逐字符取ASCII码作为待加密的明文

  • 建立一个加密字典,如果一个明文已经被加密过,则直接取字典里的值作为待加密项(也就是题目里的cFp2);如果没有被加密过,则从曲线上随机取一个点作为cFp2

    注意这里还没有真正进行加密,相同字符的cF_p2相同并不代表他们的密文相同

  • 记上一步取得的cFp2为P,进行如下操作:

  • 返回c1、c2的压缩点坐标作为本次加密的密文

对于点压缩并不需要关心什么,他已经给出了对应的load函数,所以可以直接从压缩的点坐标还原出完整的点坐标。那么也就是说我们可以获得每次加密得到的c2、c1两个点,要想办法利用其还原明文。

我们的出发点是,对于相同的字符,其每次加密的r虽然不同,但是P会相同。那么两两取密文中的点c11,c12,c21,c22,假设他们是相同字符的密文,那么就有:

由于P点相同,所以将c12与c22进行作差可以消去P得到:

那么此时将T与g作双线性配对就有:

这其实侧面表明双线性配对的一个重要作用:判断配对良好曲线上两个点是否在同一个循环子群中。也就是说,如果我们取的两个密文作差得到的点T与g作线性配对值为1,那么这两个密文对应的明文字符就相同,因为T是g的倍点,所以T在g生成的循环子群里;而如果不是同一个密文,由于无法消去P,因此T和g不在同一个循环群中,线性配对的值自然就不是1。

如此一来,我们可以两两判断出flag的明文字符分布情况,也就是确定flag的明文字符哪些是一样的。之后就是确定下flag框的位置以及flag头,然后去猜测词频就可以得到完整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
from Crypto.Util.number import *
from tqdm import *

p = 74952228632990620596507331669961128827748980750844890766694540917154772000787
a = 7527668573755289684436690520541820188297794210531835381305219764577170135028
b = 23620438740221081546022174030845858657033205037887915215836562897142269481377
F_p = GF(p)
F_p2 = GF(p^2)
a,b = F_p2(a),F_p2(b)
E = EllipticCurve(F_p2,[a,b])
n = E.order()
r = 18738057158247655149126832917490282206937245187711222691673635229288693000197
torsion = 2^2*r

def load_element(x1,x2):
is_positive=(x1<p)
x = F_p2([x1,x2])
y2 = x^3+a*x+b
if y2^((p^2-1)//2)==F_p2(-1):
return None
else :
tmp = y2.square_root()
#print((int(tmp)>(p-1)//2),is_positive)
if (int(tmp[0])>(p-1)//2) ^^ is_positive : return E(x,tmp)
else: return E(x,-tmp)


g = load_element(29278822809335293856257839032454656006652898948724335358857767737708161772420, 4396426956433009559948787995869502944693089612343852188342374458334039665950)
pk = load_element(148673571405127568767045322546948264768210305253661849398897382818523458361167, 23902769016595610010920651447268476259469689719718232568266731055385481225779)

#################################### test
C1 = E.random_element()
C2 = E.random_element()
C3 = F_p.random_element()*C1
#print(C1.weil_pairing(C2,torsion))
#print(C1.weil_pairing(C3,torsion))



#################################### attack

cipher =
points = []

for i in range(len(cipher)):
sgx1 = int(cipher[i][:65],16)
sgx2 = int(cipher[i][65:65*2],16)
tx1 = int(cipher[i][65*2:65*3],16)
tx2 =int(cipher[i][65*3:],16)
sg = load_element(sgx1,sgx2)
t = load_element(tx1,tx2)
points.append([sg,t])


dic = ["*" for i in range(len(points))]
for i in trange(len(points)):
if(dic[i] == "*"):
dic[i] = i
else:
continue
for j in range(i+1,len(points)):
t1 = points[i][0]
t2 = points[j][0]
C = t1 - t2
res = C.weil_pairing(g,torsion)
if(res == 1):
dic[j] = i
print(dic)
print(dic)

猜测+统计词频:

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
import string

dic = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 8, 1, 13, 3, 15, 5, 3, 18, 3, 20, 3, 5, 9, 1, 5, 26, 0, 15, 29, 3, 10, 0, 20, 34, 35, 7, 18, 3, 5, 35, 1, 0, 43, 0, 43, 20, 3, 15, 9, 10, 7, 6, 2, 3, 2, 7, 20, 1, 3, 10, 3, 15, 63, 43, 9, 15, 34, 1, 10, 15, 10, 0, 1, 0, 63, 43, 1, 5, 0, 43, 3, 18, 15, 10, 0, 63, 15, 5, 18, 15, 10, 7, 6, 8, 1, 6, 3, 9, 15, 34, 10, 102, 3, 63, 7, 2, 3, 35, 3, 3, 13, 10, 35, 3, 3, 13, 10, 102, 3, 63, 7, 2, 3, 2, 7, 5, 0, 43, 10, 2, 7, 5, 0, 43, 10, 102, 3, 63, 7, 2, 3, 34, 3, 15, 20, 10, 15, 5, 9, 10, 7, 7, 5, 9, 3, 63, 15, 9, 3, 10, 29, 15, 10, 10, 4, 10, 102, 34, 3, 15, 63, 43, 2, 7, 2, 3, 5, 0, 1, 10, 29, 20, 3, 63, 1, 7, 4, 10, 34, 3, 0, 6, 8, 3, 3, 0, 1, 5, 26, 8, 1, 13, 3, 15, 10, 5, 7, 35, 6, 8, 15, 13, 3, 1, 5, 15, 35, 1, 5, 0, 3, 20, 10, 0, 7, 20, 2, 35, 3, 15, 20, 3, 102, 7, 20, 5, 35, 3, 26, 20, 7, 35, 35, 3, 8, 3, 15, 20, 5, 35, 3, 8, 7, 18, 3, 35, 3, 10, 4, 6, 6, 3, 20, 35, 3, 266, 7, 34, 15, 5, 9, 35, 3, 15, 26, 3, 15, 10, 35, 3, 266, 7, 4, 20, 5, 3, 34, 0, 43, 20, 7, 4, 26, 43, 8, 1, 6, 3, 7, 4, 20, 29, 3, 20, 10, 29, 3, 63, 0, 1, 18, 3, 10, 63, 43, 15, 5, 26, 3, 7, 4, 20, 29, 20, 1, 7, 20, 1, 0, 1, 3, 10, 10, 43, 1, 6, 0, 15, 5, 9, 7, 4, 20, 4, 5, 9, 3, 20, 10, 0, 15, 5, 9, 1, 5, 26, 9, 3, 3, 29, 3, 5, 10, 35, 3, 63, 7, 2, 3, 1, 5, 0, 7, 0, 43, 3, 35, 7, 20, 8, 9, 15, 10, 102, 8, 15, 5, 13, 63, 15, 5, 18, 15, 10, 3, 10, 15, 5, 9, 8, 3, 15, 18, 3, 15, 10, 102, 3, 15, 4, 0, 1, 6, 4, 8, 8, 34, 63, 7, 2, 29, 8, 3, 424, 2, 15, 10, 0, 3, 20, 29, 1, 3, 63, 3, 10, 0, 1, 2, 3, 1, 10, 0, 43, 3, 15, 20, 0, 1, 10, 0, 0, 43, 15, 0, 10, 43, 15, 29, 3, 10, 4, 10, 15, 8, 8, 6, 8, 15, 26, 471, 34, 7, 4, 475, 43, 15, 18, 3, 475, 6, 7, 4, 5, 9, 475, 0, 43, 3, 475, 102, 1, 8, 1, 5, 3, 15, 20, 475, 29, 15, 1, 20, 1, 5, 26, 475, 0, 7, 475, 102, 20, 3, 15, 13, 475, 9, 9, 43, 475, 15, 10, 10, 4, 2, 29, 0, 1, 7, 5, 531]

table = string.ascii_uppercase + string.digits + string.punctuation
frequency = {}
for i in range(len(dic)):
frequency[dic[i]] = 0
for i in range(len(dic)):
frequency[dic[i]] += 1
print(frequency)


dict1 = {}
########################## exactly
dict1[471] = "{"
dict1[475] = "_"
dict1[531] = "}"
dict1[6] = "f"
dict1[8] = "l"
dict1[15] = "a"
dict1[26] = "g"
dict1[13] = "k"

#102, 1, 8, 1, 5, 3, 15, 20, 475, 29, 15, 1, 20, 1, 5, 26, #bilinear
#34, 7, 4, 475, 43, 15, 18, 3, 475, 6, 7, 4, 5, 9, #you have found
########################## guess
dict1[102] = "b"
dict1[1] = "i"
dict1[8] = "l"
dict1[5] = "n"
dict1[8] = "l"
dict1[3] = "e"
dict1[20] = "r"
dict1[29] = "p"
dict1[34] = "y"
dict1[7] = "o"
dict1[4] = "u"
dict1[43] = "h"
dict1[18] = "v"
dict1[9] = "d"

cipher = ""
for i in dic:
try:
cipher += dict1[i]
except:
cipher += "*"

print(cipher)

#{you_have_found_the_bilinear_pairing_to_break_ddh_assumption}



SpARse

题目描述:

1
2
You stole Alice's RSA private key, but it is sparse, can you recover the whole key?
flag is md5sum privkey.pem | awk '{print "flag{"$1"}"}'

题目:

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
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAz3om77jpWFrtAUZNPT29TYCIJzH8LTGyjZT4nuEUyGOpBXdR
IaC46+jVBpkrk4vMGARJPw1j6hWM50WlI8Qq/siRU3ut2hXDeoLHugxJZEkDsRxC
N26kHBMZNs5PQWKLHM1P4p70jVrfzrI7NluKnqiwLMXWP+93iUdx1OIuB3Rl/SCd
/FPhy4t9dvZBYNkuHQKjY+AqandWqJLkGjJpn3K8EV036g9FchQG74cx1JXrFJu0
LqAMo0qv8XqhMItDGnjx0cvFzqhVp3gg/xx3KT4++PkvLJiRNHVlQGECt1tdqsoP
6wWd5RRxQlOcqkZZ8Ynpc0VVfI0xi+F7ELCk+QIDAQA?A??BA?3?a?????3??4?Y
?a??????????n????C?y???P??a?4?rX??p?6?????????x???rzz?c?X???????
???????yp????c??Fs????????????????????jM??????5?aGv?A?????tCD?L?
??pYzs???f?li???F?????????vk?????c?????uS????????Z??E???????????
?????GJ?????hh??/?yX??C??p???X?????s??Wv?N4?7P???????o?W?????r2?
?s?6?L?????Y???????????s?EXs????7?K?????R???p????GBQ??I?u?????11
???Y??????EA???i??aS???j??l?????????y???t?f?F?H9??i4??0???????q?
8e??Y?????v??????Q???????????DLUk??M????????F???????K??U??tQ?5?r
Cl????7????T?????4?7BzRd????7W??????0???M????i?Z??w???c???E??UV?
???????b?g??h?5???????????????Ab?????Bo?q?I??????iMt???????Gq???
??Vx??????????????Gk?c??????0???c?mz?6?????3?4z??iXp?f??i??sbI??
???vA2JV?J?Per??????0???????????Q???cX??gYB??????2?e????J?d?3?y?
9?????D??????h????EmA1Y?csU???D?z?u??9??R?S9J4eyT?????j?V1??g?t?
Sd?????b????p????????/z??f?G?i?/V???q?D7???J?G?I?B?L?????Q????9?
/???e?e???7Z?0??7j??9Q??g????/?f??7d?????q?5???m????????????????
?s???b??u??Py???x??d?????r??????j?z????tB?G?7z?????yQ?ui?????t??
??????H?BI0???mp???HO???????Z??VK??H???????W?????V?E??Z?g????M?1
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????????????????
????????????????????????????????????????????????????
-----END RSA PRIVATE KEY-----

相当离谱的一个私钥恢复,可以说除了前一部分的n、e以外(其实e也破损了一点),后面的数据基本都不成人样了。而题目的任务也并不是简单对某个密文进行解密,而是用完整私钥文件的md5值当作flag提交,所以必须要把整个私钥文件完完全全恢复出来才行。

那么首先就要先对私钥进行一个拆分,由Xenny师傅在NSSCTF上的一个密码工坊知道,对于一个PKCS1格式的RSA私钥pem文件,其中数据满足如下排列形式“

1
2
3
4
5
6
7
8
9
10
版本
模数 - n
加密指数 - e
解密指数 - d
素因子1 - p
素因子2 - q
指数1 - dp
指数2 - dq
系数 - invert(q, p)
其他额外信息

简单举一个例子就是:

1
2
3
4
5
6
7
8
9
10
11
标识头 30
总长度 82 025b
版本信息 0201 00
n 028180 70647879fa43aee8a5d3785754b7988d12b14b98cf39be21b5d90f3e3264e316cf99c3058b89480f2b46a0eda9bee6b4d753199671ef17bab4ec257aabe45e7eb6f668146597a0c9a05d0523e7511e8852a76eaa92e81d16afac7af522423aa00983110ce2d65099d37c748cc2aea0e2a9f4b0613fd8a30a8cbd89187db47f85
e 0203 010001
d 028180 2f2e7e44f682a352970a876261f610dc6814759fd89e6ceac9e42d39f6fdd337283f6c574f9479e3a44f2a0f9b4ac09efa25b0802fa4275a01c9809256c6afc4032ffd486f9e029681a3de126f607603559c7837fc17589d91750ebd4ff791dff06f0fe02d92f26bc582145e12519c74f534e832a4960c4248e24375ee953901
p 0241 009bfd4a17d26da30d2b7d4e920f4b488ef014babb8a2151979917bd228bb180771c028736557afd92f54d5da3837ef27fd322d9fd06c790155dbc22ff579ff51d
q 0241 00b873912447c540c3c96a78f2f00a40898ac5ca0043aa3d914e8920822159d8fb9278920899892df7aec916c2c6002011d52a25d2cbee4d76f5a4e7531e622f89
dp 0240 6264dbe6b8e255564a57694732847f494261210488f5c95cc1c1ba98dedae138c09f4ba0d73c9454ad8cd682fcc007c0df727d646071630e4729143e528c6075
dq 0240 6dc8a347c3cbfcdb4b63aaef75bdb461e90e064817fe18bd06d0895fcab7ee74f5ddfb9550c51c6e02433fdfd7f7b51ec8105908d94652270ed802b32f2f6379
inv(q,p) 0241 009951931fa0d4df28338c2b5d978ff6761a27487704a2f08558666e0aa063604c3a2066009693a2bff04808c5eb381b1d775fb88fa9f579dc474861fdab8028ba

其中”02”、”80”这样的信息其实代表着数据类型、数据长度等信息。所以我们按照这个格式去对私钥文件做对应拆分后,可以了解到这个私钥文件的破损情况是:

  • n是完整的,是一个2048bit的由两个因子组成的大合数
  • e缺少了最后6bit,不过其实基本可以猜测他是65537
  • d、p、q、dp、dq均有不同形式的损坏,每个值泄露的比特数占比大概只有0.3左右
  • invqp这个值完全缺失

那么接下来就要想办法利用这些去还原明文。

这里多说一点,对于这种破损私钥证书的拆分,我比较推荐的做法是先把base64按已知和未知,分别转成6个bit或者6个*之类的未知符号,然后再以字节(8bit)为单位去拆分他,这样拆分比较简单而且能看到直观的比特泄露情况。

然后还有个坑点是dq不知道为啥前面会多出一个填充的00而dp没有:(

虽然d、p、q、dp、dq每个值都有大面积的损毁,但是反过来想,每个值都有小面积的泄露,因此每个值其实都可以利用,那么这个题的核心思路其实就变成了一个非常综合的深搜。简单来说,我们需要从低位开始,反复利用如下几个等式去进行剪枝:

然而一个比较严重的问题是,由于k、kp、kq都不知道,即使他们都肯定在1-e这个区间内,简单爆破的话由于还要剪枝,时间肯定太长了。所以要先想办法把k、kp、kq给求出来。而由于我们拥有d的部分高位,那么求解这三个值是容易的,具体思路如下。

由于有:

所以:

那么就有:

因此我们在1-e爆破k,并计算右侧的值去和d的高位泄露的部分作比较,如果完全相同那么就找到了正确的k。

找到k以后,要继续想办法求解kp和kq,这里我们把上面的几个等式转到模e下来看:

将几个模等式联立,就可以得到在模e下其实有:

转化一下就得到:

把这个式子再代入,得到:

把p、q再用kp、kq分别代掉得到:

然后利用k和kpkq的关系去化为单元方程,这里就以消掉kq为例:

求解这个方程就能得到kp了,显然kq也是该方程的另一个根。

有了kp、kq、k之后就是利用这几个量和上面的四个等式,从低位开始深搜剪枝。不过做到这一步之后还是有点小问题,就是这几个量高位泄露的似乎都比较少,所以剪到高位后深搜会显著的慢很多。不过一个事实是低位肯定是剪对了,因此我在剪到高700位之后化为p低位泄露的问题去copper解掉。

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
import sys
from Crypto.Util.number import *
from random import *
sys.setrecursionlimit(2000)

################################################### part1 read pem
cipher = "MIIEowIBAAKCAQEAz3om77jpWFrtAUZNPT29TYCIJzH8LTGyjZT4nuEUyGOpBXdRIaC46+jVBpkrk4vMGARJPw1j6hWM50WlI8Qq/siRU3ut2hXDeoLHugxJZEkDsRxCN26kHBMZNs5PQWKLHM1P4p70jVrfzrI7NluKnqiwLMXWP+93iUdx1OIuB3Rl/SCd/FPhy4t9dvZBYNkuHQKjY+AqandWqJLkGjJpn3K8EV036g9FchQG74cx1JXrFJu0LqAMo0qv8XqhMItDGnjx0cvFzqhVp3gg/xx3KT4++PkvLJiRNHVlQGECt1tdqsoP6wWd5RRxQlOcqkZZ8Ynpc0VVfI0xi+F7ELCk+QIDAQA?A??BA?3?a?????3??4?Y?a??????????n????C?y???P??a?4?rX??p?6?????????x???rzz?c?X??????????????yp????c??Fs????????????????????jM??????5?aGv?A?????tCD?L???pYzs???f?li???F?????????vk?????c?????uS????????Z??E????????????????GJ?????hh??/?yX??C??p???X?????s??Wv?N4?7P???????o?W?????r2??s?6?L?????Y???????????s?EXs????7?K?????R???p????GBQ??I?u?????11???Y??????EA???i??aS???j??l?????????y???t?f?F?H9??i4??0???????q?8e??Y?????v??????Q???????????DLUk??M????????F???????K??U??tQ?5?rCl????7????T?????4?7BzRd????7W??????0???M????i?Z??w???c???E??UV????????b?g??h?5???????????????Ab?????Bo?q?I??????iMt???????Gq?????Vx??????????????Gk?c??????0???c?mz?6?????3?4z??iXp?f??i??sbI?????vA2JV?J?Per??????0???????????Q???cX??gYB??????2?e????J?d?3?y?9?????D??????h????EmA1Y?csU???D?z?u??9??R?S9J4eyT?????j?V1??g?t?Sd?????b????p????????/z??f?G?i?/V???q?D7???J?G?I?B?L?????Q????9?/???e?e???7Z?0??7j??9Q??g????/?f??7d?????q?5???m?????????????????s???b??u??Py???x??d?????r??????j?z????tB?G?7z?????yQ?ui?????t????????H?BI0???mp???HO???????Z??VK??H???????W?????V?E??Z?g????M?1????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????"
table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

bitstream = ""
for i in cipher:
if(i == "?"):
bitstream += "******"
else:
bitstream += bin(table.index(i))[2:].zfill(6)


################################################### part2 split params
info1 = hex(int(bitstream[:4*8],2))[2:]
bitstream = bitstream[4*8:]
info2 = hex(int(bitstream[:3*8],2))[2:]
bitstream = bitstream[3*8:]

##### n,e
infon = hex(int(bitstream[:4*8],2))[2:]
bitstream = bitstream[4*8:]
n = int(bitstream[:257*8],2)
bitstream = bitstream[257*8:]
infoe = bitstream[:2*8]
bitstream = bitstream[2*8:]
e = bitstream[:3*8] #### seems like 65537
bitstream = bitstream[3*8:]
e = 65537

##### d
infod = bitstream[:4*8]
bitstream = bitstream[4*8:]
d = bitstream[:256*8]
bitstream = bitstream[256*8:]

##### p,q
infop = bitstream[:3*8]
bitstream = bitstream[3*8:]
p = bitstream[:129*8][8:]
bitstream = bitstream[129*8:]
infoq = bitstream[:3*8]
bitstream = bitstream[3*8:]
q = bitstream[:129*8]
bitstream = bitstream[129*8:]

##### dp,dq
infodp = bitstream[:3*8]
bitstream = bitstream[3*8:]
dp = bitstream[:128*8][8:]
bitstream = bitstream[128*8:]
infodq = bitstream[:3*8]
bitstream = bitstream[3*8:]
dq = bitstream[:129*8]
bitstream = bitstream[129*8:]

##### p_invq(no more)
##### maybe some additional change

求解k、kp、kq:

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
from Crypto.Util.number import *

#dhbin = d[:1020]
n = 26191564571207865803659079386216562920365743937051163433571210679225063647091082587257715669756485899193035208469976035738059784724348440070923338255026181997563861328562376469415513279917957395542741541429134095225386135220540927874676394354088360479481955915341958270722056286102261431573282803952793459642793864316123517546752697514637037955503796798930707520651108244395501602445588782475135899939892422822256499112247770956183176429072812823174574240664993265247180146106488076712548920459190957713108289707082714400895643134613721697077267869186086353168834023098632217473990464991482806559422568127693472179449
dhbin = "****110111******011010******************************110111************111000******011000******011010************************************************************100111************************000010******110010******************001111************011010******111000******101011010111************101001******111010******************************************************110001******************101011110011110011******011100******010111************************************************************************************110010101001************************011100************000101101100************************************************************************************************************************100011001100************************************111001******011010000110101111******000000******************************101101000010000011******001011******************101001011000110011101100******************011111******100101100010******************000101******************************************************10111110"
e = 65537
############################################ part2 solve k and get d high
k = 0
for i in range(1,e):
tt = bin(i*n // e)[2:].zfill(2048)[:1020]

FOUND = 1
for j in range(1020):
if(dhbin[j] == "*"):
continue
if(tt[j] != dhbin[j]):
FOUND = 0
break
if(FOUND == 1):
k = i
break
print(k)

############################################ part3 solve kp,kq
PR.<x> = PolynomialRing(Zmod(e))
f = x^2 - (k*(n-1)+1)*x - k
res = f.roots()
print(res)

#[(64743, 1), (30590, 1)]

dfs:

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
################################################### part3 get k,kp,kq(using sparse_solve_k_kp_kq)
k = 39770
d = "0111110111100111011010001001110101101100100111101101110111101001001111111000110111011000011101011010001011000110010001101000110000110011100001010110110000010011100111110010111001100101110011000010111010110010110010100011010011001111111011011110011010110100111000111010101011010111011111110011101001100010111010000001010011110000110100111110010100001111001010100010110001111101010101110011101011110011110011101110011100011110010111110001010101111100010000100110011101001110000100100010101010100100001011100101101100110010101001110100110111111011111011011100001000111100000101101100010111001111011110110001100001100001110000000101111111000101000101110010001001110001001111011001010110011001111001001010100011001100110001100010111001000110000000000011111001110011011010000110101111110000000000111001110001001000010011011010101101000010000011000011001011001000011000011011101001011000110011101100001111111101001001011111101111100101100010100110000001111110000101110001001000001101011100101111110001110110110011001110101111100100******************************011100******************************101110010010************************************************011001************000100************************************************************************************************000110001001******************************100001100001************111111******110010010111************000010************101001******************010111******************************101100************010110101111******001101111000******111011001111******************************************101000******010110******************************101011110110************101100******111010******001011******************************011000******************************************************************101100******000100010111101100************************111011******001010******************************010001******************101001************************000110000001010000************001000******101110******************************110101110101******************011000****************"
#kp,kq = 64743,30590
kq,kp = 64743,30590



################################################### part4 dfs
pbits = 1024
P,Q,DP,DQ,D = p[::-1],q[::-1],dp[::-1],dq[::-1],d[::-1]
def find(pl,ql,dl,dpl,dql,h):
if(h == 700):
print(pl)
######## check
if(h == pbits):
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)
dpi = int(dpl,2)
dqi = int(dql,2)
mask = 2**h

if(pi*qi % mask != n % mask):
return
if(e*di % mask != (k*(n - pi - qi + 1) + 1) % mask):
return
if(e*dpi % mask != (kp*(pi - 1) + 1) % mask):
return
if(e*dqi % mask != (kq*(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]]
pos_dp = ["0","1"] if DP[h] == "*" else [DP[h]]
pos_dq = ["0","1"] if DQ[h] == "*" else [DQ[h]]

for ii in pos_p:
for jj in pos_q:
for kk in pos_d:
for mm in pos_dp:
for nn in pos_dq:
find(ii+pl,jj+ql,kk+dl,mm+dpl,nn+dql,h+1)

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

copper:

1
2
3
4
5
6
7
8
9
10
11
12
from Crypto.Util.number import *
n = 26191564571207865803659079386216562920365743937051163433571210679225063647091082587257715669756485899193035208469976035738059784724348440070923338255026181997563861328562376469415513279917957395542741541429134095225386135220540927874676394354088360479481955915341958270722056286102261431573282803952793459642793864316123517546752697514637037955503796798930707520651108244395501602445588782475135899939892422822256499112247770956183176429072812823174574240664993265247180146106488076712548920459190957713108289707082714400895643134613721697077267869186086353168834023098632217473990464991482806559422568127693472179449
pl = int("0001010101110110000011111101011011010011101011001011111010111110000010110100000000001000110100001011001101001001101101001100011001011100011100001111010111111011010000110010110101001001000010111000110011001001000111010100011110100111110000001010100111100001010101110001100010100111111001100111101101000010101001111110010101001011001000001011010100000111111110011110111010110000101001011000000010011101110000011110111010010011111111101010110100110000011010010001000101100110001110001101001110110000011100110100010111010001101100010010001101011110110101100001001101100110011011001001101010001101001111000110100001100011000000111101001001011000011000100010100110011101010111111100000010011011010110000111",2)

PR.<x> = PolynomialRing(Zmod(n))
f = 2^700*x + pl
f = f.monic()
res = f.small_roots(X=2^324,beta=0.4)
print(res)

p = 2^700*27996439917383235383841028686373623677025754644523593251398561268422647849393297011015184428879646 + pl
q = n // p

construct priv.pem:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Util.number import *
from Crypto.PublicKey import RSA
from hashlib import md5

p = 147265078724969536365832804097158908382797954720876979653011951728406956112387192598824544012306646169035006144198481048711301331247260259281889124056472140003652896595645729235440361839211945606851933815085064600602384039806104472396638159553493474999959228820209555647809413406679561108752796856362130322823
q = 177853193696537602949543516363698051482015780601784878746924982714954417325534763064519601202059344904600445475536028577444032451210441011604812638239514702264403873572939796175627691144179401358323424660942530032692235257344002043472924610196358592525530975564329781167566090856393060393104676776509528699263
n = 26191564571207865803659079386216562920365743937051163433571210679225063647091082587257715669756485899193035208469976035738059784724348440070923338255026181997563861328562376469415513279917957395542741541429134095225386135220540927874676394354088360479481955915341958270722056286102261431573282803952793459642793864316123517546752697514637037955503796798930707520651108244395501602445588782475135899939892422822256499112247770956183176429072812823174574240664993265247180146106488076712548920459190957713108289707082714400895643134613721697077267869186086353168834023098632217473990464991482806559422568127693472179449
e = 65537
phi = (p-1)*(q-1)
d = inverse(e,phi)
dp = d % (p-1)
dq = d % (q-1)
qinvp = inverse(q,p)

rsa_key = RSA.construct((n,e,d,p,q))
print(rsa_key.exportKey().decode("utf-8"))

with open(r"D:\CTF_challs\py\crypto\2024\geekCTF 2024\privkey.pem","rb") as f:
c = f.read()

c = c.replace(b"\r\n",b"\n") #because task's command is linux
print(md5(c).hexdigest())

#flag{d13b8e56805cf71cb75789dda1816587}



HNP

题目描述:

1
2
3
I am suffering from Herniated Nucleus Pulposus, please help me!

nc chall.geekctf.geekcon.top 40555

题目:

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 hashlib import sha256
from random import seed, choice
from secret import flag
from signal import signal, alarm, SIGALRM
from string import ascii_letters, digits

def handler(signum, frame):
print('Time out!')
raise exit()

def proof_of_work():
seed(getRandomRange(1, 2**128))
proof = ''.join([choice(ascii_letters + digits) for _ in range(20)])
digest = sha256(proof.encode('latin-1')).hexdigest()
print('sha256(XXXX + {}) == {}'.format(proof[4: ], digest))
print('Give me XXXX:')
x = input()
if x != proof[: 4]:
return False
return True

n = 8
A = 2048
B = A // n
MASK1 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
MASK2 = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0000000000000000000000000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

def challenge():
N = getPrime(A)
X = getRandomRange(1, N)
Y = [getRandomRange(1, N) for _ in range(n)]
Z = [X * Y[_] % N for _ in range(n)]
print(N)
for _ in range(n):
X_ = Z[_]
Y_ = [(Y[_] >> (__ * B)) & MASK1 for __ in range(n)]
Z_ = [(X_ * Y_[__] % N) & MASK2 for __ in range(n)]
print(Z_)
alarm(5)
G = int(input('X:'))
alarm(30)
if G == X:
return 1
else:
return 0


signal(SIGALRM, handler)
alarm(60)

if not proof_of_work():
exit()

for _ in range(3):
if not challenge():
print('Wrong!')
exit()
else:
print('Good!')

print(flag)

题目是一个限时challenge,在限定时间内通过三次challenge就可以得到flag,每次challenge的内容完全相同,没有难度递进。

challenge本身的内容是要求解一个HNP问题,具体来说每次challenge会做如下几件事:

  • 生成2048bit的素数作为模数N,并生成待求解的1-N之间的随机数X

  • 生成长度为8的1-N之间的随机数列表Y,并计算其中每个元素与X在模N下的乘积,得到长度为8的列表Z,在这之后会进入一个8轮的数据泄露,这里把题目里的下划线换成轮次对应的值i可能更便于下面说法的理解

  • 每一轮中,Xi会取Z[i]作为本轮的值,也就是:

  • 将Y拆分为256bit的八个块,作为Yi列表

  • 将Xi与Yi列表中每一个值在模N下相乘,并和MASK2做与操作,得到Zi列表

  • 泄露Zi列表

  • 8轮完全泄露之后,要求给出正确的X值,即完成本次挑战

光是描述challenge内容本身就已经感觉到很绕了,最好多理一理题目内容。

特别是注意上面Yi和Y[i],Zi和Z[i]的区别

由于泄露的是Zi列表,我们要先弄清楚Zi里面是什么,分析MASK2的结构可以知道,Zi其实是对Xi与Yi中每个乘积分三段341位的泄露,记T=2^341,Zij为本次泄露值,那么其中每个值可以写为:

也就是说,每次challenge中的八轮中的每一轮,会提供给我们8个这样的泄露等式,这些等式中Xi都是相等的,都等于XY[i],那么把符号转化的好理解一些,抽象一下每一轮内的问题就是:

有八组这样的等式:

其中已知zj和N的值,T是常量2^341,yj是256bit的小量,a、b、c分别是341bit的小量,求解出x

注意这里仅仅是求解x,对应求出的也就是Xi,即XY[i]的值,而并不是最终的X。同时要注意,这是一轮内的8个值,而不是8轮。

由于等式中x并不是小量,可以看作是和N数量级相同的大数,所以当务之急是消去他。而消去他的方法和AGCD的处理手段基本一致,也就是两两取出等式:

然后上下凑x的系数:

然后作差:

然后再展开成等式:

此时等式中就仅包含以下几个未知量,其对应的比特数量级大约是:

在模N下都显得比较小,而一共有8组数据,就一共可以取得C(8,2)=28组等式来构造格,妥妥够了。事实上为了节省时间,分段取两次C(4,2)都完全可以,如此一来我们就可以构造格规约出所有的yi,拼接在一起也就得到了初始的Y[i],如此进行八轮,就得到了全部的初始Y列表。

然而有了Y列表并不足以让我们求解出X,我们仍然需要求解一个下面形式的HNP问题:

有64组这样的等式:

求解X

和上面的区别是,现在Y[i]和Yij的值我们都知道了,并且我们要规约的量变化为了每次的a、b、c。这就是一个标准的分段HNP了,有一篇很好的文章可以做参考:

HNP学习笔记 | Tover’s Blog

有一个小细节需要注意,由于每八个等式乘的Y[i]都一样,所以如果仅仅用同一组的式子来求解这个HNP,那么格中会存在线性相关的量,这会导致预期的由a、b、c组成的短向量并不会出现在LLL后的向量中,取而代之的是Yij组成的短向量,这是因为Yij的数量级小于a、b、c。

这也是题目给了八轮数据的意义所在,我们仅需要每轮中取一个值来进行HNP求解即可,这样就可以避免值存在线性相关带来的影响。由于泄露的位数真的很多,所以并不太需要考虑组合,就取2-8轮与第一轮进行消元的七组等式造格即可。

这样处理后时间其实相当宽松,所以并不一定要flatter进行优化。不过奇怪的是我的本地测试完全没有出错过,但是打远程却是概率成功并且没找到原因,所以只能反复重连去碰三次都成功的情况,不过其实也就最多5分钟就能出来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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
from Crypto.Util.number import *
from itertools import *
from math import *
from Pwn4Sage.pwn import *
from tqdm import *
from hashlib import *
import string
from time import *

while(1):
################################################# part1 proof of work
sh = remote("chall.geekctf.geekcon.top",40555)
def proof_of_work():
table = string.digits + string.ascii_letters
temp = sh.recvuntil(b"sha256(XXXX + ")
temp = sh.recvline()
suffix = temp[:16].decode()
hex1 = temp[20:].strip().decode()
for i in tqdm(table):
for j in table:
for k in table:
for m in table:
temp1 = i+j+k+m
if(sha256((temp1+suffix).encode()).hexdigest() == hex1):
sh.sendline(temp1.encode())
return
proof_of_work()
sh.recvline()


################################################# part2 get data
for rounds in range(3):
N = int(sh.recvline().strip().decode())
Total_Z = []
for jjjj in range(8):
Total_Z.append(eval(sh.recvline().strip().decode()))

################################################# part3 get Y
A = []
ZZZ = []
for jjjj in range(8):
Zi = Total_Z[jjjj]
ZZZ.append(Zi[0])

################################################## part1 get Yi[:4]
Y_ = []
T = 2^341
K = 2^2000
nums = 4
groups = comb(nums,2)
L = Matrix(ZZ,nums+4*groups,nums+4*groups)

for i in range(3*groups+nums):
if(i < nums):
L[i,i] = T
else:
L[i,i] = 1
for i in range(groups):
L[-i-1,-i-1] = N*K

count1 = 0
for i in range(nums):
for j in range(i+1,nums):
L[i,3*groups+nums+count1] = -Zi[j]*K
L[j,3*groups+nums+count1] = Zi[i]*K
L[nums+3*count1,3*groups+nums+count1] = T*K
L[nums+3*count1+1,3*groups+nums+count1] = T^3*K
L[nums+3*count1+2,3*groups+nums+count1] = T^5*K
count1 += 1

res = L.LLL()[0]
for i in res[:nums]:
Y_.append(abs(i)//T)


################################################## part2 get Yi[4:]
Zi = Zi[4:]
nums = 4
groups = comb(nums,2)
L = Matrix(ZZ,nums+4*groups,nums+4*groups)

for i in range(3*groups+nums):
if(i < nums):
L[i,i] = T
else:
L[i,i] = 1
for i in range(groups):
L[-i-1,-i-1] = N*K

count1 = 0
for i in range(nums):
for j in range(i+1,nums):
L[i,3*groups+nums+count1] = -Zi[j]*K
L[j,3*groups+nums+count1] = Zi[i]*K
L[nums+3*count1,3*groups+nums+count1] = T*K
L[nums+3*count1+1,3*groups+nums+count1] = T^3*K
L[nums+3*count1+2,3*groups+nums+count1] = T^5*K
count1 += 1

res = L.LLL()[0]
for i in res[:nums]:
Y_.append(abs(i)//T)


################################################## part3 get Yi
Yi = 0
for i in range(8):
Yi += Y_[i] * 2^(256*i)
A.append(Yi*Y_[0])


################################################## part4 HNP(known Ai*X = zi + Tai + T^3bi + T^5ci)
T = 2^341
K = 2^2000
nums = 8
L = Matrix(ZZ,4*nums,4*nums)
for i in range(3*nums):
L[i,i] = 1
L[3*nums,3*nums] = T

for i in range(nums-1):
L[3*nums+1+i,3*nums+1+i] = N*K
for i in range(1,nums):
L[0,3*nums+i] = A[i]*inverse(A[0],N)*T*K
L[1,3*nums+i] = A[i]*inverse(A[0],N)*T^3*K
L[2,3*nums+i] = A[i]*inverse(A[0],N)*T^5*K

L[3*i,3*nums+i] = -T*K
L[3*i+1,3*nums+i] = -T^3*K
L[3*i+2,3*nums+i] = -T^5*K

L[3*nums,3*nums+i] = (A[i]*inverse(A[0],N)*ZZZ[0] - ZZZ[i])*K

res = L.LLL()[0]
a,b,c = abs(res[0]),abs(res[1]),abs(res[2])
X = (ZZZ[0]+T*a+T^3*b+T^5*c)*inverse(A[0],N) % N
sh.recvuntil(b"X:")
sh.sendline(str(X).encode())
temp = sh.recvline()
print(temp)
if(b"Wrong" in temp):
sh.close()
sleep(5)
break

if(b"Good" in temp):
print(sh.recvline())
break


#flag{HNP-SUM_i$_4_pi3ce_0f_c@ke_f0r_u}