该更新更新blog了,总的来说今年体验不如去年,能学到东西的除了Card Shark的MT19937通用方法外,没啥新鲜的了
Choose_U_Flag NTRU但不完全对。也没理解到出题人的意思,而且似乎题目的NTRU也有点问题。但不影响直接非预期解决。
题目流程挺简单的,服务端随机生成一个12bytes的可打印字符串,然后用NTRU加密,加密以后把密文coefficient发回来。
服务端提供了一次解密服务,但要求我们上传的密文coefficient不能是他发过来的那个,也就是说不能直接解密刚刚发回来的密文
对发送的密文的判断只是简单的判断和random_key的系数是否完全相同。哥们直接找个系数加个64不就不同了,反正到时候算的时候就被模掉了….
事实上我还可以旋转密文,还可以LLL日私钥,反正挺多解法的,就是不知道预期该是怎样的
exp 没有exp,key_coefficients复制下来找个你觉得喜欢的数字把他加个64发过去就拿到random_key了
有没有可能预期就是这样呢
Compare 题目要我们先传个表达式上去,这个表达式得够油,油到后面自己能根据, 来控制这个表达式的True of False。
考的就是同态。看看加密
Encrypt: 所以计算 ,解密出来的就是 了,但由于mod的存在让结果永远都是整数了。
n是512位的 所以如果 那么 大概率是小于 的,而 那 大概率是大于 的
所以我们传expr = MSG - 2 ** 511 < 0
,每轮算一次 发过去,服务器那边eval()
的内容就会变成 这个式子与 的正确性大概率是一样的了。
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *from Crypto.Util.number import getPrime, getRandomNBitInteger, inverseimport reCHALLENGE_ID = '' p = remote(CHALLENGE_ID + '.2022.capturetheflag.fun' , 1337 , ssl=True ) resp = p.recvuntil("expr:" ) p.sendline('MSG - 2 ** 511 < 0' ) for _ in range (100 ): resp = p.recvuntil("msg = " ) n = int (re.findall(r'n = (.*)\n' ,resp.decode())[0 ]) a = int (re.findall(r'a = (.*)\n' ,resp.decode())[0 ]) b = int (re.findall(r'b = (.*)\n' ,resp.decode())[0 ]) s = (a * inverse(b,n * n)) % (n * n) p.sendline(str (s)) print(p.recv(1024 ))
Card Shark 这题算是比较有意思的一题了(全靠其他题衬托。
就是单纯的日MT19937,通过前面的轮次来获得getrandbits()
的输出,利用这些输出来还原生成器内部的状态,从而预测后续的随机数。
这里用到一个利用线性关系构造矩阵的解决办法。
这个办法很牛,理论上只要拿到19968位的输出,就能日烂MT19937,就是需要的时间有一点长。
如果对MT19937有了解过,就知道里面的所有操作线性的,也就是说每一次输出的每一位都是由一开始的624个状态线性变换得来的。
因为每个状态是32位,不妨记初始状态为 ,那么对于某次输出的某一位 ,会有
这里的 就是一个线性关系。那么,对于19968个输出 ,可以有一整个矩阵 的线性关系对应。
而这个攻击方法的核心就是 只与 是哪次输出的哪一位有关系。
也就是说,只要我们能知道19968位,并且知道它们都是第几次输出的第几位,就能够还原出 。
首先,从 到 遍历 ,分别用这19968个状态去生成已知那19968位对应的 。这里得到的 由于 取的都是1,所以它就是 的每一行。
通过上面的构造拿到对应的 以后,再用我们已知的那19968位去计算得到
然后就是由状态推后面的状态了
参考 https://www.anquanke.com/post/id/205861#h3-9 后面的扩展部分。
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 from random import Randomfrom tqdm import tqdmLENGTH = 19968 mask = '11110000000000000000000000000000' knownbits = mask.count('1' ) states = [] for i in tqdm(range (LENGTH)): state = ['0' ] * LENGTH state[i] = '1' states.append((3 ,tuple ([int ('' .join(state[_:_ + 32 ]),2 ) for _ in range (0 ,LENGTH,32 )] + [0 ]),None )) T = Matrix(GF(2 ),LENGTH,LENGTH) for i in tqdm(range (LENGTH)): R = Random() R.setstate(states[i]) Z = [] for _ in range (LENGTH // knownbits): rd = bin (R.getrandbits(32 ))[2 :].zfill(32 ) for r,j in zip (rd,mask): if j == '1' : Z.append(int (r)) for z in range (len (Z)): T[i,z] = Z[z] save(T,'Matrix.sobj' )
生成完 拿去还原状态就行了。
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 from random import Randomfrom tqdm import tqdmdef recoverState (leak ): x = T.solve_left(vector(leak)) x = '' .join([str (i) for i in x]) state = [] for i in range (624 ): tmp = int (x[i * 32 :(i + 1 ) * 32 ], 2 ) state.append(tmp) return state def backfirst (state ): high = 0x80000000 low = 0x7fffffff mask = 0x9908b0df tmp = state[623 ] ^^ state[396 ] if tmp & high == high: tmp = mask ^^ tmp tmp <<= 1 tmp |= 1 else : tmp <<= 1 return int ((1 << 32 - 1 ) | tmp & low), int (tmp & low) def pwn (leak ): state = recoverState(leak) L = [leak[i] for i in range (100 * knownbits)] prng = Random() guess1, guess2 = backfirst(state) state[0 ] = guess1 s = state prng.setstate((3 , tuple (s + [0 ]), None )) g1 = [] for _ in range (100 ): rd = bin (prng.getrandbits(32 ))[2 :].zfill(32 ) for r,j in zip (rd,mask): if j == '1' : g1.append(int (r)) if g1 == L: print("first" ) prng.setstate((3 , tuple (s + [0 ]), None )) return prng state[0 ] = guess2 s = state prng.setstate((3 , tuple (s + [0 ]), None )) g2 = [] for _ in range (100 ): rd = bin (prng.getrandbits(32 ))[2 :].zfill(32 ) for r,j in zip (rd,mask): if j == '1' : g1.append(int (r)) if g2 == L: print("second" ) print(s + [0 ]) prng.setstate((3 , tuple (s + [0 ]), None )) return prng T = load('Matrix.sobj' ) length = 19968 mask = '11110000000000000000000000000000' knownbits = mask.count('1' ) prng = Random() leaks = open ('state' ,'r' ).read() leak = [int (i) for i in leaks] R = pwn(leak)