from hashlib import *
from itertools import *
from binascii import hexlify , unhexlify
flag="npuctf{123456789012345678}"
seed=73991
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('./ciphertext.txt' , 'wb')
for i in flag:
key = b''.join([random.getramdanbits() for _ in range(4)])
cipher = encrypt(key , chr(i).encode())
f.write(cipher)
从main函数中的内容可知,只要我们知道了每次加密用到的key,就能逆encrypt函数恢复出明文
在每次加密中,key都是随机生成的新的四个随机字符相加,生成方式与seed有关,所以主要是来寻找seed,找到seed就可知所有key
self.state = [seed] + [0] * 232
self.srand()
self.generate()bits = self.Next(self.state[self.flag]).to_bytes(4 , 'big')
因为self.flag首先是0
从这里可以看出state的第一位首先是seed,然后由seed经过srand和generate函数再生成新的随机数,再依靠这个随机数生成key,那么我们首先要找到生成key的随机数,再根据这个随机数来爆破出seed
to_bytes(4 , 'big')随机生成四位随机字符
利用已知明文,也就是前缀npuctf,我们提取出来这个‘n',以及这个n生成的前32位密文,来恢复出第一次生成的十六个字符的key
找到第一次生成的key,取前四位:
from hashlib import *
from itertools import *
from binascii import hexlify , unhexlify
from Crypto.Util.number import *
import string
XOR = lambda s1 ,s2 : bytes([x1 ^ x2 for x1 ,x2 in zip(s1 , s2)])
cipher='cef4876036ee8b55aa59bca043725bf350a5e491debdef7ef7d63e9609a288ca1e2'
m=md5(b'n').digest()
c=unhexlify(cipher[:32])
key=XOR(c,m)
print(key)
key0=key[:4]
key0=bin(bytes_to_long(key0))[2:].zfill(32)
找到key的前四位后,我们逆Next函数来找到生成key前四位的随机数
def Next(self , tmp):
tmp ^= (tmp >> 11)
tmp ^= (tmp << 7) & 0x9ddf4680
tmp ^= (tmp << 15) & 0xefc65400
tmp ^= (tmp >> 18) & 0x34adf670
return tmp
给出逆Next代码:
def reverse1(x):
x1=x[:18]
tmp=eval('0b'+x[:14])&(eval('0b'+bin(0x34adf670)[2:][-14:]))
x2=eval('0b'+x[-14:])^tmp
x2=bin(x2)[2:].zfill(14)
return x1+x2
def reverse2(x):
x2=x[-15:]
x1=(eval('0b'+x2)&eval('0b'+bin(0xefc65400)[2:][2:17]))^eval('0b'+x[2:17])
tmp=eval('0b'+bin(x1)[-2:])&eval('0b'+bin(0xefc65400)[2:][:2])^eval('0b'+x[:2])
return bin(tmp)[2:].zfill(2)+bin(x1)[2:].zfill(15)+x2
def reverse3(x):
x3=x[-7:]
x2=(eval('0b'+x3)&eval('0b'+bin(0x9ddf4680)[2:][-14:-7]))^eval('0b'+x[-14:-7])
x1=(x2&eval('0b'+bin(0x9ddf4680)[2:][-21:-14]))^eval('0b'+x[-21:-14])
x0=(x1&eval('0b'+bin(0x9ddf4680)[2:][-28:-21]))^eval('0b'+x[-28:-21])
tmp=bin(x0)[2:].zfill(7)+bin(x1)[2:].zfill(7)+bin(x2)[2:].zfill(7)+x3
ans=(eval('0b'+tmp[3:28]+'0000000')&0x9ddf4680)^eval('0b'+x)
return bin(ans)[2:].zfill(32)
def reverse4(x):
x1=x[:11]
x2=eval('0b'+x1)^eval('0b'+x[11:22])
tmp='00000000000'+(x1+bin(x2)[2:].zfill(11))[:21]
ans=eval('0b'+tmp)^eval('0b'+x)
return bin(ans)[2:].zfill(32)
这里四次逆Next的原理差不多,讲解一下最复杂的一部分:
reverse3:
tmp ^= (tmp << 7) & 0x9ddf4680
我们设这一行中传入的tmp经过移位、按位与、异或后得到新的tmp的称作密文,以此便于区分
我们要逆这一行代码,首先发现是tmp左移7位,经过按位与运算后与原tmp异或,由按位与和异或的性质可以得到tmp的后7位不变,这七位左移7位成为了[-14:-7]位,这些位数再与0x9ddf4680的[-14:-7]位按位与,再与密文的[-14:-7]位异或,就得到了原来的tmp的[-14:-7]位。、
这下我们恢复了tmp的[-14:-7]位,加上原来已知的后七位,后十四位都是已知的了,利用上面的方法,依次恢复出全部的32位,其他reverse原理相同
恢复seed生成的随机数后,开始爆破seed
def srand():
for i in range(232):
state[i+1] = 1812433253 * (state[i] ^ (state[i] >> 27)) - i
state[i+1] &= 0xffffffff
def generate():
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
seed=0
while True:
if seed%10000==0:
print(seed)
state = [seed] + [0] * 232
srand()
generate()
if state[0]!=res:
seed+=1
if state[0]==res:
break
print(seed)
#1668245885
需要爆很长时间
得到seed后就简单多了
利用seed传入随机数生成器,得到每次的key,
def encrypt(key , plain):
tmp = md5(plain).digest()
return hexlify(XOR(tmp , key))
在encrypt函数中,将明文md5然后与key异或,由于key每次都不一样,我们先把明文md5的值打表以便后续使用
printable=string.printable
dict={}
for i in printable:
tmp = md5(i.encode()).digest()
dict[tmp]=i
既然知道了key,将密文按顺序与key异或,得到tmp,再将tmp传入dict查表,得到明文
flag=''
seed=1668245885
random=mt73991(seed)
c=[]
f=open('cipher.txt').read()
for i in range(0,len(f),32):
c.append(f[i:i+32])
c=c[:-1]
for i in c:
key = b''.join([random.getramdanbits() for _ in range(4)])
xor=unhexlify(i)
tmp=XOR(xor,key)
flag+=dict[tmp]
print(flag)
完整题目代码:
from hashlib import *
from itertools import *
from binascii import hexlify , unhexlify
from Crypto.Util.number import *
import string
XOR = lambda s1 ,s2 : bytes([x1 ^ x2 for x1 ,x2 in zip(s1 , s2)])
cipher='cef4876036ee8b55aa59bca043725bf350a5e491debdef7ef7d63e9609a288ca1e2'
m=md5(b'n').digest()
c=unhexlify(cipher[:32])
key=XOR(c,m)
print(key)
key0=key[:4]
key0=bin(bytes_to_long(key0))[2:].zfill(32)
#print(key0)
def reverse1(x):
x1=x[:18]
tmp=eval('0b'+x[:14])&(eval('0b'+bin(0x34adf670)[2:][-14:]))
x2=eval('0b'+x[-14:])^tmp
x2=bin(x2)[2:].zfill(14)
return x1+x2
def reverse2(x):
x2=x[-15:]
x1=(eval('0b'+x2)&eval('0b'+bin(0xefc65400)[2:][2:17]))^eval('0b'+x[2:17])
tmp=eval('0b'+bin(x1)[-2:])&eval('0b'+bin(0xefc65400)[2:][:2])^eval('0b'+x[:2])
return bin(tmp)[2:].zfill(2)+bin(x1)[2:].zfill(15)+x2
def reverse3(x):
x3=x[-7:]
x2=(eval('0b'+x3)&eval('0b'+bin(0x9ddf4680)[2:][-14:-7]))^eval('0b'+x[-14:-7])
x1=(x2&eval('0b'+bin(0x9ddf4680)[2:][-21:-14]))^eval('0b'+x[-21:-14])
x0=(x1&eval('0b'+bin(0x9ddf4680)[2:][-28:-21]))^eval('0b'+x[-28:-21])
tmp=bin(x0)[2:].zfill(7)+bin(x1)[2:].zfill(7)+bin(x2)[2:].zfill(7)+x3
ans=(eval('0b'+tmp[3:28]+'0000000')&0x9ddf4680)^eval('0b'+x)
return bin(ans)[2:].zfill(32)
def reverse4(x):
x1=x[:11]
x2=eval('0b'+x1)^eval('0b'+x[11:22])
tmp='00000000000'+(x1+bin(x2)[2:].zfill(11))[:21]
ans=eval('0b'+tmp)^eval('0b'+x)
return bin(ans)[2:].zfill(32)
res=key0
for i in range(1,5):
res=eval('reverse'+str(i)+'(res)')
res=int(res,2)
#爆破seed:
'''def srand():
for i in range(232):
state[i+1] = 1812433253 * (state[i] ^ (state[i] >> 27)) - i
state[i+1] &= 0xffffffff
def generate():
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
seed=0
while True:
if seed%10000==0:
print(seed)
state = [seed] + [0] * 232
srand()
generate()
if state[0]!=res:
seed+=1
if state[0]==res:
break'''
#print(seed)
#1668245885
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
printable=string.printable
dict={}
for i in printable:
tmp = md5(i.encode()).digest()
dict[tmp]=i
flag=''
seed=1668245885
random=mt73991(seed)
c=[]
f=open('cipher.txt').read()
for i in range(0,len(f),32):
c.append(f[i:i+32])
c=c[:-1]
for i in c:
key = b''.join([random.getramdanbits() for _ in range(4)])
xor=unhexlify(i)
tmp=XOR(xor,key)
flag+=dict[tmp]
print(flag)
参考:[NPUCTF2020]Mersenne twister(mt73991伪随机)_前方是否可导?的博客-CSDN博客