MT19937
文章目录
- MT19937
- 题型1 逆向extract_number
- [SUCTF2019]MT
- 题型2 预测随机数
- [GKCTF 2021]Random
- 题型3逆向twist
- [V&N2020 公开赛]Backtrace
- 题型4 逆向init
- 扩展题型
- WKCTF easy_random
- 现成模块
- randcrack库
- Extend MT19937 Predictor库
MT19937是一种周期很长的伪随机数生成算法,可以快速生成高质量的伪随机数,主要由三部分组成:
1.利用seed初始化624的状态
2.对状态进行旋转
3.根据状态提取伪随机数
梅森旋转算法可以产生高质量的伪随机数,并且效率高效,弥补了传统伪随机数生成器的不足。梅森旋转算法的最长周期取自一个梅森素数:2^19937 - 1由此命名为梅森旋转算法。常见的两种为基于32位的MT19937-32和基于64位的MT19937-64
32位的MT19937的python代码如下:
def _int32(x):
#保证32位
return int(0xFFFFFFFF & x)
class MT19937:
# 根据seed初始化624位的state
def __init__(self, seed):
self.mt = [0] * 624
self.mt[0] = seed#这里的mt[]数组就是我们的状态state
self.mti = 0#类似于一个计数器
for i in range(1, 624):
self.mt[i] = _int32(1812433253 * (self.mt[i - 1] ^ self.mt[i - 1] >> 30) + i)
# 提取伪随机数
def extract_number(self):
if self.mti == 0:
self.twist()#624个数据取完之后进行旋转,即把数据更新
y = self.mt[self.mti]
y = y ^ y >> 11
y = y ^ y << 7 & 2636928640
y = y ^ y << 15 & 4022730752
y = y ^ y >> 18#数据还要进行一系列的位运算和与运算
self.mti = (self.mti + 1) % 624#以624为一个周期
return _int32(y)
# 对状态进行旋转
def twist(self):
for i in range(0, 624):
y = _int32((self.mt[i] & 0x80000000) + (self.mt[(i + 1) % 624] & 0x7fffffff))
self.mt[i] = (y >> 1) ^ self.mt[(i + 397) % 624]
if y % 2 != 0:
self.mt[i] = self.mt[i] ^ 0x9908b0df
需要注意的是:python中内置的Random类就是采用了MT19937算法
还有一个点:MT19937每次生成的随机数都是32位的
getrandbits(32)方法可以获得一个32位随机数
getrandbits(8)获得的是32位随机数的前8位,而在这之后再次使用getrandbits(8)获取的是下一个32位随机数的前8位
getrandbits(64)则是两个32位随机数的拼接,倒序拼接
import random random.seed(1) print(hex(random.getrandbits(32))[2:])#getrandbits(a)是生成a位二进制的随机数 print(hex(random.getrandbits(32))[2:]) #2265b1f5 #91b7584a random.seed(1) print(hex(random.getrandbits(8))[2:]) print(hex(random.getrandbits(8))[2:]) #22 #91 random.seed(1) print(hex(random.getrandbits(64))[2:])#64位的是两个32位的拼接,但是是倒序拼接 #91b7584a2265b1f5 random.seed(1) # print(random.randbytes(8))#ranbytes(a)是生成a个字节的数据 print(hex(bytes_to_long(random.randbytes(8)))[2:])#正好是getrandbits生成的数据的倒序 #f5b165224a58b791
总结来说:
getrandbits( a )生成指定a位数的数据
以32位为分界线
小于32位的取前a位,接下来再次生成的随机数又是从下一个32位数据中取
大于32位的就取多个32位数据进行拼接,倒序拼接
randbytes()生成指定字节数的数据
同getrandbits类似的,但是这个拼接是顺序拼接的,其实就是一个大端一个小端
state
是伪随机数发生器的重要变量,它决定了输出怎样的随机数,也就是说知道了state
你可以预测出它将要输出的数
import random
ori_state = random.getstate() # 得到该伪随机数发生器的state,也就是状态量
# 输出随机数
print(random.getrandbits(32)) # 3410545957
print(random.getrandbits(32)) # 1647702295
random.setstate(ori_state)
# 验证随机数是否相等
print(random.getrandbits(32)) # 3410545957
print(random.getrandbits(32)) # 1647702295
#也就是说相同的state可以得到相同的随机数
接下来看看state的组成:
import random
random.seed(1)
print(random.getstate())
#(3, (2147483648, 163610392, ... , 1781494222, 624), None)
中间的元组类型有625项,前624项即是初始化时得到的整数,而第625项(此时等于624)是该state此时对应的第几个整数,也就是说,在下一次执行extract_number()函数时的state里面应该调用第几个整数,换句话说,就是extract_number()函数中的mti,此时mti = 624,作用类似于计数器,因为每624为一个周期
其他参数似乎是默认的,在之后我们求出624 个整数之后,需要按这个格式进行构造真正的state;为了方便叙述,之后的state都暂时指代这624个整数
题型1 逆向extract_number
看到extract_number()函数
def extract_number(self):
if self.mti == 0:
self.twist()#624个数据取完之后进行旋转,即把数据更新
y = self.mt[self.mti]
y = y ^ y >> 11
y = y ^ y << 7 & 2636928640
y = y ^ y << 15 & 4022730752
y = y ^ y >> 18#数据还要进行一系列的位运算和与运算
self.mti = (self.mti + 1) % 624#以624为一个周期
return _int32(y)
可以发现该函数对于state[i]进行了一系列的异或和位运算,最后输出随机数
那么我们一步步来推导一下:
y = y ^ ( y >> 18 )
可以发现这一步对于y的高18位并没有影响,即运算结束后得到的 y’= y ^ ( y >> 18 ) , y’ 的高18位就是 y 的高18位,这样就可以得到y的高18位,那么再异或回去,我们就可以得到y的高36位了,依次类推下去,循环的异或我们就能够得到y的所有位了
也就是说我们可以在有限步内,获得y的所有信息,即我们可以根据 y’ 逆向出 y
代码:
o = 2080737669
print(o.bit_length())#31
y = o^o>>18
# 控制位移的次数
for i in range(31//18):
y = y^(y>>18)
print(y==o)
#如果o的位数大于36那么代码还需要做修改
#但是实际上y的大小应该是固定的,32位数,所以这段代码可以固定使用
继续分析:
y = y ^ y << 15 & 4022730752
根据运算符的优先级有:
y = y ^ ((y << 15) & 4022730752)
所以 y’ 的低15位是y的低15位异或4022730752的低15位的结果
那么通过 y’ 的低15位和4022730752的低15位异或就可以求到y的低15位,与上面同样的,我们根据y的低15位与4022730752与一下,再和 y’ 的15-30位进行异或就可以得到y的15-30位了,这样就求到了y的30位,以此类推,经过有限步,我们可以求到y的所有位
代码:
o = 2080737669
y = o ^ o << 15 & 4022730752
tmp = y
for i in range(32 // 15):
# (y<<15)&40022730752 每次可以恢复y的15位
y = tmp ^ y << 15 & 4022730752
print(y==o)
剩下的两步和上面的类似,最终的代码如下:
o = 2080737669
# 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 extract_number(y):
y = y ^ y >> 11
y = y ^ y << 7 & 2636928640
y = y ^ y << 15 & 4022730752
y = y ^ y >> 18
return y&0xffffffff
def recover(y):
y = inverse_right(y,18)
y = inverse_left_mask(y,15,4022730752)
y = inverse_left_mask(y,7,2636928640)
y = inverse_right(y,11)
return y&0xffffffff#保证是32位
y = extract_number(o)
print(recover(y) == o)
来道题练练吧:
[SUCTF2019]MT
from Crypto.Random import random
from Crypto.Util import number
from flag import flag
def convert(m):
m = m ^ m >> 13
m = m ^ m << 9 & 2029229568
m = m ^ m << 17 & 2245263360
m = m ^ m >> 19
return m
def transform(message):
assert len(message) % 4 == 0
new_message = ''
for i in range(len(message) / 4):
block = message[i * 4 : i * 4 +4]
block = number.bytes_to_long(block)
block = convert(block)
block = number.long_to_bytes(block, 4)
new_message += block
return new_message
transformed_flag = transform(flag[5:-1].decode('hex')).encode('hex')
print 'transformed_flag:', transformed_flag
# transformed_flag: 641460a9e3953b1aaa21f3a2
很明显和extract_number()函数很类似,直接套上面的脚本即可
from Crypto.Util.number import *
# 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(y):
y = inverse_right(y,19)
y = inverse_left_mask(y,17,2245263360)
y = inverse_left_mask(y,9,2029229568)
y = inverse_right(y,13)
return y&0xffffffff
enc='641460a9e3953b1aaa21f3a2'
enc=long_to_bytes(int(enc,16))
#b'd\x14`\xa9\xe3\x95;\x1a\xaa!\xf3\xa2'
m=b''
# print(len(enc))12
for i in range(3):
block = enc[i * 4: i * 4 + 4]
block = bytes_to_long(block)
block = recover(block)
m += long_to_bytes(block)
print(m)
print(hex(bytes_to_long(m))[2:])
#84b45f89af22ce7e67275bdc
这里还有另外一种办法,就是通过不断加密最终又可以回到明文
因为本题使用的加密算法是梅森旋转算法,是一种伪随机数生成算法
该算法的一个更新的和更常用的是MT19937, 32位字长,对应了题目
并且该算法生成的随机数具有周期性,这也就不难理解为什么一直加密密文就能得到明文了,因为经过一个周期后得到的还是密文,那么上一个就是明文了
from Crypto.Random import random
from Crypto.Util import number
def convert(m):
m = m ^ m >> 13
m = m ^ m << 9 & 2029229568
m = m ^ m << 17 & 2245263360
m = m ^ m >> 19
return m
def transform(message):
assert len(message) % 4 == 0
new_message = b''
for i in range(len(message) // 4):
block = message[i * 4 : i * 4 +4]
block = number.bytes_to_long(block)
block = convert(block)
block = number.long_to_bytes(block, 4)
new_message += block
return new_message
def circle(m):
t=m
while True:
x=t
t=transform(t)
if t==m:
return x
transformed_flag='641460a9e3953b1aaa21f3a2'
print(bytes.fromhex(transformed_flag))
flag = hex(bytes_to_long(circle(bytes.fromhex(transformed_flag))))[2:]
print('transformed_flag:', flag)
咱们上面的是基于对extract_number函数的观察所得到的逆向方法,实际上我们还可以从extract_number的运算本质上进行逆向。(接下来的这种方法也是比较重要的,即通过构建矩阵进行求解)
假设
s
t
a
t
e
[
i
]
的二进制表示形式为:
x
0
x
1
x
2
.
.
.
x
31
输出的随机数形式为:
z
0
z
1
z
2
.
.
.
z
31
根据
e
x
t
r
a
c
t
_
n
u
m
b
e
r
函数可以知道
z
和
x
是存在线性关系的(可以推出来的)
即
X
∗
T
=
Z
X
=
Z
∗
T
−
1
这就把问题转换成了矩阵的问题了
其中
X
,
Z
是
G
F
(
2
)
上的
1
∗
32
的向量,
T
是
G
F
(
2
)
上的
32
∗
32
的矩阵
我们要在
G
F
(
2
)
上求解
X
,
已知
Z
,
如何求
T
呢?
可以令
X
=
(
1
,
0
,
0
,
.
.
.
.
,
0
)
,
这样
X
∗
T
得到的
Z
就是
T
的第一行了
实际计算中,采用黑盒测试的方法进行猜解
T
就是设置特定的状态,然后向后预测得到
Z
,
这个
Z
就是我们
T
的某一行
(
因为
X
T
=
Z
实际体现的是随机数预测的线性关系)
假设state[i]的二进制表示形式为:\\x_0x_1x_2...x_{31}\\输出的随机数形式为:\\z_0z_1z_2...z_{31}\\根据extract\_number函数可以知道z和x是存在线性关系的(可以推出来的)\\即X*T=Z\\X=Z*T^{-1}\\这就把问题转换成了矩阵的问题了\\其中X,Z是GF(2)上的1*32的向量,T是GF(2)上的 32*32的矩阵\\我们要在GF(2)上求解X,已知Z,如何求T呢?\\可以令X=(1,0,0,....,0),这样X*T得到的Z就是T的第一行了\\实际计算中,采用黑盒测试的方法进行猜解T\\就是设置特定的状态,然后向后预测得到Z,这个Z就是我们T的某一行\\(因为XT=Z实际体现的是随机数预测的线性关系)
假设state[i]的二进制表示形式为:x0x1x2...x31输出的随机数形式为:z0z1z2...z31根据extract_number函数可以知道z和x是存在线性关系的(可以推出来的)即X∗T=ZX=Z∗T−1这就把问题转换成了矩阵的问题了其中X,Z是GF(2)上的1∗32的向量,T是GF(2)上的32∗32的矩阵我们要在GF(2)上求解X,已知Z,如何求T呢?可以令X=(1,0,0,....,0),这样X∗T得到的Z就是T的第一行了实际计算中,采用黑盒测试的方法进行猜解T就是设置特定的状态,然后向后预测得到Z,这个Z就是我们T的某一行(因为XT=Z实际体现的是随机数预测的线性关系)
线性关系:(根据函数一位一位的看,这里的异或其实和+类似)
代码:
# sagemath 9.0
from sage.all import *
from random import Random
def buildT():
rng = Random()
T = matrix(GF(2),32,32)
for i in range(32):
s = [0]*624
# 构造特殊的state
s[0] = 1<<(31-i)
rng.setstate((3,tuple(s+[0]),None))
tmp = rng.getrandbits(32)#往后预测数据,即得到Z
# 获取T矩阵的每一行
row = vector(GF(2),[int(x) for x in bin(tmp)[2:].zfill(32)])
T[i] = row#获取T的第i行
return T
def reverse(T,leak):
Z = vector(GF(2),[int(x) for x in bin(leak)[2:].zfill(32)])
X = T.solve_left(Z)#X=Z*T^(-1)
state = int(''.join([str(i) for i in X]),2)
return state
def test():
rng = Random()
# 泄露信息
leak = [rng.getrandbits(32) for i in range(32)]
originState = [i for i in rng.getstate()[1][:32]]
# 构造矩阵T
T = buildT()
recoverState = [reverse(T,i) for i in leak]
print(recoverState==originState)
test()
题型2 预测随机数
就是题目给出一系列的随机数,然后根据这些随机数预测后面的随机数
这一类题型本质上就是题型一,因为也是基于对extract_number 函数的逆向而发展来的
直接看题吧
[GKCTF 2021]Random
import random
from hashlib import md5
def get_mask():
file = open("random.txt","w")
for i in range(104):
file.write(str(random.getrandbits(32))+"\n")
file.write(str(random.getrandbits(64))+"\n")
file.write(str(random.getrandbits(96))+"\n")
file.close()
get_mask()
flag = md5(str(random.getrandbits(32)).encode()).hexdigest()
print(flag)
分析:
我们可以看出前面先生成了许多的随机数,然后flag是之后生成的随机数的md5加密值
那么我们就需要根据给的数据往后预测即可
那么就需要根据给的数据恢复出624个状态才行
插入一个小知识点:
import random random.seed(1) print(hex(random.getrandbits(32))[2:])#getrandbits(a)是生成a位二进制的随机数 print(hex(random.getrandbits(32))[2:]) #2265b1f5 #91b7584a random.seed(1) print(hex(random.getrandbits(64))[2:])#64位的是两个32位的拼接,但是是倒序拼接 #91b7584a2265b1f5
即getrandbits()里面的位数如果大于32则是由多个随机数拼接而成的,且是倒序的
当然小于32则是取前几位,但下一个随机数是从新的32位随机数上取
那么题目中getrandbits(64)就是由2个32位的随机数拼接而成,getrandbits(96)就是由3个32位的随机数拼接而成
总共有104*(1+2+3)=624个随机数,恢复一下状态state,即可往后预测
exp:
from hashlib import md5
from Crypto.Util.number import *
from gmpy2 import *
# -*- coding: utf-8 -*-
from random import Random
def invert_right(m,l,val=''):
length = 32
mx = 0xffffffff
if val == '':
val = mx
i,res = 0,0
while i*l<length:
mask = (mx<<(length-l)&mx)>>i*l
tmp = m & mask
m = m^tmp>>l&val
res += tmp
i += 1
return res
def invert_left(m,l,val):
length = 32
mx = 0xffffffff
i,res = 0,0
while i*l < length:
mask = (mx>>(length-l)&mx)<<i*l
tmp = m & mask
m ^= tmp<<l&val
res |= tmp
i += 1
return res
def invert_temper(m):
m = invert_right(m,18)
m = invert_left(m,15,4022730752)
m = invert_left(m,7,2636928640)
m = invert_right(m,11)
return m
def clone_mt(record):
state = [invert_temper(i) for i in record]
gen = Random()
gen.setstate((3,tuple(state+[0]),None))#设置state,方便后面的预测
return gen
f = open("random.txt",'r').readlines()
prng = []
j=0
for i in f:
i = i.strip('\n')
if(j%3==0):#分割数字,我们要按照32的位数来
prng.append(int(i))
elif(j%3==1):#将生成两次随机数的两个随机数分离出来,倒序的
prng.append(int(i)& (2 ** 32 - 1))
prng.append(int(i)>> 32)
else:#将生成三次随机数的三个随机数分离出来
prng.append(int(i)& (2 ** 32 - 1))
prng.append(int(i)& (2 ** 64 - 1) >> 32)
prng.append(int(i)>>64)
j+=1
# print(prng)
# print(len(prng))624
g = clone_mt(prng[:624])#回溯状态,即题型1类似
for i in range(624):
g.getrandbits(32)
flag = md5(str(g.getrandbits(32)).encode()).hexdigest()
print(flag)
还有一种办法,可以直接用现成的randcrack库
from hashlib import md5
from randcrack import RandCrack
f = open("random.txt",'r').readlines()
prng = []
j=0
for i in f:
i = i.strip('\n')
if(j%3==0):#分割数字,我们要按照32的位数来
prng.append(int(i))
elif(j%3==1):#将生成两次随机数的两个随机数分离出来,倒序的
prng.append(int(i)& (2 ** 32 - 1))
prng.append(int(i)>> 32)
else:#将生成三次随机数的三个随机数分离出来
prng.append(int(i)& (2 ** 32 - 1))
prng.append(int(i)& (2 ** 64 - 1) >> 32)
prng.append(int(i)>>64)
j+=1
rc = RandCrack()
for i in prng:
rc.submit(i)#传入624位的数据
flag = rc.predict_getrandbits(32) # 在给出的随机数数量多时,predict_getrandbits()可以预测下一个随机数
print('GKCTF{' + md5(str(flag).encode()).hexdigest() + '}')
题型3逆向twist
前面我们是根据已知连续624个随机数恢复624个状态state,往后预测随机数
那么如何往前预测随机数呢?
因为每过一轮624个数据之后,都会用twist()函数进行数据更新
所以我们可以通过逆向twist()函数得到前一组的状态state,进而得到前一组随机数
那么先看看twist()函数吧:
# 对状态进行旋转
def twist(self):
for i in range(0, 624):
y = _int32((self.mt[i] & 0x80000000) + (self.mt[(i + 1) % 624] & 0x7fffffff))
self.mt[i] = (y >> 1) ^ self.mt[(i + 397) % 624]
if y % 2 != 0:
self.mt[i] = self.mt[i] ^ 0x9908b0df
考虑下面的例子:
1. 11100110110101000100101111000001 // state[i]
2. 10101110111101011001001001011111 // state[i + 1]
3. 11101010010001001010000001001001 // state[i + 397]
// y = state[i] & 0x80000000 | state[i + 1] & 0x7fffffff
// |和+的效果是类似的,因为最后都是32位的数据
4. 10101110111101011001001001011111 // y
5. 01010111011110101100100100101111 // next = y >> 1
6. 11001110011100100111100111110000 // next ^= 0x9908b0df
0x9908b0df => 10011001000010001011000011011111
7. 00100100001101101101100110111001 // next ^= state[i + 397]
因为生成新的state[i]只与原来的state[i],state[i+1],state[i+397]有关。
而第7步是必须进行的一步(注意异或的次序是不影响结果的,所以异或state[i+397]可以看成最后一步)
第6步需要根据第4步结果的奇偶性来确定,即不一定有第6步
这里有一个很巧妙的点:因为把第7步放在最后,所以第7步是由第5步或者第6步的结果和state[i+397异或]得到的,看到第5步,因为y>>1,那么得到的next的高位一定是0,如果进行了第6步,即和0x9908b0df 异或
0x9908b0df => 10011001000010001011000011011111
得到的结果next的最高位通过0^1=1,所以我们可以通过第7步逆向得到的结果的高位是1还是0来区分是否进行第6步,进而可以往前推,因为第5步的next的后31位包含了state[i]的首位和state[i+1]的第2位到第31位,所以根据是否进行第6步可以知道y的最后一位是1还是0,即得到state[i+1]的第32位
综上所述:根据当前的state[i]和之前的state[i+397],我们可以得到原来的state[i]的第1位和state[i+1]的低31位,那么要获得原来state[i]的低31位则需要对state[i-1]进行同样的操作,依次循环下去即可
实现代码:
因为twist()函数生成新的state时是从0-623的,所以到生成state[227]时,最后的异或state[(i+397)%624]=state[0],这个时候异或的state[0]已经是新的state了,即state[227]之后异或的state[i+397]都是新的state了(即下一轮的state)
所以我们可以根据下一轮的state反推前一轮的state的
def backtrace(cur):
high = 0x80000000
low = 0x7fffffff
mask = 0x9908b0df
state = cur
for i in range(623,-1,-1):
tmp = state[i]^state[(i+397)%624]
# recover Y,tmp = Y
if tmp & high == high:
tmp ^= mask
tmp <<= 1
tmp |= 1
else:
tmp <<=1
# recover highest bit
res = tmp&high
# recover other 31 bits,when i =0,it just use the method again it so beautiful!!!!
tmp = state[i-1]^state[(i+396)%624]
# recover Y,tmp = Y
if tmp & high == high:
tmp ^= mask
tmp <<= 1
tmp |= 1
else:
tmp <<=1
res |= (tmp)&low
state[i] = res
return state
那来道题目看看
[V&N2020 公开赛]Backtrace
# !/usr/bin/env/python3
import random
flag = "flag{" + ''.join(str(random.getrandbits(32)) for _ in range(4)) + "}"
with open('output.txt', 'w') as f:
for i in range(1000):
f.write(str(random.getrandbits(32)) + "\n")
print(flag)
分析:
给的数据是在flag的随机数之后的随机数,也就是说我们需要恢复前4个数据,一般来说我们是根据连续的624个随机数才能够得到上一组随机数的状态,虽然题目给了我们1000个随机数,但是前4个丢失
(这里是将1000个数据分为了两部分,一部分带着未知的4个数据-620个,一部分则是下一轮的数据-380个)
但是我们知道前4个状态经过twist()函数扭转后对应的是第625,626,627,628位随机数的状态,根据我们上面的推导是可逆的(直接逆回去即可),所以即便没有连续的624个随机数,也可以求解出完整的state
其实就是根据数据得到状态,再根据上面的推导反推前4个state就行
exp:
# -*- coding: utf-8 -*-
import random
def invert_right(m,l,val=''):
length = 32
mx = 0xffffffff
if val == '':
val = mx
i,res = 0,0
while i*l<length:
mask = (mx<<(length-l)&mx)>>i*l
tmp = m & mask
m = m^tmp>>l&val
res += tmp
i += 1
return res
def invert_left(m,l,val):
length = 32
mx = 0xffffffff
i,res = 0,0
while i*l < length:
mask = (mx>>(length-l)&mx)<<i*l
tmp = m & mask
m ^= tmp<<l&val
res |= tmp
i += 1
return res
def invert_temper(m):
m = invert_right(m,18)
m = invert_left(m,15,4022730752)
m = invert_left(m,7,2636928640)
m = invert_right(m,11)
return m
#往前回溯
def backtrace(cur):
high = 0x80000000
low = 0x7fffffff
mask = 0x9908b0df
state = cur
for i in range(3,-1,-1):
tmp = state[i+624]^state[(i+397)%624]#根据新的结果推前一个state的结果
# recover Y,tmp = Y
if tmp & high == high:
tmp ^= mask
tmp <<= 1
tmp |= 1
else:
tmp <<=1
# recover highest bit
res = tmp&high
# recover other 31 bits,when i =0,it just use the method again it so beautiful!!!!
tmp = state[i-1+624]^state[(i+396)%624]
# recover Y,tmp = Y
if tmp & high == high:
tmp ^= mask
tmp <<= 1
tmp |= 1
else:
tmp <<=1
res |= (tmp)&low
state[i] = res
return state
f = open("output.txt",'r').readlines()
prng = []
for i in f:
i = i.strip('\n')
prng.append(int(i))
state=[]
for i in range(1000):
state.append(invert_temper(prng[i]))
state2=backtrace([0]*4+state)[:624]#[:624]取backtrace返回结果的前624个即可
random.setstate((3,tuple(state2+[0]),None))
flag = "flag{" + ''.join(str(random.getrandbits(32)) for _ in range(4)) + "}"
print(flag)
题型4 逆向init
这种类型的题还没有出现过,但是也可以进行逆向
我们前面有通过输出的数据逆向对于的state,逆向上一组的state
这部分则是根据第一次的state,逆向求出seed
先来看init这个函数吧:
def _int32(x):
#保证32位
return int(0xFFFFFFFF & x)
def __init__(self, seed):
self.mt = [0] * 624
self.mt[0] = seed#这里的mt[]数组就是我们的状态state
self.mti = 0#类似于一个计数器
for i in range(1, 624):
self.mt[i] = _int32(1812433253 * (self.mt[i - 1] ^ self.mt[i - 1] >> 30) + i)
看到关键代码:
self.mt[i] = _int32(1812433253 * (self.mt[i - 1] ^ self.mt[i - 1] >> 30) + i)
可以发现self.mt[i - 1] ^ self.mt[i - 1] >> 30是可逆的,类似于我们题型一,位运算和异或运算
注意我们的_int32()只是为了保证位数,相当于mod 2^32 ,所以我们完全可以通过求逆元得到self.mt[i - 1] ^ self.mt[i - 1] >> 30的值
其中gcd(1812433253,2^32)=1,即1812433253是存在逆元的
代码如下:
from gmpy2 import invert
def _int32(x):
return int(0xFFFFFFFF & x)
def init(seed):
mt = [0] * 624
mt[0] = seed
for i in range(1, 624):
mt[i] = _int32(1812433253 * (mt[i - 1] ^ mt[i - 1] >> 30) + i)
return mt
seed = 2080737669
def invert_right(res,shift):
tmp = res
for i in range(32//shift):
res = tmp^res>>shift
return _int32(res)
def recover(last):
n = 1<<32
inv = invert(1812433253,n)#求逆元
for i in range(623,0,-1):
last = ((last-i)*inv)%n
last = invert_right(last,30)
return last
state = init(seed)
print(recover(state[-1]) == seed)
扩展题型
这个其实就是题型1中后面介绍的黑盒测试方法,即通过构建矩阵得到state,这个方法在一些特殊情况下是需要使用到的
构建矩阵的脚本:
#! /bin/bash/env python3
from sage.all import *
from random import Random
from tqdm import tqdm
prng = Random()
length = 19968
def myState():
state = [0]*624
i = 0
while i<length:
ind = i//32
expont = i%32
state[ind] = 1<<(31-expont)
s = (3,tuple(state+[0]),None)
yield s
state[ind] = 0
i += 1
def getRow():
rng = Random()
gs = myState()
for i in range(length):
s = next(gs)
rng.setstate(s)
# print(s[1][0])
row = vector(GF(2),[rng.getrandbits(1) for j in range(length)])
yield row
def buildBox():
b = matrix(GF(2),length,length)
rg = getRow()
for i in tqdm(range(length)):
b[i] = next(rg)
return b
def test():
prng = Random()
originState = prng.getstate()
# 这里都是用的MSB,如果采用不同的二进制位(如LSB)最后的矩阵T 也会不同
leak = vector(GF(2),[prng.getrandbits(1) for i in range(length)])
b = buildBox()
f = open("Matrix","w")
for i in range(b.nrows()):
for j in range(b.ncols()):
f.write(str(b[i,j])+"n")
f.close()
x = b.solve_left(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)
prng.setstate(originState)
prng.getrandbits(1)
originState = [x for x in prng.getstate()[1][:-1]]
print(originState[1:] == state[1:])
# print(state)
return state,b
test()
来看看下面这道题,能够理解那么MT19937也就差不多了
WKCTF easy_random
import random
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
flag = b'WKCTF{}'
pad_flag = pad(flag,16)#填充
key = random.randbytes(16)
cipher = AES.new(key,AES.MODE_ECB)#AES加密
print(cipher.encrypt(pad_flag))
# b'a\x93\xdc\xc3\x90\x0cK\xfa\xfb\x1c\x05$y\x16:\xfc\xf3+\xf8+%\xfe\xf9\x86\xa3\x17i+ab\xca\xb6\xcd\r\xa5\x94\xeaVM\xdeo\xa7\xdf\xa9D\n\x02\xa3'
with open('random.txt','w') as f:
for i in range(2496):
f.write(str(random.getrandbits(8))+'\n')
这个看我之前的wp吧,写的很详细了
WKCTFwp
现成模块
此外,介绍几个现成的模块,即可以直接用的,很方便
randcrack库
这个是官方的解释:https://pypi.org/project/randcrack/
import random, time
from randcrack import RandCrack
random.seed(time.time())
rc = RandCrack()
for i in range(624):
rc.submit(random.getrandbits(32))
# Could be filled with random.randint(0,4294967294) or random.randrange(0,4294967294)
print("Random result: {}\nCracker result: {}"
.format(random.randrange(0, 4294967295), rc.predict_randrange(0, 4294967295)))
大致来说:我们需要给出624个32位由random模块产生的随机数(即设置状态),然后就可以使用predict进行预测,这样我们就可以省去了根据数据恢复state的步骤了,其他函数就自己去探索吧(因为本人也没咋遇到过了)
import random
from randcrack import RandCrack
rc=RandCrack()#实例化randcrack类
for i in range(624):
rc.submit(random.getrandbits(32))#将624个随机数传入,即设置状态
print(random.getrandbits(64))
print(rc.predict_getrandbits(64))#根据前面给的状态往后预测
#10211364013887435935
#10211364013887435935
注意,使用这个库存在一些限制:
1.传入312个64位的数据
import random
from randcrack import RandCrack
rc = RandCrack()
for i in range(312):
rc.submit(random.getrandbits(64))
print(random.getrandbits(32))
print(rc.predict_getrandbits(32))
#报错了,ValueError: Didn't recieve enough bits to predict
#没有获取足够的位数去预测
2.那么试试传入624个64位的数据
import random
from randcrack import RandCrack
rc = RandCrack()
for i in range(624):
rc.submit(random.getrandbits(64))
print(random.getrandbits(32))
print(rc.predict_getrandbits(32))
# 888233832
# 0
#预测结果错误
3.试试传入624个16位的数据
import random
from randcrack import RandCrack
rc = RandCrack()
for i in range(624):
rc.submit(random.getrandbits(16))
print(random.getrandbits(32))
print(rc.predict_getrandbits(32))
# 2554149584
# 3275678419
#预测结果错误
也就是说,randcrack只能提交624次,且必须是32位数,位数多了少了都不行,这样预测出的结果才是正确的
可以看出randcrack库还是比较局限的
Extend MT19937 Predictor库
这个库相对于randcrack就要灵活很多了
官方解释:extend-mt19937-predictor · PyPI
一样,我们需要先传入624个32位的数据,即设置状态state:
ExtendMT19937Predictor().setrandbits(random.getrandbits(bits), bits)
#bits表示需要生成数据的位数
这个库可以传入不是32的位数,只要最后传入了不少于624个32位的数据即可
具体使用:
- 传入数据之后,可以使用predict_getrandbits()函数往后预测数据
import random
from extend_mt19937_predictor import ExtendMT19937Predictor
predictor = ExtendMT19937Predictor()
for _ in range(624):
predictor.setrandbits(random.getrandbits(32), 32)
for _ in range(1024):
assert predictor.predict_getrandbits(32) == random.getrandbits(32)
assert predictor.predict_getrandbits(64) == random.getrandbits(64)
assert predictor.predict_getrandbits(128) == random.getrandbits(128)
assert predictor.predict_getrandbits(256) == random.getrandbits(256)
- backtrack_getrandbits() 实现往前预测
import random
from extend_mt19937_predictor import ExtendMT19937Predictor
numbers = [random.getrandbits(64) for _ in range(1024)]
predictor = ExtendMT19937Predictor()
for _ in range(78):#78*(256/32)=624
predictor.setrandbits(random.getrandbits(256), 256)
#先往前预测回退到之前的状态state
_ = [predictor.backtrack_getrandbits(256) for _ in range(78)]
for x in numbers[::-1]:#往前预测
assert x == predictor.backtrack_getrandbits(64)
参考博客:
大部分参考badmonkey的文章,大佬膜拜了:浅析MT19937伪随机数生成算法-安全客 - 安全资讯平台 (anquanke.com)
[CTF/randcrack]python随机数预测模块分析及改进方案