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随机数预测模块分析及改进方案



















