huangx607087 11月的切题4 0.Introduction 11月月底,又做了几个题,然后 2 号考密码学,5 号考无线数字通信技术。两场考试考完,趁 6 号休息一天,整理一下之前做过的题目。
1.[强网拟态2024]brokenoracle 11月25日,向外校的师傅要了一个强网拟态的题目,感觉不是很难,但因为自己想简单了,所以本地搞还是用了 3 小时。
先看一眼题目代码:
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 from Crypto.Util.number import *from Crypto.Cipher import AESfrom hashlib import sha256import socketserverimport signalimport osimport stringimport randomfrom Crypto.PublicKey import RSAfrom Crypto.Util.number import getPrime,getStrongPrimefrom Crypto.Random import get_random_bytesclass Task (socketserver.BaseRequestHandler): def _recvall (self ): BUFF_SIZE = 4096 data = b'' while True : part = self .request.recv(BUFF_SIZE) data += part if len (part) < BUFF_SIZE: break return data.strip() def send (self, msg, newline=True ): try : if newline: msg += b'\n' self .request.sendall(msg) except : pass def recv (self, prompt=b'> ' ): self .send(prompt, newline=False ) return self ._recvall() def handle (self ): p=getStrongPrime(512 ) q=getStrongPrime(512 ) e=65537 n=p*q phi=(p-1 )*(q-1 ) d=pow (e,-1 ,phi) token=os.urandom(1000 //8 ) c=pow (bytes_to_long(token),e,n) self .send(b"n=" ) self .send(hex (n).encode()) self .send(b"your token is" ) self .send(hex (c).encode()) signal.alarm(60 ) for i in range (100 ): self .send(b"give me your message to decrypt" ) c=self .recv() try : c=int (c,16 ) except : self .send(b"wrong!" ) self .request.close() m=pow (c,d,n) brokenm=m&((2 **(40 )-1 )*(2 **492 )) self .send(hex (brokenm).encode()) self .send(b"give me your token" ) t1=self .recv() if bytes .fromhex(t1.decode())==token: f=open ("./flag" ,"rb" ) flag=f.read() f.close() self .send(flag) else : self .send(b"wrong! try again!" ) self .request.close()class ThreadedServer (socketserver.ThreadingMixIn, socketserver.TCPServer): pass class ForkedServer (socketserver.ForkingMixIn, socketserver.TCPServer): pass if __name__ == "__main__" : HOST, PORT = '0.0.0.0' , 9999 print ("HOST:POST " + HOST+":" + str (PORT)) server = ForkedServer((HOST, PORT), Task) server.allow_reuse_address = True server.serve_forever()
很明显,这个题和 RSA ParityOracle类似,不过既不知道最高位,也不是知道最低位,当构造密文发过去之后,服务器会给你解密,并返回对应明文的 $40$ 比特内容,对应的位权为 $2^{492}\sim2^{531}$。时间限制 1min,查询限制 100 次。
既然这样,那就可以从Parity Oracle那里想。可以先本地写个代码测一下:
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 from Crypto.Util.number import * p=getStrongPrime(512 ) q=getStrongPrime(512 ) e=65537 n=p*q phi=(p-1 )*(q-1 ) d=pow (e,-1 ,phi) token=bytes_to_long(os.urandom(1000 //8 )) c=pow ((token),e,n) mask=(2 **40 -1 )<<492 print ('{:0256X}' .format (token))def E (x ): return int (pow (x,e,n))def D (x ): return int (pow (x,d,n))&mask u=pow (16 ,e,n) c0=cfor i in range (9 ): dc0=D(c0) dc0>>=492 print ('{:10X}' .format (dc0)) c0=c0*u%n
观察前6个数据,结果分别为:$\mathtt{D887DFB86EH,887DF86E2H,87DFB86E2FH,7DFB86E2F7H,DFB86E2F73H,FB86E2F731H}$。有很明显的移位特征,因为你对密文乘了个 $16^e$ 的系数,解密后明文也乘 $16$,对应到十六进制输出就是明显的移位特征。
由于 $t$ 是 $1000$ 位,$n$ 是 $1024$ 位,因此从第 $7$ 个数据开始,本应是 $\mathtt{B86E2F731XH}$,却变成了 $\mathtt{FD147B8278H}$,很明显,这应该是取模过的结果,也就是 $16^{6}t=xn+y$。这里把 $16^6t$ 写成了 $n$ 进制的格式,$x,y<n$。由于是乘了 $16$ 之后,才超过了 $n$,因此 $x$ 一定不超过 $15$。看看能不能搞出 $x$?
可以发现,这里 $x=1$,也就是这一次在取模前,数值为 $x$ 的一倍多,也就是 $\left \lfloor\dfrac{16^6t}{n} \right \rfloor=1$。
再测两组可以发现:$\mathtt{FD147B8278H}$ 变为 $\mathtt{15EE0436E8H}$ 对应的 $x$ 也是 $1$ ,和 $\mathtt{15EE0436F4H}$ 有很小的误差。同理,$\mathtt{15EE0436F4H}$ 变为 $\mathtt{B61FBFC52H}$ 对应的 $x $ 为 $5$ (实际计算为 $\mathtt{B61FBFC49H}$),也就是当 $t$ 再乘 $16$ 之后,$t$ 达到了 $n$ 的 $5$ 倍。
结合三个 $x$,大概可以知道: $$ \left \lfloor\dfrac{16^8t}{n} \right \rfloor=277 $$ (其中 $277=\mathtt{115H}$)
因此,我们可以利用这个方法,求出 $\left \lfloor\dfrac{16^{k}t}{n}\right\rfloor$ 对应的值。这里会带有误差,但可以认为误差不超过 $16$ 就是正确的。不过问题是,如果想要获得一个非常精准的值,最后的值应该超过 $n$。并且你只有 $100$ 次机会,$16^{100}=2^{400}$,看来不太可行。
但可以利用这个方法,一次爆破三位十六进制数,即将上面所有的 $16$ 换成 $4096$,然后我们开一个数组 $M$,记录上个解密的有效状态,再开一个数组 $T$,代表 $\left \lfloor\dfrac{4096^{k}t}{n}\right\rfloor$ 结果的 $4096$ 进制表示。
最终本地测试代码如下,耗时只有两秒,估计就算对靶机应该也不会超过 5 秒吧:
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 from pwn import *from Crypto.Util.number import *from tqdm import * sh=remote('127.0.0.1' ,60712 ) sh.recvuntil(b'n=' ) n=sh.recvline(keepends=False ) n=sh.recvline(keepends=False ) n=eval (n) t=sh.recvline(keepends=False ) t=sh.recvline(keepends=False ) t=eval (t)print (n,t) M=[] T=[] mask=(2 **40 -1 )<<492 RATE2N=4096 POW2NE=pow (RATE2N,65537 ,n)%nfor i in trange(99 +1 ): sh.recvuntil(b'>' ) sh.send(hex (t)[2 :].encode()) msg=sh.recvline(keepends=False ) middval=eval (msg)>>492 M.append(middval) if (i==0 ): t=t*POW2NE%n continue prevmid=(M[-2 ]&0x000fffffffffffffffff )*RATE2N prevmid<<=492 for j in range (RATE2N): y=prevmid-(j*n) y=y&mask y=y>>492 if (abs (y-middval)<RATE2N): T.append(j) break t=t*POW2NE%nprint (M)print (T) RR2=0 for i in range (len (T)): RR2*=4096 RR2+=T[i] C=(RR2*n)>>(12 *99 )print (C.bit_length()) ansstr='{:0250x}' .format (C+1 )print (ansstr) sh.sendline(ansstr.encode()) sh.interactive()
测试结果图中,[0,1,1166,...,3942]
的数组表就表示的相除向下取整得到结果的从高位到低位的 $4096$ 进制的表示。不过最后解出来 $t$ 之后,经过测试,再加1才是正确答案。
2.[HITCTF2024]supercomputer 先看一眼题目
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 def f (x ): x ^= ((x >> 11 ) & 0xb114514bb114514b ) x ^= ((x << 13 ) & 0x114514cc114514cc ) x ^= ((x << 17 ) & 0xaa191981aa191981 ) x ^= ((x >> 19 ) & 0xb1919810b1919810 ) x ^= ((x << 23 ) & 0x114514cd114514cd ) x ^= ((x >> 29 ) & 0xb114514ab114514a ) x ^= ((x << 31 ) & 0x114514cb114514cb ) x ^= ((x << 37 ) & 0xaa19198caa19198c ) x ^= ((x >> 41 ) & 0xb191981db191981d ) x ^= ((x << 43 ) & 0x114514ce114514ce ) return xdef calc (n ): x = 0x1122334455667788 for _ in range (n): x = f(x) return xassert calc(1 ) == 0xaa732acf4eb6a79d assert calc(10 ) == 0x28262ecbd673b7d7 assert calc(100 ) == 0x23a6b8be477b7c3 assert calc(1000 ) == 0x93737600f66aa785 assert calc(100000 ) == 0x8a762387c4e3aed8 assert calc(1000000 ) == 0x1267270a756bf7df assert calc(10000000 ) == 0x39377648f4e367d7 assert calc(100000000 ) == 0x82232f8777773e8a assert calc(1000000000 ) == 0x8762b83d6faa783 assert calc(10000000000 ) == 0x27237cdd6726788 assert calc(100000000000 ) == 0x97f6b44e7fe66de assert calc(1000000000000 ) == 0x88723b8666ff27ca assert calc(10000000000000 ) == 0x8a32670ee5ef6ec0 assert calc(100000000000000 ) == 0x993b3b4265ea6edb assert calc(1000000000000000 ) == 0x8b732ec065e7e795 print ('flag{%s}' % hex (calc(10000000000000000 )))
题目给出了一个 $64$ 比特的变换过程,并给出了 $10^{0}\sim10^{14}$ 次变换过程的结果,要求经过 $10^{15}$ 次比特变换的结果。
很明显,题目给出的数字很大,并且是比特变换,因此可以往矩阵快速幂的地方想。
然后十分钟不到,写好了这个函数,构造之后却不对。。然后一步一步地对着调试,发现只进行一步的时候是对的,进行两步就开始不对了。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def get (t,a,b ): vec2k=[1 <<i for i in range (64 )] xornum=vec2k.copy() if (t==1 ): for i in range (64 ): xornum[i]>>=a if (t==-1 ): for i in range (64 ): xornum[i]<<=a xornum[i]&=((2 **64 )-1 ) mask=[int (bool (b&(1 <<i))) for i in range (64 )] xored=[mask[i]*xornum[i] for i in range (64 )] result=[(vec2k[i]^^xored[i])&((2 **64 )-1 ) for i in range (64 )] M=[[0 for _ in range (64 )] for __ in range (64 )] for i in range (64 ): for j in range (64 ): M[i][j]=((result[i]&(1 <<j))!=0 ) return Matrix(GF(2 ),M)
然后调前两步的时候,发现先乘 $M_{13}$ 再乘 $M_{11}$ 可以得到正确结果。原来矩阵变换方式为 $(\prod M)\vec v=M_1M_2M_3\vec v$(还是以三个举例)。根据矩阵变换的几何意义,先跟 $\vec v$ 相乘的是 $M_3$,也就是正着乘,先进行了 $M_3$ 对应的变换。那就还是一个一个来,倒着乘。
那么答案就出来了:
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 def get (t,a,b ): vec2k=[1 <<i for i in range (64 )] xornum=vec2k.copy() if (t==1 ): for i in range (64 ): xornum[i]>>=a if (t==-1 ): for i in range (64 ): xornum[i]<<=a xornum[i]&=((2 **64 )-1 ) mask=[int (bool (b&(1 <<i))) for i in range (64 )] xored=[mask[i]*xornum[i] for i in range (64 )] result=[(vec2k[i]^^xored[i])&((2 **64 )-1 ) for i in range (64 )] M=[[0 for _ in range (64 )] for __ in range (64 )] for i in range (64 ): for j in range (64 ): M[i][j]=((result[i]&(1 <<j))!=0 ) return Matrix(GF(2 ),M) Marr=[] Marr.append(get(-1 ,11 ,0xb114514bb114514b )) Marr.append(get(1 ,13 ,0x114514cc114514cc )) Marr.append(get(1 ,17 ,0xaa191981aa191981 )) Marr.append(get(-1 ,19 ,0xb1919810b1919810 )) Marr.append(get(1 ,23 ,0x114514cd114514cd )) Marr.append(get(-1 ,29 ,0xb114514ab114514a )) Marr.append(get(1 ,31 ,0x114514cb114514cb )) Marr.append(get(1 ,37 ,0xaa19198caa19198c )) Marr.append(get(-1 ,41 ,0xb191981db191981d )) Marr.append(get(1 ,43 ,0x114514ce114514ce )) Marr=Marr[::-1 ] M=identity_matrix(GF(2 ),64 )for mi in Marr: M=M*mi v0=[(0x1122334455667788 &(1 <<i))!=0 for i in range (64 )] v0=vector(v0) v1=(M**10000000000000000 )*v0print (v1)print ('flag{%s}' %hex (vector(ZZ,[1 <<i for i in range (64 )])*(vector(ZZ,list (v1)))))
3.[强网拟态2024]notiv 瞄一眼题目(注:题目非核心部分 与原题略有改动,比如取消了proof_of_work,并改了一下flag的形式,但不影响题目原本难度和解题做法)。
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 from hashlib import sha256import socketserverimport osimport sysimport randomimport signalimport stringfrom hashlib import sha256from mypad import *from datetime import *from Crypto.Cipher import AESfrom random import *from Crypto.Util.number import *class Task (socketserver.BaseRequestHandler): def _recvall (self ): BUFF_SIZE = 2048 data = b'' while True : part = self .request.recv(BUFF_SIZE) data += part if len (part) < BUFF_SIZE: break return data.strip() def send (self, msg, newline=True ): try : if newline: msg += b'\n' self .request.sendall(msg) except : pass def recv (self, prompt=b'> ' ): self .send(prompt, newline=False ) return self ._recvall() def close (self ): self .request.close() def handle (self ): try : signal.alarm(120 ) count = 0 for _ in range (50 ): seed(os.urandom(8 )) key = pad_x923(b"" ) print (key.hex ()) chal = hex (getrandbits(64 *3 ))[2 :].zfill(16 *3 ) print (str (datetime.now()),chal) for i in range (200 ): iv = long_to_bytes(getrandbits(128 )).rjust(16 , b"\00" ) cipher = AES.new(key, AES.MODE_CBC, iv) test = self .recv(b":> " ).decode() tb = bytes .fromhex(test) ret = cipher.encrypt(pad_x923(tb)) if len (ret) > 16 : self .send(b"forbid" ) continue self .send((iv+ret).hex ().encode()) s = self .recv(b">> " ).decode() print ('s=' ,s) iv = bytes .fromhex(s[:32 ]) print ('iv=' ,iv) ans = bytes .fromhex(s[32 :]) print ('ans=' ,ans) cipher = AES.new(key, AES.MODE_CBC, iv) att = cipher.decrypt(ans) print ('attpad=' ,att) att = unpad_x923(att) print ('att=' ,att,'chalEncode=' ,chal.encode()) if att == chal.encode(): count += 1 self .send(b"you got %d score" % count) if count >= 20 : FLAG="flag{Success at " +str (datetime.now())+"}" FLAG=FLAG.replace(' ' ,'-' ) self .send(b"cong!your flag is " +FLAG.encode()) else : self .send(b"sorry,plz try again" ) except : self .send(b"something wrong! plz try again!" ) self .close()class ThreadedServer (socketserver.ThreadingMixIn, socketserver.TCPServer): pass class ForkedServer (socketserver.ForkingMixIn, socketserver.TCPServer): pass if __name__ == "__main__" : HOST, PORT = '127.0.0.1' , 60717 print (f'running on {HOST} :{PORT} ' ) server = ForkedServer((HOST, PORT), Task) server.allow_reuse_address = True server.serve_forever()
其中:
1 2 3 4 5 from Crypto.Random import get_random_bytes pad_x923=lambda x,block_size=16 :x+get_random_bytes((block_size-(len (x)%block_size)-1 ))+bytes ([(block_size-len (x)%block_size)]) unpad_x923=lambda x,block_size=16 :x[:-((x[-1 ]-1 )%block_size)-1 ]
注:get_random_bytyes
调用的是 os.urandom
,不可预测。
题目拿到第一眼:getrandbits(128)
,这妥妥的MT随机数预测,200次机会,说明156次就可以预测后面的随机数了。然后发现,题目要求是构造一个给定的 48 字节的串的密文(这个可以求出来,因为这个串是 getrandbits(64*3)
生成的,前向反推一次就行)。
但在题目中,每次只能对至多 15 字节内容进行加密,且加密填充方式为:填充 $n-1$ 个随机字符,然后再放入 1 字节填充长度来保证分组。当明文长度为 48 时,意味着密文长度为 64(因为要填充一整组)。且题目使用了 CBC 模式,也就是前一组密文和后一组明文异或后才是真正的加密内容,即:$IV=C_0$,$C_i=E(C_{i-1}\oplus M_i),i=1,2,3,4$。
经过分析后,我们可以大致地得到这些事实:
由于填充的随机性,因此,必须构造 15 字节的密文 ,这样才能保证 填充后字符串的确定性(只会填充一个\x01
)
使用的IV是可以预测的,可以从这个地方出发考虑。
构造密文时,IV是可指定的,因此 $C_1$ 可以直接构造。同时,对 $C_4$ 的要求是:保证最后四个比特为 $0$ 就行,对应 $1/16$ 的概率。
因此本题重点是主要考虑 $C_2$ 和 $C_3$,且要保证能达到约 40 percent 的构造成功率。
设 $\mathrm{chal}=H_1,H_2,H_3,H_4$,其中可以假设 $H_4$ 为全零(根据第三条后半部分)。考虑直接构造:由于 $C_2=E_K(H_2\oplus C_1)$。而在之前选择明文的过程中:$C_2=E_K(M_2\oplus IV_2)$,因此,我们构造出来的 $M_2$ 应该为 $M_2=IV_2\oplus C_1\oplus H_2$。因此我们需要保证 $M_2$ 的最后一字节的十六进制为 $\mathtt{01H}$,这个概率为 $1/256$。
而同理,$M_3$ 也是 $1/256$ 的概率构造。那这样成功率算下来就是 $1/65536$,故期望得分为: $$ E(\mathrm{Point})=\frac{50\times200}{65536}=0.1526 $$ 也就是平均尝试六次,才只能得 $1$ 分。
借鉴的MaPl大哥的EXP,其中,mttool见本人博客中2021年7月的Explore MT19937文章。
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 from pwn import *from tqdm import *from mttool import * AES_P, AES_C = [], []def turn (sh, data: bytes ): assert len (data) == 15 sh.recvuntil(b':> ' ) sh.sendline(data.hex ().encode()) ret = sh.recvline().decode().strip() iv = bytes .fromhex(ret[:32 ]) ciph = bytes .fromhex(ret[32 :]) AES_P.append(xor(data + b"\1" , iv)) AES_C.append(ciph) return iv, ciphdef getrandbits (F, bits ): assert bits % 8 == 0 state = [F.getstate() for _ in range (bits//32 )][::-1 ] return b"" .join(w.to_bytes(4 , "big" ) for w in state)def skip_state (F, state_count ): for _ in range (state_count): F.getstate()def xor (a, b ): return bytes (a[i] ^ b[i] for i in range (min (len (a), len (b)))) sh = remote('127.0.0.1' , 60717 )for i in range (50 ): AES_P, AES_C = [], [] D = [] F = MT19937() for _ in range (624 //4 ): my_plain = b"0" *15 + b"\x01" iv, ciph = turn(sh, my_plain[:15 ]) for j in [12 , 8 , 4 , 0 ]: D.append(int .from_bytes(iv[j:j+4 ], 'big' )) F.setstate(D) F.invtwist() F.invtwist() skip_state(F, 624 -6 ) chal = getrandbits(F, 24 *8 ).hex ().encode() print (f"chal: {chal} " ) P1 = chal[:16 ] P2 = chal[16 :32 ] P3 = chal[32 :] skip_state(F, 624 ) times = 200 - 624 //4 solve = [] next_iv = getrandbits(F, 128 ) while times > 2 : for P, C in zip (AES_P, AES_C): if (C[-1 ] ^ P2[-1 ] ^ next_iv[-1 ]) == 1 : payload = xor(P2, next_iv) payload = xor(C, payload) assert payload[-1 ] == 1 iv, ciph = turn(sh, payload[:15 ]) assert iv == next_iv next_iv = getrandbits(F, 128 ) IV, C1, C2 = xor(P, P1), C, ciph if (C2[-1 ] ^ P3[-1 ] ^ next_iv[-1 ]) == 1 : payload = xor(P3, next_iv) payload = xor(C2, payload) iv, ciph = turn(sh, payload[:15 ]) assert iv == next_iv next_iv = getrandbits(F, 128 ) times -= 1 C3 = ciph for P, C in zip (AES_P, AES_C): if (P[-1 ] ^ C3[-1 ]) % 16 == 0 and (C[-1 ] ^ next_iv[-1 ]) == 1 : C4 = Cs print (f"[+] get solve:" ) print (f"\tIV: {IV.hex ()} " ) print (f"\tC1: {C1.hex ()} " ) print (f"\tC2: {C2.hex ()} " ) print (f"\tC3: {C3.hex ()} " ) print (f"\tC4: {C4.hex ()} " ) solve.append((IV, C1, C2, C3, C4)) break else : turn(sh, b"0" *15 ) next_iv = getrandbits(F, 128 ) times -= 1 while times: turn(sh, b"0" *15 ) times -= 1 sh.recvuntil(b'>> ' ) if solve: sh.sendline(b"" .join(solve[0 ]).hex ().encode()) else : sh.send(b"00" *32 )print (sh.readline())
因为这个算法只求一个 $C_2$,然后对这个 $C_2$ 根据当前的IV去求 $C_3$,这样来的话概率当然会很低了。看了wp之后发现, wp的思路是存储所有满足条件的 $C_2$ 及其对应的 $C_1,IV$,然后对每个 $C_2$ 去用当前IV猜 $C_3$。由于一开始 $C_2$ 的数量少,因此如果没有找到合适的 $C_3$,则再获取一组可行的 $C_2$ 。
测了一下,一次运行大概可以获取 $19$ 个 $C_2$,尝试次数为 $19\times9=171$ 次。获取可行的 $C_3$ 的概率为 $1/256$,因此当获得 $19$ 个可行的 $C_2$ 后,就有很大的概率获取一个 $C_3$。
至于当前IV?IV是在获取 $156$ 组数据之后就可以预测出来,因此可以先进行测试,如果没有 $C_2$ 或者根据当前的 $C_2$ 找不到可行的 $C_3$,就可以再找一组 $C_2$。再本地进行枚举,找不到合适的就不交,不会浪费查询次数。
至于 $C_4$ ,可以认为 $H_4$ 为全 $0$,因此 $C_4$ 可以根据 $C_3$ 构造出来,概率为 $1/16$。
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 from pwn import *from tqdm import *from Crypto.Util.number import *from mttool import * sh=remote('127.0.0.1' ,60717 )def sendMyMsg (msg ): msghex=msg.hex () print (len (msghex),msghex) sh.recvuntil(b':> ' ) sh.sendline(msghex.encode()) recvline=sh.recvline(keepends=False ) recvline=recvline.strip() print (recvline) assert (len (recvline)==64 ) iv,ciph=recvline[:32 ],recvline[32 :] iv,ciph=int (iv,16 ),int (ciph,16 ) plain=iv^ciph state4=[] iv00=iv for i in range (4 ): state4.append(iv&0xffffffff ) iv>>=32 return plain,ciph,state4,iv00from tqdm import *for o in tqdm(range (50 )): V,M,C,D=[],[],[],[] for i in range (156 ): recvline=sendMyMsg(bytes ([0 ]*15 )) M.append(recvline[0 ]) C.append(recvline[1 ]) D=D+recvline[2 ] V.append(recvline[3 ]) F=MT19937() F.setstate(D) F.invtwist() F.invtwist() Ha=[F.getstate() for _ in range (624 )] Ha=Ha[-6 :] H=sum ([Ha[i]<<(32 *i) for i in range (6 )]) H=(hex (H)[2 :].zfill(48 )) print (H) H1,H2,H3=[bytes_to_long(H[16 *i:16 *i+16 ].encode()) for i in range (3 )] F.setstate(D) Fv=[sum ([F.getstate()<<(32 *_) for _ in range (4 )]) for __ in range (44 )] V=V+Fv assert len (V)==200 C0,C1,C2,C3,C4=0 ,0 ,0 ,0 ,0 possC2=[] possC1ind=[] T=156 while (T<200 ): print (possC2) ivT=V[T] foundC3=0 for i in range (len (possC2)): X=ivT^possC2[i]^H3 if (X%256 ==1 ): msgX=(hex (X)[2 :]).zfill(32 ) msgX=(hex (X)[2 :]).zfill(32 ) msgX=bytes .fromhex(msgX) print (msgX) print ('C3' ) recvline=sendMyMsg(msgX[:-1 ]) T+=1 C3=recvline[1 ] C2=possC2[i] C1=C[possC1ind[i]] C0=V[possC1ind[i]]^H1^1 foundC3=1 break if (foundC3): break foundC2=0 for i in range (len (C)): X=ivT^C[i]^H2 if (X%256 ==1 ): msgX=(hex (X)[2 :]).zfill(32 ) msgX=bytes .fromhex(msgX) print (msgX) recvline=sendMyMsg(msgX[:-1 ]) T+=1 possC2.append(recvline[1 ]) C.append(recvline[1 ]) possC1ind.append(i) foundC2=1 break if (foundC2): continue else : recvline=sendMyMsg(bytes ([0 ]*15 )) T+=1 M.append(recvline[0 ]) C.append(recvline[1 ]) if (C0*C1*C2*C3): while (T<200 ): X=V[T]^C3^0 if (X%16 ==1 ): X^=(X&0xf0 ) msgX='{:032X}' .format (X) msgX=bytes .fromhex(msgX) assert len (msgX)==16 recvline=sendMyMsg(msgX[:-1 ]) C4=recvline[1 ] T+=1 break else : T+=1 recvline=sendMyMsg(msgX[:-1 ]) M.append(recvline[0 ]) C.append(recvline[1 ]) while (T<200 ): T+=1 recvline=sendMyMsg(msgX[:-1 ]) M.append(recvline[0 ]) C.append(recvline[1 ]) s='' for i in [C0,C1,C2,C3,C4]: s+='{:032X}' .format (i) print (s) sh.sendline(s.encode()) else : while (T<200 ): T+=1 recvline=sendMyMsg(msgX[:-1 ]) M.append(recvline[0 ]) C.append(recvline[1 ]) sh.sendline(bytes ([48 ]*96 )) sh.interactive()
运行结果,最后运行时间为 $41$ 秒,符合题目两分钟的限时。
9.总结 没有总结,感觉自己水平还是不太够。。并且,12月期末周了,要做的东西好多,看看自己能不能平衡好吧。。。