啊太菜了太菜了…打这CTF也有半年了,还是这么菜,好想有一次能ak啊….. 两道题一题手快找到资料拿了个三血, 另外一题做了半天都没出来, 有条关系式理解错了真的想锤人….
0x00 cubic problem 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 math import gcdfrom functools import reducefrom fractions import Fraction as FracN = 6 def read_num (prompt ): try : num = int (input (prompt)) except : return 0 return num if num > 0 else 0 print(f"Please give me {N} pairs of positive integers (x,y,z) " f"satisfying the equation `x/(y+z) + y/(z+x) + z/(x+y) = {N} `\n" ) anss = [] mark = 0 for i in range (N): x = read_num("[>] x: " ) y = read_num("[>] y: " ) z = read_num("[>] z: " ) if x * y * z == 0 : mark = 1 print("This is not what i want!\n" ) break if reduce(gcd, [x, y, z]) != 1 : mark = 1 print("This is not what i want!\n" ) break if Frac(x, y+z) + Frac(y, z+x) + Frac(z, x+y) != N: mark = 1 print("This is not what i want!\n" ) break ans = tuple (sorted ([x, y, z])) if ans in anss: mark = 1 print("This is not what i want!\n" ) break else : print("You are right!\n" ) anss.append(ans) if mark == 0 : flag = open ('/flag' , 'r' ).read() print("flag is: " + flag + "\n" ) else : print("Something wrong!\n" )
analyses 找到6组$a,b,c$满足: $$ {a \over b+c}+{b \over a+c}+{c \over a+b} = 6 $$ 这题拿到就直接去搜了, 没想到直接出来一篇知乎的文章 , 看完之后才知道原来是椭圆曲线…. 原理什么的里面讲的很清楚了, 这边就不再说了, 只是中间有一个化式子为维尔斯特拉斯形式的过程, 我是误打误撞弄成的(因为公式是同一个, 直接改一下知乎里的就行了), 后来找到了paper - > An unusual cubic representation problem , 才找到具体的公式 $$ a={8(N+3)-x+y \over 2(N+3)(4-x)} \\ b={8(N+3)-x-y \over 2(N+3)(4-x)} \\ c={-4(N+3)-x(N+2) \over (N+3)(4-x)} $$ 这里的$N = 6$ 有了这个我们就能把式子化成$y^2 = ax^3 + bx^2+cx$的形式了, 这里又有个问题, 该怎么利用上面的式子快速化成我们要的形式呢当然是拿草稿纸算啦
这里可以利用sage的解方程, 解出这个关系式来
1 2 3 4 5 6 7 8 a,b,c,x,y = var('a' ),var('b' ),var('c' ),var('x' ),var('y' ) N = 6 a=(8 *(N+3 )-x+y)/(2 *(N+3 )*(4 -x)) b=(8 *(N+3 )-x-y)/(2 *(N+3 )*(4 -x)) c=(-4 *(N+3 )-(N+2 )*x)/((N+3 )*(4 -x)) f = a^3 + b^3 +c^3 - 9 *a*b*c - 5 *(a^2 *b+a^2 *c+b^2 *a+b^2 *c+c^2 *a+c^2 *b) print(f.solve(y)[0 ])
然后利用解出来的参数跟着知乎上的内容写代码就行了, 要知道原理就去知乎看吧, 链接也在上面了
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 a,b,c,x,y = var('a' ),var('b' ),var('c' ),var('x' ),var('y' ) N = 6 a=(8 *(N+3 )-x+y)/(2 *(N+3 )*(4 -x)) b=(8 *(N+3 )-x-y)/(2 *(N+3 )*(4 -x)) c=(-4 *(N+3 )-(N+2 )*x)/((N+3 )*(4 -x)) f = a^3 + b^3 +c^3 - 9 *a*b*c - 5 *(a^2 *b+a^2 *c+b^2 *a+b^2 *c+c^2 *a+c^2 *b) print(f.solve(y)[0 ]) ee=EllipticCurve([0 ,213 ,0 ,288 ,0 ]) P = ee(-200 ,680 ) def check (num ): return num if num > 0 else 0 def orig (P,N ): x=P[0 ] y=P[1 ] a=(8 *(N+3 )-x+y)/(2 *(N+3 )*(4 -x)) b=(8 *(N+3 )-x-y)/(2 *(N+3 )*(4 -x)) c=(-4 *(N+3 )-(N+2 )*x)/((N+3 )*(4 -x)) da=denominator(a) db=denominator(b) dc=denominator(c) l=lcm(da,lcm(db,dc)) return [a*l,b*l,c*l] i = 1 arr = [] while True : f = True res = orig(i*P,6 ) for j in res: if check(j) == 0 : f = False if f: arr.append(res) i+=1 if len (arr) == 6 : break arr
0x01 simultaneous problem 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 from functools import reducefrom flag import flagdef encrypt (m, e, n ): n = int (n) size = n.bit_length() // 2 m_low = m & ((1 << size) - 1 ) m_high = (m >> size) b = (m_low**2 - m_high**3 ) % n EC = EllipticCurve(Zmod(n), [0 , b]) return (EC((m_high, m_low)) * e).xy() def decrypt (c, d, n ): n = int (n) size = n.bit_length() // 2 c_high, c_low = c b = (c_low**2 - c_high**3 ) % n EC = EllipticCurve(Zmod(n), [0 , b]) m_high, m_low = (EC((c_high, c_low)) * d).xy() m_high, m_low = int (m_high), int (m_low) return (m_high << size) | m_low def gen_prime (size ): p = random_prime(1 << size, lbound=1 << (size-1 )) while p % 3 != 2 : p = random_prime(1 << size, lbound=1 << (size-1 )) q = random_prime(1 << size, lbound=1 << (size-1 )) while q % 3 != 2 : q = random_prime(1 << size, lbound=1 << (size-1 )) if q > p: p, q = q, p return ZZ(p), ZZ(q) SIZE = 512 HINTSIZE = 96 assert len (flag) == 42 flag = int .from_bytes(flag, "big" ) masks = [randint(1 << (SIZE-1 ), 1 << SIZE) for _ in range (3 )] masked_flag = reduce(lambda a, b: a ^^ b, masks, flag) count = 0 ciphertexts = [] x = random_prime(floor((1 <<(2 *SIZE-2 ))**0.373 ), proof=False ) while count < 3 : try : p, q = gen_prime(SIZE) n = p * q y = random_prime(floor(n**0.373 ), proof=False ) zbound = -1 * int (((p-q) * round (n ** 0.25 ) * y) // (3 * (p + q))) z_ = zbound + ((p + 1 )*(q + 1 )*y - zbound) % x e = ((p + 1 ) * (q + 1 ) * y - z_) // x assert (e*x - y*(p+1 )*(q+1 ) == -z_) assert (abs (z_) < abs (zbound)) assert (gcd(x, y) == 1 ) d = inverse_mod(e, (p+1 )*(q+1 )) c = encrypt(masks[count], e, n) assert decrypt(c, d, n) == masks[count] ciphertexts.append({ "n" : n, "e" : e, "c" : c, "hint" : p & ((1 <<HINTSIZE)-1 ) }) count += 1 except KeyboardInterrupt: break except ZeroDivisionError: pass print("masked_flag = " , masked_flag) print("ciphertexts = " , ciphertexts)
analyses 用的ECC加密了三个masked_flag
, 但基本上没办法从ECC上入手. 回来看前面的密钥生成.
这里每一轮的私钥都是不同的, 每一轮都随机生成素数y
, 并利用x,y
两个素数生成一个公钥e
因为每轮用的x
都是相同的, 而且我们有关系式: $$ \begin{cases} e_1 x = y_1(p_1+1)(q_1+1) - z_1 \\ e_2 x = y_2(p_2+1)(q_2+1) - z_2 \\ e_3 x = y_3(p_3+1)(q_3+1) - z_1 \end{cases} $$ 化简一下可以有: $$ \begin{cases} e_1 x -y_1n_1 = y_1(p_1+q_1+1) - z_1 \\ e_2 x -y_2n_2 = y_2(p_2+q_2+1) - z_2 \\ e_3 x -y_3n_3 = y_3(p_3+q_3+1) - z_3 \end{cases} $$ 记$y_i(p_i+q_i+1) - z_i$为$s_i$, 可以造出这个一个格子 $$ (x,y_1,y_2,y_3) \begin{pmatrix} \sqrt n&e_1&e_2&e_3 \\ 0&-n_1&0&0 \\ 0&0&-n_2&0 \\ 0&0&0&-n_3 \\ \end{pmatrix} = (\sqrt nx,s_1,s_2,s_3) $$ 这里的$x$和$y$都够小, 足够规约出结果来. 规约出$(\sqrt nx,s_1,s_2,s_3)$可以求出$x$和$y$
当时做到这里, 被超大的$z$卡住了, 做了一下午都没有想出该怎么用Hint,x,y
把p
搞出来. (zbound
有关的式子被我弄错了!! 没有想到这个zbound
虽然被消去但还是有作用的!)
比赛结束以后d33师傅发了一篇paper , 但是不能直接用里面的方法求,(界卡的太死了?) 说是要利用coppersmith的姿势和hint
一点一点的把p整出来. 看了一天copper也没啥结果, 只能等wp了. 出来了再好好看看到底该怎么调coppersmith
后来翻虎符wp翻到一篇wp 里面有一个二分法的方法, 看完才发现原来自己一直把zbound
的式子搞错了… 不过就算没搞错自己也可能想不到那个构造方程的方法, 虽然那个方法不算特别复杂, 也算是好好学习了一波
参考这里 的二分法 先来看一下生成$zbound$和$z$的等式 $$ zbound = \lfloor {(p-q)\cdot \lfloor n^{1\over 4}\rceil \cdot y \over 3(p+q)} \rceil \\ z = zbound + (y(p+1)(q+1) - zbound \mod x) $$ 这里我一开始做的时候忽略了一个细节, $y(p+1)(q+1) - zbound \mod x$是小于$x$的! 而$y$是略大于$x$的,那么会有 $$ {z \over y} =\lfloor {(p-q)\cdot \lfloor n^{1\over 4}\rceil \over 3(p+q)} \rceil +{(y(p+1)(q+1) - zbound \mod x) \over y} \approx \lfloor {(p-q)\cdot \lfloor n^{1\over 4}\rceil \over 3(p+q)} \rceil \tag 1 $$ 第二项, 也就是省略掉的那个项是小于$1$的, 所以这个约等于两边其实是非常非常接近的.
我们上面计算了$s = y(p+q+1) - z$, 再根据$(1)$式, 就可以有 $$ {s \over y} = p + q + 1 - {z \over y} \approx p+q+1-\lfloor {(p-q)\cdot \lfloor n^{1\over 4}\rceil \over 3(p+q)} \rceil $$ 我们可以把后面的$\lfloor \rceil$先忽略, 因为它的影响并不大. 我们记$K = \lfloor {s \over y} \rceil$, 式子会是$p+q$和$p-q$的一个关系(其实这里已经式$p,q$的一个关系式了, 理论上根据$n = pq$已经可以解方程得到$p$的近似了, 但是为了计算上的方便, 才看成$p+q$和$p-q$ $$ K = p+q+1-{(p-q) \cdot \lfloor n^{1\over 4}\rceil \over 3(p+q)} $$ 移项, 并利用$(p-q)^2 = (p+q)^2 - 4pq$,可以把式子化成一个关于$p+q$的方程 $$ 9((K-1)(p+q)-(p+q)^2 )^2 - n^{1 \over 2}((p+q)^2 - 4n) = 0 \\ 9X^2(K-X-1)^2 - n^{1 \over 2}(X^2 - 4n) = 0 $$ 但是前面用了太多的近似, 导致这个方程的解会偏离真正的$p+q$并且不再是整数解(这里我试了sage自带的解方程, 可能是我姿势不对, 解出来的结果再取整并不是正确的), 观察到当$X = K$和$X=0$的时候左边的式子是异号的, 这个时候就可以利用高中的知识! 根存在性定理, 并用二分法去逼近一个靠近解的整数$X_0$.
而这个$X_0 \approx p+q $, 利用这个$X_0$就可以大概率的解出$p,q$了
因为$X_0$只是个近似, 而且求出来还可能是奇数, 所以需要前后调一下, 比如+1 -1 +2 -2 等等 关于为啥不能是奇数, $p+q$怎么会是奇数呢hhh
至此, 我们成功的算出了$p,q$, 所有的参数包括私钥也都可以求出来了, 再利用题目自带的解密算法解密就行了.
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 from gmpy2 import *from Crypto.Util.number import long_to_bytes as l2bdef find_p_puls_q (f,k ): s = 0 b = k for i in range (2000 ): a = (s+b) // 2 if f.subs(x==a) > 0 : s = a else : b = a return a def find_p (pq,n ): p = var('p' ) if pq^2 - 4 *n<0 : return [] f = p^2 - pq*p+n r = f.roots() res = [] for i in r: if i[0 ]>0 : res.append(int (i[0 ])) return (res[0 ],res[1 ]) def decrypt (c, d, n ): n = int (n) size = n.bit_length() // 2 c_high, c_low = c b = (c_low**2 - c_high**3 ) % n EC = EllipticCurve(Zmod(n), [0 , b]) m_high, m_low = (EC((c_high, c_low)) * d).xy() m_high, m_low = int (m_high), int (m_low) return (m_high << size) | m_low masked_flag = 10629883656219490982178951599484437698263214790268091560719514148215968944141123323489358824379735126966804354267426040231682908197526817342035901781691470 ciphertexts = [...] PK = [] N = [ciphertexts[0 ]['n' ],ciphertexts[1 ]['n' ],ciphertexts[2 ]['n' ]] E = [ciphertexts[0 ]['e' ],ciphertexts[1 ]['e' ],ciphertexts[2 ]['e' ]] C = [ciphertexts[0 ]['c' ],ciphertexts[1 ]['c' ],ciphertexts[2 ]['c' ]] M=iroot(int (N[0 ]),2 )[0 ] a=[0 ]*4 a[0 ]=[M,E[0 ],E[1 ],E[2 ]] a[1 ]=[0 ,-N[0 ],0 ,0 ] a[2 ]=[0 ,0 ,-N[1 ],0 ] a[3 ]=[0 ,0 ,0 ,-N[2 ]] Mat = matrix(ZZ,a) Mat_LLL=Mat.LLL() x = int (abs (Mat_LLL[0 ][0 ])//M) assert x * M == abs (Mat_LLL[0 ][0 ])S=[Mat_LLL[0 ][1 ],Mat_LLL[0 ][2 ],Mat_LLL[0 ][3 ]] A=[round (N[0 ]^0.25 )^2 ,round (N[1 ]^0.25 )^2 ,round (N[2 ]^0.25 )^2 ] assert (E[0 ] * x - S[0 ]) % N[0 ] == 0 assert (E[1 ] * x - S[1 ]) % N[1 ] == 0 assert (E[2 ] * x - S[2 ]) % N[2 ] == 0 Y = [0 ,0 ,0 ] for i in range (3 ): Y[i] = (E[i]*x - S[i]) // N[i] KK = [0 ,0 ,0 ] for i in range (3 ): KK[i] = (E[i] * x -Y[i] * N[i] ) // Y[i] x = var('x' ) FF = [0 ,0 ,0 ] for i in range (3 ): FF[i] = (3 *(KK[i]-1 )*x-3 *x^2 )^2 - A[i]*(x^2 -4 *N[i]) ppq1 = find_p_puls_q(FF[0 ],KK[0 ]) ppq2 = find_p_puls_q(FF[1 ],KK[1 ]) ppq3 = find_p_puls_q(FF[2 ],KK[2 ]) p1,q1 = find_p(ppq1+1 ,N[0 ]) p2,q2 = find_p(ppq2+1 ,N[1 ]) p3,q3 = find_p(ppq3+1 ,N[2 ]) d1,d2,d3 = inverse_mod(E[0 ],(p1+1 )*(q1+1 )),inverse_mod(E[1 ],(p2+1 )*(q2+1 )),inverse_mod(E[2 ],(p3+1 )*(q3+1 )) l2b(decrypt(C[0 ],d1,N[0 ])^^decrypt(C[1 ],d2,N[1 ])^^decrypt(C[2 ],d3,N[2 ])^^masked_flag)