MT19937

news2024/9/20 22:22:30

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函数可以知道zx是存在线性关系的(可以推出来的)XT=ZX=ZT1这就把问题转换成了矩阵的问题了其中X,ZGF(2)上的132的向量,TGF(2)上的3232的矩阵我们要在GF(2)上求解X,已知Z,如何求T呢?可以令X=(1,0,0,....,0),这样XT得到的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位的数据即可

具体使用:

  1. 传入数据之后,可以使用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)
  1. 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随机数预测模块分析及改进方案

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1941319.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】深刻基础笔记

目录 关于debug&#xff1a; 多文件编译&#xff1a; 编译器工作原理 预处理&#xff1a; 如何将机器二进制转换成可以看懂的文件 链接器的工作原理 Pointers指针 Reference引用 C线程 程序如何从源文件变成exe可执行文件&#xff1f; 首先是预处理器#include <..…

斐波那契数列的多种解法 C++实现,绘图部分用Python实现

斐波那契数列的多种解法 C实现&#xff0c;绘图部分用Python实现 flyfish 斐波那契数列&#xff08;Fibonacci sequence&#xff09;是一个经典的数列&#xff0c;定义如下&#xff1a; { 0 if n 0 1 if n 1 F ( n − 1 ) F ( n − 2 ) if n > 1 \begin{cases} 0 &…

c++网络编程实战——开发基于ftp协议的文件传输模块(二) 配置ftp服务与手动执行ftp命令

配置FTP服务 一.前言 博主的环境是阿里云服务器&#xff0c;操作系统版本为 ubuntu20.04,一下所有操作都基于以上环境下进行的操作&#xff0c;同时为了简化操作我将开放同一个云服务器的不同端口&#xff0c;让它同时充当服务端和客户端&#xff0c;大家如果想测试效果更好且…

Java基础:类与对象,递归,方法

类与对象的区别和联系 1.类是抽象的,概念的他是数据类型 2.对象是具体的实际的,代表一个具体事务 3.类是对象的模板,对象是类的个体 **对象在内存中的存在形式** 基本数据类型在堆,引用类型如String,数组在方法区 对象的属性 属性基本说明 1.成员变量or成员属性 属性的定…

双向链表<数据结构 C版>

目录 关于链表的分类 双向链表结构体 初始化 尾插 头插 打印 判断是否为空 尾删 头删 查找 指定位置之后的插入 指定位置的删除 销毁 关于链表的分类 根据链表的三大特性&#xff0c;单向or双向、带头or不带头、循环or不循环&#xff0c;可将链表分为2*2*2&#xf…

利用ascp下载SRA Explorer中转录组数据

最近在windows系统里下载了MobaXterm可以远程登入服务器&#xff0c;处理RNA的数据&#xff0c;需要从NCBI数据库上下载数据。本文提供用虚拟机ubuntu或者linux系统下载Aspera的方法和问题解决&#xff0c;以及从NCBI上批量下载数据库、最后得到一个项目里的所有fastq文件。 A…

前端:Vue学习-2

前端&#xff1a;Vue学习-2 1. vue的生命周期2. 工程化开发和脚手架Vue CLI2.1 组件化开发2.2 scoped解决样式冲突2.3 data是一个函数2.4 组件通信2.5 非父子通信- event bus事件&#xff0c;provide&inject 3.v-model原理->实现父子组件双向绑定4. sync 修饰符->实现…

谷粒商城实战笔记-42-前端基础-Vue-生命周期和钩子函数

下面是Vue官网关于生命周期及不同阶段的钩子函数的图示。 Vue 实例生命周期是指从一个组件被创建到最终被销毁的整个过程。 在这一过程中&#xff0c;Vue 提供了一系列的“钩子”函数&#xff0c;在生命周期的不同阶段执行自定义的代码。 以下是 Vue 对象生命周期的主要阶段…

C语言实现二叉树以及二叉树的详细介绍

目录 1.树概念及结构 1.1树的概念 1.2树的相关概念 1.3树的表示 2.二叉树概念及结构 2.1二叉树的概念 2.2特殊的二叉树 2.3二叉树的性质 2.4二叉树的存储结构 3.二叉树顺序结构--特殊的二叉树--堆及其实现 3.1堆的概念及结构 3.2堆的实现 3.2.1堆的结构 3.2.2堆…

filebeat生产环境配置

