
randomized random
题目
# FROM python:3import randomwith open("flag.txt","rb") as f: flag=f.read()for i in range(2**64): print(random.getrandbits(32)+flag[random.getrandbits(32)%len(flag)]) input()
题目分析
因为题目有用到random库,所以这是MT19937问题,可以参考Xenny师傅的文章:MT19937 分析 | Xenny 的博客。
而题目每次交互里的遍历都会返回random.getrandbits(32)+flag[random.getrandbits(32)%len(flag)
,也就是一个随机32bit数与flag里的一个随机字节的和。
题目需要我们通过次遍历去还原出flag。
而每次得到的信息(即这个随机32bit数)不仅会因为加法而影响一定的bit数,还是不连续的(因为加flag里的一个随机字节那个地方也用了getrandbits);所以得取前面一部分bit来打MT19937。
我跟naby师傅当时是想着取前16bit应该是没问题的,但没打出来(今天赛后听Dexter师傅他们说,他们测试发现——影响的bit挺多的,所以取前10bit好点)。
之后就是造矩阵来打MT19937,这个思路的原理可以参考鸡块师傅的这篇文章:2024-同济大学第二届网络安全新生赛CatCTF-wp-crypto
(关键还是在数据量上,数据量不够确实还原不出来正确的state,算是我们当时没注意到的一个问题)
打出来后就是整理出flag了
首先肯定是相减得到flag的那些字符,但单单这样肯定还不能得到flag(毕竟是随机的,所以得到的都是随机的flag字符),但这些随机的flag字符的顺序与flag的长度有关。
而我们知道——flag里的左括号和右括号肯定不会有很多,所以我们分别找两组左括号和右括号所对应的getrandbits,然后相减做gcd就能得到长度,然后就根据长度去整理出flag即可。
exp
因为整的时候Dexter、鸡块、Lst4r几位师傅他们说了些不同的方式(但都是同一种思路),用时都差不多
第一种就是刚刚说的造矩阵:
点击展开代码
# sage 10.5import randomfrom sage.all import *from Crypto.Util.number import *from random import *from tqdm import *from pwn import *
# 1, get datax,c=[],[]sh = remote("1.95.57.127", 3001)for i in trange(2500): tmp1=eval(sh.recvline()) sh.send(b"\n") x.append(tmp1) c.append(tmp1>>22)
# 2, recover MT and get random bytes of (f_inf, f_len)RNG = Random()length = 19968def construct_a_row(RNG): # 这里是关键, 一定要跟你已知数据的生成方式一致 row = [] for i in range(2500): row+=list(map(int, (bin(RNG.getrandbits(32) >> 22)[2:].zfill(10)))) RNG.getrandbits(32) return row
L = []for i in trange(length): state = [0]*624 temp = "0"*i + "1"*1 + "0"*(length-1-i) for j in range(624): state[j] = int(temp[32*j:32*j+32],2) RNG.setstate((3,tuple(state+[624]),None)) L.append(construct_a_row(RNG))L = Matrix(GF(2),L)
known = []for i in c: known+=list(map(int, (bin(i)[2:].zfill(10))))print("solve_left")s = L.solve_left(vector(GF(2),known))print("ok")init = "".join(list(map(str,s)))print("init")state = []for i in range(624): state.append(int(init[32*i:32*i+32],2))print("state")
prng = Random()prng.setstate(tuple([3, tuple(state+[624]), None]))f_inf = []f_loc = []for i in range(2500): x1 = long_to_bytes(x[i]-prng.getrandbits(32)) x2 = prng.getrandbits(32) # print(x1, x2) f_inf.append(x1) f_loc.append(x2)
# 3, get flag_len and recover flagloc1, loc2 = [], []i = 0while len(loc1) < 2 or len(loc2) < 2: if f_inf[i].decode() == "{": loc1.append(f_loc[i]) if f_inf[i].decode() == "}": loc2.append(f_loc[i]) i += 1f_len = GCD(loc1[0]-loc1[1], loc2[0]-loc2[1])i = 0flag = ["*"]*f_lenwhile 1: if all(i != "*" for i in flag): print("".join(flag)) break if flag[f_loc[i]%f_len] == "*": flag[f_loc[i]%f_len] = f_inf[i].decode() i += 1'''100%|███████████████████████████████████████████████████████████████████████████████| 2500/2500 [01:34<00:00, 26.46it/s]100%|████████████████████████████████████████████████████████████████████████████| 19968/19968 [01:41<00:00, 197.41it/s]solve_leftokinitstateTPCTF{Ez_MTI9937_pr3d1cTi0n}TPCTF{Ez_MTI9937_pr3d1cTi0n}'''
第二种是使用maple师傅之前整的gf2bv库,算是比较无脑?(不过就是需要多堆点数据):
点击展开代码
# gf2bv# python3import randomfrom gf2bv import LinearSystemfrom gf2bv.crypto.mt import MT19937from tqdm import *from pwn import *from Crypto.Util.number import *import pickle
def mt19937(bs, out): lin = LinearSystem([32] * 624) mt = lin.gens()
rng = MT19937(mt) zeros = [] for o in out: zeros.append((rng.getrandbits(32)>>22) ^ int(o)) rng.getrandbits(32) sol = lin.solve_one(zeros)
rng = MT19937(sol) pyrand = rng.to_python_random() return pyrand
if(0): print(random.getstate()[1]) x,c=[], [] for i in trange(3500): tmp1=random.getrandbits(32) random.getrandbits(32) x.append(tmp1) c.append(tmp1>>22) RNG = mt19937(int(10), c) for i in trange(832): xx = RNG.getrandbits(32) assert x[i] == xx and c[i] == (xx>>22) RNG.getrandbits(32)
if(1): # 1, get data nums = 5000 sh = remote("1.95.57.127", 3001) out = [] cout = [] for _ in trange(nums): x = eval(sh.recvline()) out.append(x) cout.append(x>>22) sh.send(b"\n") # 2, recover MT and get random bytes of (f_inf, f_len) RNG = mt19937(16, cout) f_inf = [] f_loc = [] for i in range(nums): x1 = long_to_bytes(out[i]-RNG.getrandbits(32)) x2 = RNG.getrandbits(32) f_inf.append(x1) f_loc.append(x2) # 3, get flag_len and recover flag loc1, loc2 = [], [] i = 0 while len(loc1) < 2 or len(loc2) < 2: if f_inf[i].decode() == "{": loc1.append(f_loc[i]) if f_inf[i].decode() == "}": loc2.append(f_loc[i]) i += 1 f_len = GCD(loc1[0]-loc1[1], loc2[0]-loc2[1]) i = 0 flag = ["*"]*f_len while 1: if all(i != "*" for i in flag): print("".join(flag)) break if flag[f_loc[i]%f_len] == "*": flag[f_loc[i]%f_len] = f_inf[i].decode() i += 1'''100%|███████████████████████████████████████████████████████████████████████████████| 5000/5000 [02:59<00:00, 27.93it/s]TPCTF{Ez_MTI9937_pr3d1cTi0n}'''
后记
这次算是对MT19937这个问题有了一点理解,希望下次能会分析吧
(至于后面的题,因为当时没全部下载,所以等后面看别的师傅的blog里有没有再接着复现吧)