sign the ca7s
这题跟sign in the ca7s是一模一样的,但是多一个level3,所以只贴这一道题。
题目
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 from Crypto.Util.number import bytes_to_long from hashlib import md5 import os FLAG = os.environ.get("FLAG", "flag{**redacted**}") E = EllipticCurve(GF(0x1337_ca7_eae368ff5d702e6067aaaa77ca_ca7_1337), [0, 3]) G, n = E(1, 2), E.order() def sign(priv, ctx, msg): k = bytes_to_long(ctx + md5(str(priv).encode() + msg).digest()) z = bytes_to_long(md5(ctx + msg).digest()) r = int((k * G).x()) % n s = (pow(k, -1, n) * (z + r * priv)) % n return r, s def verify(pub, ctx, msg, sig): z = bytes_to_long(md5(ctx + msg).digest()) r, s = sig if 0 < r < n and 0 < s < n: return r == int((pow(s, -1, n) * (z * G + r * pub)).x()) % n def chall(level, flag): priv = randint(1, n - 1) pub = priv * G msg = os.urandom(64) print(f"=== level {level} ===") for _ in range(catalan_number(level)): ctx = bytes.fromhex(input('context: ')) r, s = sign(priv, ctx, msg) assert verify(pub, ctx, msg, (r, s)) if level <= 1: print('message:', msg.hex()) if level <= 2: print('sign:', r) if level <= 3: print('ature:', s) r, s = map(int, input('signature: ').split()) assert verify(pub, b'n1junior_2025', f'cat /flag{level}'.encode(), (r, s)) print(f'flag{level}:', flag) if __name__ == "__main__": chall(1, "💧") chall(2, "🐱") chall(3, FLAG)
分析
看的出来这是一个ECDSA的题目,过程简单说说就是一次签名流程先生成一个私钥p r i v priv p r i v ,在每次签名过程中随机生成一个整数k k k ,明文通过MD5转换得到整数z z z ,最后生成公钥组{ r , s } \{r,s\} { r , s } 。公式如下
r ≡ ( k ⋅ G ) x ( m o d n ) s ≡ k − 1 ⋅ ( z + r ⋅ p r i v ) ( m o d n ) r\equiv (k \cdot G)_x \pmod{n}\\
s\equiv k^{-1} \cdot (z+r \cdot priv) \pmod{n}
r ≡ ( k ⋅ G ) x ( m o d n ) s ≡ k − 1 ⋅ ( z + r ⋅ p r i v ) ( m o d n )
理解上述过程之后检验参数其实是比较好推的
( s − 1 ⋅ ( z ⋅ G + r ⋅ p u b ) ) x = ( s − 1 ⋅ ( z + r ⋅ p r i v ) ⋅ G ) x = ( s − 1 ⋅ ( s ⋅ k ) ⋅ G ) x = ( k ⋅ G ) x ≡ r ( m o d n ) (s^{-1}\cdot (z \cdot G+r \cdot pub))_x=(s^{-1} \cdot (z +r \cdot priv)\cdot G)_x=(s^{-1} \cdot (s\cdot k) \cdot G)_x=(k \cdot G)_x \equiv r \pmod{n}
( s − 1 ⋅ ( z ⋅ G + r ⋅ p u b ) ) x = ( s − 1 ⋅ ( z + r ⋅ p r i v ) ⋅ G ) x = ( s − 1 ⋅ ( s ⋅ k ) ⋅ G ) x = ( k ⋅ G ) x ≡ r ( m o d n )
再来看看构造的曲线E : y 2 = x 3 + 3 E:y^2=x^3+3 E : y 2 = x 3 + 3 ,曲线E E E 的阶刚好等于有限域p p p 的值,求解ECDLP的时候可以考虑smart attack,不过这里可能是G G G 和E E E 比较简单的缘故,直接使用log()函数也能求解k k k 。不过这里求得的k k k 是模n n n 意义下的,如果c t x ctx c t x 过长,无法恢复出最原始的k k k 。另外由于我们恢复点R R R 的时候,只知道R R R 的横坐标,此时会得到两个点R R R ,因此得到两个k k k 。这个时候可以利用k k k 的构造方法,k k k 的高位是c t x ctx c t x 所以k k k 会满足
c t x ⋅ 2 128 < k < ( c t x + 1 ) c d o t 2 128 ctx \cdot 2^{128}<k<(ctx+1) cdot 2^{128}
c t x ⋅ 2 1 2 8 < k < ( c t x + 1 ) c d o t 2 1 2 8
level1
level1阶段给出了{ m s g , r , s } \{msg,r,s\} { m s g , r , s } ,通过m s g msg m s g 我们可以得到z z z 的值,然后通过r r r 我们可以得到k k k 的值。根据s ≡ k − 1 ⋅ ( z + r ⋅ p r i v ) ( m o d n ) s\equiv k^{-1} \cdot (z+r \cdot priv) \pmod{n} s ≡ k − 1 ⋅ ( z + r ⋅ p r i v ) ( m o d n ) 其中只有一个未知数p r i v priv p r i v ,于是求得p r i v priv p r i v 。
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 * from hashlib import md5 E = EllipticCurve(GF(0x1337_ca7_eae368ff5d702e6067aaaa77ca_ca7_1337), [0, 3]) G, n = E(1, 2), E.order() msg=bytes.fromhex("..") r=.. s=.. ctx=bytes.fromhex("..")#输入尽量短 g=bytes_to_long(ctx) R=E.lift_x(r) k=0 for _ in range(2): k=R.log(G) if k>g*2^128%n and k<(g+1)*2^128%n: break R=-R z=bytes_to_long(md5(ctx + msg).digest()) priv=(s*k-z)*inverse(r,n)%n kk=bytes_to_long(ctx + md5(str(priv).encode() + msg).digest()) assert kk==k print(k) def sign(priv, ctx, msg): k = bytes_to_long(ctx + md5(str(priv).encode() + msg).digest()) z = bytes_to_long(md5(ctx + msg).digest()) r = int((k * G).x()) % n s = (pow(k, -1, n) * (z + r * priv)) % n return r, s rr,ss=sign(priv,b'n1junior_2025',f'cat /flag{0}'.encode()) print(rr) print(ss)
level2
对比level1我们少了m s g msg m s g 的内容也就是无法得到z z z ,但是我们多了一组输入。
整理一下我们的已知条件
s 1 k 1 ≡ z 1 + r 1 ⋅ p r i v ( m o d n ) s 2 k 2 ≡ z 2 + r 2 ⋅ p r i v ( m o d n ) s_1k_1 \equiv z_1+r_1 \cdot priv \pmod{n}\\
s_2k_2 \equiv z_2+r_2 \cdot priv \pmod{n}
s 1 k 1 ≡ z 1 + r 1 ⋅ p r i v ( m o d n ) s 2 k 2 ≡ z 2 + r 2 ⋅ p r i v ( m o d n )
到这里容易想到HNP问题,这也就是为什么卡了两天一点进展都没有(怒)。后面想想其实也能知道为什么,两个模式解三个未知数,并且z 1 , z 2 , p r i v z_1,z_2,priv z 1 , z 2 , p r i v 没有很强的范围要求。本身维度较小的情况解决n . b i t = 157 n.bit=157 n . b i t = 1 5 7 的情况是完全不可能的。所以漏洞似乎转移到了MD5上,可惜到比赛结束也没想出什么漏洞。后面看了大佬的WP才知道MD5的利用方式。
简单来说就是可以通过hashclash得到两个不同的s 1 , s 2 s1,s2 s 1 , s 2 实现MD5(s1)=MD5(s2)并且这样的s 1 , s 2 s1,s2 s 1 , s 2 还有非常优秀的性质即对于任意字符串suf都有MD5(s1+suf)=MD5(s2+suf),再会看题目中对z z z 的构造方式,z = bytes_to_long(md5(ctx + msg).digest()),签名过程中m s g msg m s g 是固定的,因此我们可以构造出两个c t x ctx c t x 实现z z z 相同,这样问题就变成二元一次的模式方程组求解,显然可以求出p r i v priv p r i v 。
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 E = EllipticCurve(GF(0x1337_ca7_eae368ff5d702e6067aaaa77ca_ca7_1337), [0, 3]) G, n = E(1, 2), E.order() ctx=[..,..] ctx=[bytes.fromhex(ct) for ct in ctx] r= [..,..] s= [..,..] k=[] for i in range(2): R=E.lift_x(r[i]) g=bytes_to_long(ctx[i]) print(g*2^128%n) print((g+1)*2^128%n) for _ in range(2): kk=R.log(G) if kk>g*2^128%n and kk<(g+1)*2^128%n: k.append(kk) break R=-R print(k) priv=(s[0]*k[0]-s[1]*k[1])*inverse(r[0]-r[1],n)%n print('priv=',priv) def sign(priv, ctx, msg): k = bytes_to_long(ctx + md5(str(priv).encode() + msg).digest()) z = bytes_to_long(md5(ctx + msg).digest()) r = int((k * G).x()) % n s = (pow(k, -1, n) * (z + r * priv)) % n return r, s rr,ss=sign(priv,b'n1junior_2025',f'cat /flag{2}'.encode()) print(rr) print(ss)
level3
首先回顾一下Weierstrass曲线的点加公式椭圆曲线一般写成:E : y 2 = x 3 + a x + b E: \; y^2 = x^3 + ax + b E : y 2 = x 3 + a x + b
对于两点P = ( x 1 , y 1 ) , Q = ( x 2 , y 2 ) , P = (x_1, y_1), \quad Q = (x_2, y_2), P = ( x 1 , y 1 ) , Q = ( x 2 , y 2 ) , 它们的和R = P + Q = ( x 3 , y 3 ) R = P + Q = (x_3, y_3) R = P + Q = ( x 3 , y 3 ) 的计算公式是:
斜率
λ = y 2 − y 1 x 2 − x 1 , ( P ≠ Q ) \lambda = \frac{y_2 - y_1}{x_2 - x_1}, \quad (P \neq Q)
λ = x 2 − x 1 y 2 − y 1 , ( P = Q )
横坐标
x 3 = λ 2 − x 1 − x 2 x_3 = \lambda^2 - x_1 - x_2
x 3 = λ 2 − x 1 − x 2
纵坐标
y 3 = λ ( x 1 − x 3 ) − y 1 y_3 = \lambda (x_1 - x_3) - y_1
y 3 = λ ( x 1 − x 3 ) − y 1
对比前面的level可见我们现在只有s s s 的信息,缺失r r r 意味着我们缺失了k k k 的内容。不过由于k k k 的特殊构造,我们可以知道k k k 高位的信息也就是c t x ctx c t x ,于是我们可以写成k i = c t x ⋅ 2 128 + h k_i=ctx \cdot 2^{128} +h k i = c t x ⋅ 2 1 2 8 + h ,这里的h h h 也就是MD5产生的值不超过128位。顺着这个思路我们得到R i = ( c t x ⋅ 2 128 + h ) G R_i=(ctx \cdot 2^{128} +h) G R i = ( c t x ⋅ 2 1 2 8 + h ) G 。为了消去h h h 的影响不妨差分得到
R 2 − R 1 = ( c t x 2 − c t x 1 ) ⋅ 2 128 G R 3 − R 1 = ( c t x 3 − c t x 1 ) ⋅ 2 128 G R 4 − R 1 = ( c t x 4 − c t x 1 ) ⋅ 2 128 G R 5 − R 1 = ( c t x 5 − c t x 1 ) ⋅ 2 128 G R_2-R_1=(ctx_2-ctx_1)\cdot 2^{128} G\\
R_3-R_1=(ctx_3-ctx_1)\cdot 2^{128} G\\
R_4-R_1=(ctx_4-ctx_1)\cdot 2^{128} G\\
R_5-R_1=(ctx_5-ctx_1)\cdot 2^{128} G
R 2 − R 1 = ( c t x 2 − c t x 1 ) ⋅ 2 1 2 8 G R 3 − R 1 = ( c t x 3 − c t x 1 ) ⋅ 2 1 2 8 G R 4 − R 1 = ( c t x 4 − c t x 1 ) ⋅ 2 1 2 8 G R 5 − R 1 = ( c t x 5 − c t x 1 ) ⋅ 2 1 2 8 G
可以发现等式右边全是已知量,我们只需要知道其中任意一个R i R_i R i 就可以得到全部的R R R ,于是我们不妨记R 1 = ( r 1 , y ) R_1=(r_1,y) R 1 = ( r 1 , y ) ,对于R i R_i R i ,我们记对应得常数点为D i = ( d x , d y ) D_i=(d_x,d_y) D i = ( d x , d y ) ,根据点加法我们知道
λ = d y − y d x − r 1 \lambda = \frac{d_y - y}{d_x - r_1}
λ = d x − r 1 d y − y
则
r i = λ 2 − d x − r 1 r_i=\lambda^2 - d_x - r_1
r i = λ 2 − d x − r 1
带入s i k i = z + r i ⋅ p r i v s_ik_i=z+r_i \cdot priv s i k i = z + r i ⋅ p r i v 得到
s i k i = z + ( λ 2 − d x − r 1 ) ⋅ p r i v s_ik_i=z+(\lambda^2 - d_x - r_1)\cdot priv
s i k i = z + ( λ 2 − d x − r 1 ) ⋅ p r i v
为了在多项式环中表示(不能出现分式),我们将等式两边乘以分母 ( d x i − r 1 ) 2 (dx_i - r_1)^2 ( d x i − r 1 ) 2 :
( s i k i − z ) ⋅ ( d x i − r 1 ) 2 = [ ( d y i − y 1 ) 2 − ( r 1 + d x i ) ( d x i − r 1 ) 2 ] ⋅ p r i v (s_i k_i - z) \cdot (dx_i - r_1)^2
= \Big[ (dy_i - y_1)^2 - (r_1 + dx_i)(dx_i - r_1)^2 \Big] \cdot priv
( s i k i − z ) ⋅ ( d x i − r 1 ) 2 = [ ( d y i − y 1 ) 2 − ( r 1 + d x i ) ( d x i − r 1 ) 2 ] ⋅ p r i v
如法炮制可以得到四个类似得等式,再又因为我们设出了R 1 = ( r 1 , y ) R_1=(r_1,y) R 1 = ( r 1 , y ) ,于是我们还能得到两个等式
y 2 = r 1 3 + 3 s 1 k 1 = z + r 1 ⋅ p r i v y^2=r_1^3+3 \\
s_1k_1=z+r_1\cdot priv
y 2 = r 1 3 + 3 s 1 k 1 = z + r 1 ⋅ p r i v
一共六个式子。然后我们梳理一下未知量的个数发现我们只缺少{ h , z , p r i v , r 1 , y } \{h,z,priv,r_1,y\} { h , z , p r i v , r 1 , y } 这五个,此时差不多已经胜券在握了。
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 * from hashlib import md5 E = EllipticCurve(GF(0x1337_ca7_eae368ff5d702e6067aaaa77ca_ca7_1337), [0, 3]) G, n = E(1, 2), E.order() ctx=['ca44bdf16ebf41337b32e92b53da7da9d9f5a5c6a96a4d3cf90942822e2a25b40ffbeacdfe134e1c8bea761a888f587db154c96aa04c7539e0e4475396dda070a73dcff0477b54b15d34d07cd9571e30886e833a01463eeac95af6bbec47fee32025a695f4d007fdc88b50f7b17072ec5948ffc708f6eee6b0d2f71bafab9fbdc3062b2f701959dff2df72e3051dfe82112da950008f9dd02e8a82dd08ba1b2e0f04fddf38722b41c74bbca39e4b0e629d17ad9d9313a6d22f07dc5c396912ba318bd0079baccacac255808167fb06c7d4ef8dc7c4a5fa0c1ccd3576cd4bbdbc4d38b2dfa9369ca8b271c58188945e9dca885c3976d6a3e4c7d759dedb175c86a35d6390db80955d6c05b17150945327daaac4f0c689c116437243f2ed9516058ebc0eee3cad76de6b4650422a125a75347024551169566fdc3e5e8a6ee79f96b2eedaf2c92dfe5a29c1dac5168b45bec176fcb63156cefd128961064c37f58e350fbb7f20904e07de5b099a9dcc99bd0e9de8fcabd2a396c32458dedc97d938', 'ca44bdf16ebf41337b32e92b53da7da9d9f5a5c6a96a4d3cf90942822e2a25b40ffbeacdfe134e1c8bea761a888f587db154c96aa04c7539e0e4475396dda070a73dcff0477b54b15d34d07cd9571e30886e833a01463eeac95af6bbec47fee32025a695f4d007fdc88b50f7b17072ec5948ffc708f6eee6b0d2f71bafab9fbdc3062b2f701959dff2df72e3051dfe82112da950008f9dd02e8a82dd08ba1b2e0f04fddf38722b41c74bbca39e4b0e629d17ad9d9313a6d22f07dc5c396912ba318bd0079baccacac255808167fb06c7d4ef8dc7c4a5fa0c1ccd3576cd4bbdbc4d38b2dfa9369ca8b271c58188945e9dca885c3976d6a3e4c7d759dedb175c86a35d6390db80955d6c05b17150945327daaac470c689c116437243f2ed9516058ebc0eee3cad76de6b4650422a925a75347024551169566fdc3e5e0a6ee79f96b2eedaf2c92dfe5a29c1dac5168b45bec176fc363156cefd128961064c37f58e350fbb7f20904e07de5b099a9d4c99bd0e9de8fcabd2a396c324585edc97d938', 'ca44bdf16ebf41337b32e92b53da7da9d9f5a5c6a96a4d3cf90942822e2a25b40ffbeacdfe134e1c8bea761a888f587db154c96aa04c7539e0e4475396dda070a73dcff0477b54b15d34d07cd9571e30886e833a01463eeac95af6bbec47fee32025a695f4d007fdc88b50f7b17072ec5948ffc708f6eee6b0d2f71bafab9fbdc3062b2f701959dff2df72e3051dfe82112da9d0008f9dd02e8a82dd08ba1b2e0f04fddf38722b41c74bbca39ecb0e629d17ad9d9313a6d22f07dcdc396912ba318bd0079baccacac255808167fb06c7d4ef8d47c4a5fa0c1ccd3576cd4bbdbc4d38b2dfa9369ca8b271c58188145e9dca885c3976d6a3e4c7d7595edb175c86a35d6390db80955d6c05b17150945327daaac4f0c689c116437243f2ed9516058ebc0eee3cad76de6b4650422a125a75347024551169566fdc3e5e8a6ee79f96b2eedaf2c92dfe5a29c1dac5168b45bec176fcb63156cefd128961064c37f58e350fbb7f20904e07de5b099a9dcc99bd0e9de8fcabd2a396c32458dedc97d938', 'ca44bdf16ebf41337b32e92b53da7da9d9f5a5c6a96a4d3cf90942822e2a25b40ffbeacdfe134e1c8bea761a888f587db154c96aa04c7539e0e4475396dda070a73dcff0477b54b15d34d07cd9571e30886e833a01463eeac95af6bbec47fee32025a695f4d007fdc88b50f7b17072ec5948ffc708f6eee6b0d2f71bafab9fbdc3062b2f701959dff2df72e3051dfe82112da9d0008f9dd02e8a82dd08ba1b2e0f04fddf38722b41c74bbca39ecb0e629d17ad9d9313a6d22f07dcdc396912ba318bd0079baccacac255808167fb06c7d4ef8d47c4a5fa0c1ccd3576cd4bbdbc4d38b2dfa9369ca8b271c58188145e9dca885c3976d6a3e4c7d7595edb175c86a35d6390db80955d6c05b17150945327daaac470c689c116437243f2ed9516058ebc0eee3cad76de6b4650422a925a75347024551169566fdc3e5e0a6ee79f96b2eedaf2c92dfe5a29c1dac5168b45bec176fc363156cefd128961064c37f58e350fbb7f20904e07de5b099a9d4c99bd0e9de8fcabd2a396c324585edc97d938', 'ca44bdf16ebf41337b32e92b53da7da9d9f5a546a96a4d3cf90942822e2a25b40ffbeacdfe134e1c8bea761a880f597db154c96aa04c7539e0e447d396dda070a73dcff0477b54b15d34d07cd9571e30886e83ba01463eeac95af6bbec47fee32025a695f4d007fdc88b50f7b1f071ec5948ffc708f6eee6b0d2f79bafab9fbdc3062b2f701959dff2df72e3051dfe82112da950008f9dd02e8a82dd08ba1b2e0f04fddf38722b41c74bbca39e4b0e629d17ad9d9313a6d22f07dc5c396912ba318bd0079baccacac255808167fb06c7d4ef8dc7c4a5fa0c1ccd3576cd4bbdbc4d38b2dfa9369ca8b271c58188945e9dca885c3976d6a3e4c7d759dedb175c86a35d6390db80955d6c05b17150945327daaac4f0c689c116437243f2ed9516058ebc0eee3cad76de6b4650422a125a75347024551169566fdc3e5e8a6ee79f96b2eedaf2c92dfe5a29c1dac5168b45bec176fcb63156cefd128961064c37f58e350fbb7f20904e07de5b099a9dcc99bd0e9de8fcabd2a396c32458dedc97d938'] s=[11154694896732447905689374736338618815276091537, 74933960157450757671256061540484810446107345123, 51873568513748381018351602926997229967297958417, 31581818612454837771407319666185013378637291428, 43899203296223282768371041553603410773341644426] ctx=[(bytes_to_long(bytes.fromhex(ct))) for ct in ctx] P.<r_1,y,h,z,priv>=PolynomialRing(GF(n)) TG=2^128*G D_points=[] for i in range(1,len(ctx)): D_points.append((ctx[i]-ctx[0])*TG) polynomials=[] polynomials.append(y^2-r_1^3-3) for i in range(len(ctx)): k_i=ctx[i]*2^128+h if i==0: polynomials.append(s[i]*k_i-z-r_1*priv) else: D_i=D_points[i-1] dx_i, dy_i = D_i.xy() num = (dy_i - y)^2 - (r_1 + dx_i) * (dx_i - r_1)^2 den = (dx_i - r_1)^2 poly_sig = (s[i] * k_i - z) * den - num * priv polynomials.append(poly_sig)
现在polynomials里面存的就是我们刚刚推导的六个式子,再通过格罗布纳基方法化简多项式。
1 2 3 4 5 6 7 8 I=P.ideal(polynomials) B=I.groebner_basis() poly_h = None for poly in B: vars_in_poly = poly.variables() if len(vars_in_poly) == 1 and vars_in_poly[0] == h: poly_h = poly break
得到的poly_h就是只含有h h h 的多项式h + 109715005092730082481014013427730311863191312900 h+109715005092730082481014013427730311863191312900 h + 1 0 9 7 1 5 0 0 5 0 9 2 7 3 0 0 8 2 4 8 1 0 1 4 0 1 3 4 2 7 7 3 0 3 1 1 8 6 3 1 9 1 3 1 2 9 0 0 ,不过这里不能用roots方法求解h h h 因为这个多项式虽然只有一个元h h h ,但是他的类型依旧是多元多项式,无法调用。
下面提供两种方法求解h h h 。
1 2 3 4 const_term=poly_h.coefficient({h:0})#返回h^0的系数,也就是常系数 #或者调用const_term=poly_h.constant_coefficient() h_val=n-const_term print(h_val)
1 2 3 4 R.<h>=PolynomialRing(GF(n)) poly_h_univar=R(poly_h) # 转成一元多项式 roots=poly_h_univar.roots(multiplicities=False) print(roots)
最后由于得到了h h h 于是我们可以算出k i k_i k i 的值,这样就变成和level2一样的问题了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 k1=int(ctx[1]*2^128+h_val) k2=int(ctx[2]*2^128+h_val) r1=int((k1*G).x())%n r2=int((k2*G).x())%n priv_val=(s[1]*k1-s[2]*k2)*inverse(r1-r2,n)%n def sign(priv, ctx, msg): k = bytes_to_long(ctx + md5(str(priv).encode() + msg).digest()) z = bytes_to_long(md5(ctx + msg).digest()) r = int((k * G).x()) % n s = (pow(k, -1, n) * (z + r * priv)) % n return r, s rr,ss=sign(priv_val,b'n1junior_2025',f'cat /flag{3}'.encode()) print(rr) print(ss)
完整代码如下
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 from Crypto.Util.number import * from hashlib import md5 E = EllipticCurve(GF(0x1337_ca7_eae368ff5d702e6067aaaa77ca_ca7_1337), [0, 3]) G, n = E(1, 2), E.order() ctx=['ca44bdf16ebf41337b32e92b53da7da9d9f5a5c6a96a4d3cf90942822e2a25b40ffbeacdfe134e1c8bea761a888f587db154c96aa04c7539e0e4475396dda070a73dcff0477b54b15d34d07cd9571e30886e833a01463eeac95af6bbec47fee32025a695f4d007fdc88b50f7b17072ec5948ffc708f6eee6b0d2f71bafab9fbdc3062b2f701959dff2df72e3051dfe82112da950008f9dd02e8a82dd08ba1b2e0f04fddf38722b41c74bbca39e4b0e629d17ad9d9313a6d22f07dc5c396912ba318bd0079baccacac255808167fb06c7d4ef8dc7c4a5fa0c1ccd3576cd4bbdbc4d38b2dfa9369ca8b271c58188945e9dca885c3976d6a3e4c7d759dedb175c86a35d6390db80955d6c05b17150945327daaac4f0c689c116437243f2ed9516058ebc0eee3cad76de6b4650422a125a75347024551169566fdc3e5e8a6ee79f96b2eedaf2c92dfe5a29c1dac5168b45bec176fcb63156cefd128961064c37f58e350fbb7f20904e07de5b099a9dcc99bd0e9de8fcabd2a396c32458dedc97d938', 'ca44bdf16ebf41337b32e92b53da7da9d9f5a5c6a96a4d3cf90942822e2a25b40ffbeacdfe134e1c8bea761a888f587db154c96aa04c7539e0e4475396dda070a73dcff0477b54b15d34d07cd9571e30886e833a01463eeac95af6bbec47fee32025a695f4d007fdc88b50f7b17072ec5948ffc708f6eee6b0d2f71bafab9fbdc3062b2f701959dff2df72e3051dfe82112da950008f9dd02e8a82dd08ba1b2e0f04fddf38722b41c74bbca39e4b0e629d17ad9d9313a6d22f07dc5c396912ba318bd0079baccacac255808167fb06c7d4ef8dc7c4a5fa0c1ccd3576cd4bbdbc4d38b2dfa9369ca8b271c58188945e9dca885c3976d6a3e4c7d759dedb175c86a35d6390db80955d6c05b17150945327daaac470c689c116437243f2ed9516058ebc0eee3cad76de6b4650422a925a75347024551169566fdc3e5e0a6ee79f96b2eedaf2c92dfe5a29c1dac5168b45bec176fc363156cefd128961064c37f58e350fbb7f20904e07de5b099a9d4c99bd0e9de8fcabd2a396c324585edc97d938', 'ca44bdf16ebf41337b32e92b53da7da9d9f5a5c6a96a4d3cf90942822e2a25b40ffbeacdfe134e1c8bea761a888f587db154c96aa04c7539e0e4475396dda070a73dcff0477b54b15d34d07cd9571e30886e833a01463eeac95af6bbec47fee32025a695f4d007fdc88b50f7b17072ec5948ffc708f6eee6b0d2f71bafab9fbdc3062b2f701959dff2df72e3051dfe82112da9d0008f9dd02e8a82dd08ba1b2e0f04fddf38722b41c74bbca39ecb0e629d17ad9d9313a6d22f07dcdc396912ba318bd0079baccacac255808167fb06c7d4ef8d47c4a5fa0c1ccd3576cd4bbdbc4d38b2dfa9369ca8b271c58188145e9dca885c3976d6a3e4c7d7595edb175c86a35d6390db80955d6c05b17150945327daaac4f0c689c116437243f2ed9516058ebc0eee3cad76de6b4650422a125a75347024551169566fdc3e5e8a6ee79f96b2eedaf2c92dfe5a29c1dac5168b45bec176fcb63156cefd128961064c37f58e350fbb7f20904e07de5b099a9dcc99bd0e9de8fcabd2a396c32458dedc97d938', 'ca44bdf16ebf41337b32e92b53da7da9d9f5a5c6a96a4d3cf90942822e2a25b40ffbeacdfe134e1c8bea761a888f587db154c96aa04c7539e0e4475396dda070a73dcff0477b54b15d34d07cd9571e30886e833a01463eeac95af6bbec47fee32025a695f4d007fdc88b50f7b17072ec5948ffc708f6eee6b0d2f71bafab9fbdc3062b2f701959dff2df72e3051dfe82112da9d0008f9dd02e8a82dd08ba1b2e0f04fddf38722b41c74bbca39ecb0e629d17ad9d9313a6d22f07dcdc396912ba318bd0079baccacac255808167fb06c7d4ef8d47c4a5fa0c1ccd3576cd4bbdbc4d38b2dfa9369ca8b271c58188145e9dca885c3976d6a3e4c7d7595edb175c86a35d6390db80955d6c05b17150945327daaac470c689c116437243f2ed9516058ebc0eee3cad76de6b4650422a925a75347024551169566fdc3e5e0a6ee79f96b2eedaf2c92dfe5a29c1dac5168b45bec176fc363156cefd128961064c37f58e350fbb7f20904e07de5b099a9d4c99bd0e9de8fcabd2a396c324585edc97d938', 'ca44bdf16ebf41337b32e92b53da7da9d9f5a546a96a4d3cf90942822e2a25b40ffbeacdfe134e1c8bea761a880f597db154c96aa04c7539e0e447d396dda070a73dcff0477b54b15d34d07cd9571e30886e83ba01463eeac95af6bbec47fee32025a695f4d007fdc88b50f7b1f071ec5948ffc708f6eee6b0d2f79bafab9fbdc3062b2f701959dff2df72e3051dfe82112da950008f9dd02e8a82dd08ba1b2e0f04fddf38722b41c74bbca39e4b0e629d17ad9d9313a6d22f07dc5c396912ba318bd0079baccacac255808167fb06c7d4ef8dc7c4a5fa0c1ccd3576cd4bbdbc4d38b2dfa9369ca8b271c58188945e9dca885c3976d6a3e4c7d759dedb175c86a35d6390db80955d6c05b17150945327daaac4f0c689c116437243f2ed9516058ebc0eee3cad76de6b4650422a125a75347024551169566fdc3e5e8a6ee79f96b2eedaf2c92dfe5a29c1dac5168b45bec176fcb63156cefd128961064c37f58e350fbb7f20904e07de5b099a9dcc99bd0e9de8fcabd2a396c32458dedc97d938'] s=[11154694896732447905689374736338618815276091537, 74933960157450757671256061540484810446107345123, 51873568513748381018351602926997229967297958417, 31581818612454837771407319666185013378637291428, 43899203296223282768371041553603410773341644426] ctx=[(bytes_to_long(bytes.fromhex(ct))) for ct in ctx] P.<r_1,y,h,z,priv>=PolynomialRing(GF(n)) TG=2^128*G D_points=[] for i in range(1,len(ctx)): D_points.append((ctx[i]-ctx[0])*TG) polynomials=[] polynomials.append(y^2-r_1^3-3) for i in range(len(ctx)): k_i=ctx[i]*2^128+h if i==0: polynomials.append(s[i]*k_i-z-r_1*priv) else: D_i=D_points[i-1] dx_i, dy_i = D_i.xy() num = (dy_i - y)^2 - (r_1 + dx_i) * (dx_i - r_1)^2 den = (dx_i - r_1)^2 poly_sig = (s[i] * k_i - z) * den - num * priv polynomials.append(poly_sig) I=P.ideal(polynomials) B=I.groebner_basis() poly_h = None for poly in B: vars_in_poly = poly.variables() if len(vars_in_poly) == 1 and vars_in_poly[0] == h: poly_h = poly break const_term=poly_h.coefficient({h:0})#返回h^0的系数,也就是常系数 h_val=n-const_term k1=int(ctx[1]*2^128+h_val) k2=int(ctx[2]*2^128+h_val) r1=int((k1*G).x())%n r2=int((k2*G).x())%n priv_val=(s[1]*k1-s[2]*k2)*inverse(r1-r2,n)%n def sign(priv, ctx, msg): k = bytes_to_long(ctx + md5(str(priv).encode() + msg).digest()) z = bytes_to_long(md5(ctx + msg).digest()) r = int((k * G).x()) % n s = (pow(k, -1, n) * (z + r * priv)) % n return r, s rr,ss=sign(priv_val,b'n1junior_2025',f'cat /flag{3}'.encode()) print(rr) print(ss)
至此三个level已经全部解决。
对于构造hashclash的更多细节可以看这里 。