from hashlib import *
from itertools import *
from binascii import hexlify , unhexlify
from flag import flag ,seed
assert len(flag) == 26
assert flag[:7] == 'npuctf{'
assert flag[-1] == '}'
XOR = lambda s1 ,s2 : bytes([x1 ^ x2 for x1 ,x2 in zip(s1 , s2)])
class mt73991:
def __init__(self , seed):
self.state = [seed] + [0] * 232
self.flag = 0
self.srand()
self.generate()
def srand(self):
for i in range(232):
self.state[i+1] = 1812433253 * (self.state[i] ^ (self.state[i] >> 27)) - i
self.state[i+1] &= 0xffffffff
def generate(self):
for i in range(233):
y = (self.state[i] & 0x80000000) | (self.state[(i+1)%233] & 0x7fffffff)
temp = y >> 1
temp ^= self.state[(i + 130) % 233]
if y & 1:
temp ^= 0x9908f23f
self.state[i] = temp
def getramdanbits(self):
if self.flag == 233:
self.generate()
self.flag = 0
bits = self.Next(self.state[self.flag]).to_bytes(4 , 'big')
self.flag += 1
return bits
def Next(self , tmp):
tmp ^= (tmp >> 11)
tmp ^= (tmp << 7) & 0x9ddf4680
tmp ^= (tmp << 15) & 0xefc65400
tmp ^= (tmp >> 18) & 0x34adf670
return tmp
def encrypt(key , plain):
tmp = md5(plain).digest()
return hexlify(XOR(tmp , key))
if __name__ == "__main__":
flag = flag.encode()
random = mt73991(seed)
f = open('./cipher.txt' , 'wb')
for i in flag:
key = b''.join([random.getramdanbits() for _ in range(4)])
cipher = encrypt(key , chr(i).encode())
f.write(cipher)
容易知道要求出seed才能解密。
通过分析可知,加密过程中一个字符对应 4 个随机数,根据题目给出的已知flag格式,可以求得前28个随机数和最后4个随机数(也就是第101~104)。通过这些随机数,写出Next()的逆过程,可以求出相应的generate之后的state。
Next逆过程如下:
浅析MT19937伪随机数生成算法 - 安全客,安全资讯平台 (anquanke.com)
# right shift inverse
def inverse_right(res, shift, bits=32):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp >> shift
return tmp
# right shift with mask inverse
def inverse_right_mask(res, shift, mask, bits=32):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp >> shift & mask
return tmp
# left shift inverse
def inverse_left(res, shift, bits=32):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp << shift
return tmp
# left shift with mask inverse
def inverse_left_mask(res, shift, mask, bits=32):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp << shift & mask
return tmp
def recover_state(tmp):
tmp = inverse_right_mask(tmp, 18,0x34adf670)
tmp = inverse_left_mask(tmp, 15, 0xefc65400)
tmp = inverse_left_mask(tmp, 7, 0x9ddf4680)
tmp = inverse_right(tmp, 11)
return tmp
仔细观察generate()
def generate(self):
for i in range(233):
y = (self.state[i] & 0x80000000) | (self.state[(i+1)%233] & 0x7fffffff)
temp = y >> 1
temp ^= self.state[(i + 130) % 233]
if y & 1:
temp ^= 0x9908f23f
self.state[i] = temp
轮新的状态值【i】由这一轮的【i】(提供最高位)和【i+1】(提供除最高bit位)拼接,右移1位后异或【(i + 130) % 233】而来。随后根据y & 1选择要不要异或0x9908f23f。
而此时我们正好已知state[0]和state[103],于是我们可以猜测state[104]。
至此我们得到了srand()之后的state[104],那怎么求出seed呢?
仔细观察srand()
def srand(self):
for i in range(232):
self.state[i + 1] = 1812433253 * (self.state[i] ^ (self.state[i] >> 27)) - i
self.state[i + 1] &= 0xffffffff
我们可以从这里逆出seed。& 0xffffffff是不是就是mod (0xffffffff+1),于是/1812433253就相当于乘它的逆元。而self.state[i] ^ (self.state[i] >> 27)这步操作,只需要再执行一遍就可以求出原来的state[i]。
相应代码如下
def recover_seed(seed):
mod = 0xffffffff + 1
inv = int(invert(1812433253,mod))
init = [0]*104 + [seed]
for i in range(103, -1, -1):
init[i] = ((init[i+1] + i)*inv) % mod
init[i] = init[i] ^ (init[i] >> 27)
return init[0]
求出了seed之后还有进行判断是否是对的,只需要再执行一遍srand()和generate(),得到的结果跟已知的generate()之后的state[]做对比就行了。
求出了seed之后,后面的解密就很简单了。
总体代码如下:
import hashlib
from binascii import hexlify , unhexlify
from gmpy2 import invert
XOR = lambda s1, s2 : bytes([x1 ^ x2 for x1, x2 in zip(s1, s2)])
# right shift inverse
def inverse_right(res, shift, bits=32):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp >> shift
return tmp
# right shift with mask inverse
def inverse_right_mask(res, shift, mask, bits=32):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp >> shift & mask
return tmp
# left shift inverse
def inverse_left(res, shift, bits=32):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp << shift
return tmp
# left shift with mask inverse
def inverse_left_mask(res, shift, mask, bits=32):
tmp = res
for i in range(bits // shift):
tmp = res ^ tmp << shift & mask
return tmp
def recover_state(tmp):
tmp = inverse_right_mask(tmp, 18,0x34adf670)
tmp = inverse_left_mask(tmp, 15, 0xefc65400)
tmp = inverse_left_mask(tmp, 7, 0x9ddf4680)
tmp = inverse_right(tmp, 11)
return tmp
with open(r'cipher.txt', 'rb') as f:
cipher = unhexlify(f.readline())
#print(len(hashlib.md5(b'n').digest()))#16bits
key1 = XOR(hashlib.md5(b'n').digest(), cipher[:16])
state0 = recover_state(int.from_bytes(key1[:4], 'big'))
print(state0)
key2 = XOR(hashlib.md5(b'}').digest(), cipher[-16:])
state103 = recover_state(int.from_bytes(key2[-4:], 'big'))
print(state103)
#用state[104]恢复seed
def recover_seed(seed):
mod = 0xffffffff + 1
inv = int(invert(1812433253,mod))
init = [0]*104 + [seed]
for i in range(103, -1, -1):
init[i] = ((init[i+1] + i)*inv) % mod
init[i] = init[i] ^ (init[i] >> 27)
return init[0]
#生成初始state
def srand(seed):
state = [seed] + [0]*232
for i in range(232):
state[i+1] = 1812433253 * (state[i] ^ (state[i] >> 27)) - i
state[i+1] &= 0xffffffff
return state
#检验
def check(state):
for i in range(233):
y = (state[i] & 0x80000000) | (state[(i + 1) % 233] & 0x7fffffff)
temp = y >> 1
temp ^= state[(i + 130) % 233]
if y & 1:
temp ^= 0x9908f23f
state[i] = temp
if state[0] == state0 and state[103] == state103:
return True
else:
False
#恢复generate之前的state[104]
def dec_generate(a,b):#a为state0,b为state103
maybe_list = []
#if state[104]最低bit为0
temp = b
temp ^= a
y = (temp << 1) + 0
#猜 state[104]最高bit为0,1
may1 = 0x80000000 + (y & 0x7fffffff)
may2 = y & 0x7fffffff
# if state[104]最低bit为1
temp ^= 0x9908f23f
temp ^= a
y = (temp << 1) + 1
# 猜 state[104]最高bit为0,1
may3 = 0x80000000 + (y & 0x7fffffff)
may4 = y & 0x7fffffff
maybe_list = [may1, may2, may3, may4]
print(f'maybe_list = {maybe_list}')
for each in maybe_list:
seed = recover_seed(each)
state = srand(seed)
if check(state.copy()) == True:
return seed
return -1
seed = dec_generate(state0, state103)
print(f'seed = {seed}')
class mt73991:
def __init__(self, seed):
self.state = [seed] + [0] * 232
self.flag = 0
self.srand()
self.generate()
def srand(self):
for i in range(232):
self.state[i + 1] = 1812433253 * (self.state[i] ^ (self.state[i] >> 27)) - i
self.state[i + 1] &= 0xffffffff
def generate(self):
for i in range(233):
y = (self.state[i] & 0x80000000) | (self.state[(i + 1) % 233] & 0x7fffffff)
temp = y >> 1
temp ^= self.state[(i + 130) % 233]
if y & 1:
temp ^= 0x9908f23f
self.state[i] = temp
def getramdanbits(self):
if self.flag == 233:
self.generate()
self.flag = 0
bits = self.Next(self.state[self.flag]).to_bytes(4, 'big')
self.flag += 1
return bits
def Next(self, tmp):
tmp ^= (tmp >> 11)
tmp ^= (tmp << 7) & 0x9ddf4680
tmp ^= (tmp << 15) & 0xefc65400
tmp ^= (tmp >> 18) & 0x34adf670
return tmp
def decrypt(key, cipher):
tmp = XOR(key, cipher)
for i in range(20, 127):
if hashlib.md5(chr(i).encode()).digest() == tmp:
return chr(i)
random = mt73991(seed)
flag = ''
for i in range(0, len(cipher), 16):
key = b''.join([random.getramdanbits() for _ in range(4)])
ch = decrypt(key, cipher[i:i+16])
flag += ch
print(flag)
参考:
2022DASCTF MAY 出题人挑战赛 - gla2xy’s blog (gal2xy.github.io)
浅析MT19937伪随机数生成算法 - 安全客,安全资讯平台 (anquanke.com)