配置文件属性 生产配置 filebeat.inputs: - type: logenabled: truepaths: - /tmp/logs/*.log- /var/log/system.log- /var/log/wifi.logsymlinks: truejson.keys_under_root: truejson.message_key: xxxjson.add_error_key: true# 如果想卡部分日志&#xff0c;比如用时间作…

Monaco 使用 HoverProvider

Monaco 中自定义 Hover&#xff0c;Hover 效果是指当鼠标移动文字上展示出提示效果&#xff0c;就像页面上的 Tooltip 效果。最终页面的显示效果如下&#xff1a; 通过 registerHoverProvider 注册 Hover 触发时的处理方法 接口中提供了 4 个参数&#xff0c;前两个参数比较重…

python学习之闭包与装饰器

一、闭包 闭包允许一个函数访问并操作函数外部的变量&#xff08;即父级作用域中的变量&#xff09;&#xff0c;即使在该函数外部执行。 特性&#xff1a; (1)外部函数嵌套内部函数。 (2)外部函数可以返回内部函数。 (3)内部函数可以访问外部函数的局部变量。 def out()…

【jmeter边界值提取器】

【目的】 从响应头中取token的内容 【方案】 使用后置处理器-边界值提取器 【组件路径】HTTP请求->右键添加->后置处理器->边界提取器 用途&#xff1a;边界提取器(Boundary Extractor)是放在Sample请求之后执行的&#xff0c;用于获取左右边界中间的所有字符&#xf…

Windows配置Qt+VLC

文章目录 前言下载库文件提取文件编写qmakeqtvlc测试代码 总结 前言 在Windows平台上配置Qt和VLC是开发多媒体应用程序的一个重要步骤。Qt作为一个强大的跨平台应用开发框架&#xff0c;为开发人员提供了丰富的GUI工具和库&#xff0c;而VLC则是一个开源的多媒体播放器&#x…

基于STM32的农业大棚温湿度采集控制系统的设计

目录 1、设计要求 2、系统功能 3、演示视频和实物 4、系统设计框图 5、软件设计流程图 6、原理图 7、主程序 8、总结 &#x1f91e;大家好&#xff0c;这里是5132单片机毕设设计项目分享&#xff0c;今天给大家分享的是智能教室。 设备的详细功能见网盘中的文章《8、基…

451.根据字符出现频率排序(中等)

451.根据字符出现频率排序&#xff08;中等&#xff09; 1. 题目描述2.详细题解3.代码实现3.1 Python3.2 Java 1. 题目描述 题目中转&#xff1a;451.根据字符出现频率排序&#xff08;中等&#xff09; 2.详细题解 题目&#xff1a; 347. 前 K 个高频元素&#xff08;中等&am…

2月科研——arcgis计算植被差异

ArcGIS中&#xff0c;设置高于或低于某个值的像元为 -9999&#xff0c;然后将这些地方设为空——目的&#xff1a;去除异常值和黑色背景值 Con(("T_std ano7.tif" > 2) | ("T_std ano7.tif" < - 2), - 9999,"T_std ano7.tif") SetNull(&…

数学基础【俗说矩阵】:初等矩阵和矩阵的初等行变化关系推导

初等矩阵和矩阵的初等行变换 初等矩阵 矩阵的初等行变换 对单位阵E进行一次初等行变化得到的阵叫做初等阵。 这里只能进行一次初等行变换。 置换阵 给矩阵【左乘】一个【置换阵】&#xff0c;相当与对该矩阵进行了一次【置换阵】对应的【置换】初等行变换&#xff1b; 数…

爬取百度图片,想爬谁就爬谁

前言 既然是做爬虫&#xff0c;那么肯定就会有一些小心思&#xff0c;比如去获取一些自己喜欢的资料等。 去百度图片去抓取图片吧 打开百度图片网站&#xff0c;点击搜索xxx&#xff0c;打开后&#xff0c;滚动滚动条&#xff0c;发现滚动条越来越小&#xff0c;说明图片加载…

Wordpress文章分享到微信朋友圈不带图像问题的解决

目录 一、插件选择 二、插件安装 三、获取微信公众号管理信息 1、登录微信公众平台 2、配置“JS接口安全域名” 3、至安全中心设置“IP白名单” 4、获得“开发者ID(AppID)” 5、获得“开发者密码(AppSecret)” 四、配置插件 五、验证 WordPress装配好后&#xff0c;可…