MT19937

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位的statedef __init__(self, seed):self.mt = [0] * 624self.mt[0] = seed#这里的mt[]数组就是我们的状态stateself.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 >> 11y = y ^ y << 7 & 2636928640y = y ^ y << 15 & 4022730752y = 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 randomrandom.seed(1)
print(hex(random.getrandbits(32))[2:])#getrandbits(a)是生成a位二进制的随机数
print(hex(random.getrandbits(32))[2:])
#2265b1f5
#91b7584arandom.seed(1)
print(hex(random.getrandbits(8))[2:])
print(hex(random.getrandbits(8))[2:])
#22
#91random.seed(1)
print(hex(random.getrandbits(64))[2:])#64位的是两个32位的拼接,但是是倒序拼接
#91b7584a2265b1f5random.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 randomori_state = random.getstate() # 得到该伪随机数发生器的state,也就是状态量
# 输出随机数
print(random.getrandbits(32)) # 3410545957
print(random.getrandbits(32)) # 1647702295random.setstate(ori_state)
# 验证随机数是否相等
print(random.getrandbits(32)) # 3410545957
print(random.getrandbits(32)) # 1647702295
#也就是说相同的state可以得到相同的随机数

接下来看看state的组成

import randomrandom.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 >> 11y = y ^ y << 7 & 2636928640y = y ^ y << 15 & 4022730752y = 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 = resfor i in range(bits // shift):tmp = res ^ tmp >> shiftreturn tmp# right shift with mask inverse
def inverse_right_mask(res, shift, mask, bits=32):tmp = resfor i in range(bits // shift):tmp = res ^ tmp >> shift & maskreturn tmp# left shift inverse
def inverse_left(res, shift, bits=32):tmp = resfor i in range(bits // shift):tmp = res ^ tmp << shiftreturn tmp# left shift with mask inverse
def inverse_left_mask(res, shift, mask, bits=32):tmp = resfor i in range(bits // shift):tmp = res ^ tmp << shift & maskreturn tmpdef extract_number(y):y = y ^ y >> 11y = y ^ y << 7 & 2636928640y = y ^ y << 15 & 4022730752y = y ^ y >> 18return y&0xffffffffdef 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 flagdef convert(m):m = m ^ m >> 13m = m ^ m << 9 & 2029229568m = m ^ m << 17 & 2245263360m = m ^ m >> 19return mdef transform(message):assert len(message) % 4 == 0new_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 += blockreturn new_messagetransformed_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 = resfor i in range(bits // shift):tmp = res ^ tmp >> shiftreturn tmp# right shift with mask inverse
def inverse_right_mask(res, shift, mask, bits=32):tmp = resfor i in range(bits // shift):tmp = res ^ tmp >> shift & maskreturn tmp# left shift inverse
def inverse_left(res, shift, bits=32):tmp = resfor i in range(bits // shift):tmp = res ^ tmp << shiftreturn tmp# left shift with mask inverse
def inverse_left_mask(res, shift, mask, bits=32):tmp = resfor i in range(bits // shift):tmp = res ^ tmp << shift & maskreturn 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 numberdef convert(m):m = m ^ m >> 13m = m ^ m << 9 & 2029229568m = m ^ m << 17 & 2245263360m = m ^ m >> 19return mdef transform(message):assert len(message) % 4 == 0new_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 += blockreturn new_message
def circle(m):t=mwhile True:x=tt=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 Randomdef buildT():rng = Random()T = matrix(GF(2),32,32)for i in range(32):s = [0]*624# 构造特殊的states[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 Tdef 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 statedef test():rng = Random()# 泄露信息leak = [rng.getrandbits(32) for i in range(32)]originState = [i for i in rng.getstate()[1][:32]]# 构造矩阵TT = buildT()recoverState = [reverse(T,i) for i in leak]print(recoverState==originState)
test()

题型2 预测随机数

就是题目给出一系列的随机数,然后根据这些随机数预测后面的随机数

这一类题型本质上就是题型一,因为也是基于对extract_number 函数的逆向而发展来的

直接看题吧

[GKCTF 2021]Random

import random
from hashlib import md5def 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 randomrandom.seed(1)
print(hex(random.getrandbits(32))[2:])#getrandbits(a)是生成a位二进制的随机数
print(hex(random.getrandbits(32))[2:])
#2265b1f5
#91b7584arandom.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 md5from Crypto.Util.number import *
from gmpy2 import *# -*- coding: utf-8 -*-from random import Randomdef invert_right(m,l,val=''):length = 32mx = 0xffffffffif val == '':val = mxi,res = 0,0while i*l<length:mask = (mx<<(length-l)&mx)>>i*ltmp = m & maskm = m^tmp>>l&valres += tmpi += 1return resdef invert_left(m,l,val):length = 32mx = 0xffffffffi,res = 0,0while i*l < length:mask = (mx>>(length-l)&mx)<<i*ltmp = m & maskm ^= tmp<<l&valres |= tmpi += 1return resdef 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 mdef clone_mt(record):state = [invert_temper(i) for i in record]gen = Random()gen.setstate((3,tuple(state+[0]),None))#设置state,方便后面的预测return genf = 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))624g = 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 RandCrackf = 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+=1rc = 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 = 0x80000000low = 0x7fffffffmask = 0x9908b0dfstate = curfor i in range(623,-1,-1):tmp = state[i]^state[(i+397)%624]# recover Y,tmp = Yif tmp & high == high:tmp ^= masktmp <<= 1tmp |= 1else:tmp <<=1# recover highest bitres = 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 = Yif tmp & high == high:tmp ^= masktmp <<= 1tmp |= 1else:tmp <<=1res |= (tmp)&lowstate[i] = res    return state

那来道题目看看

[V&N2020 公开赛]Backtrace

# !/usr/bin/env/python3
import randomflag = "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 randomdef invert_right(m,l,val=''):length = 32mx = 0xffffffffif val == '':val = mxi,res = 0,0while i*l<length:mask = (mx<<(length-l)&mx)>>i*ltmp = m & maskm = m^tmp>>l&valres += tmpi += 1return resdef invert_left(m,l,val):length = 32mx = 0xffffffffi,res = 0,0while i*l < length:mask = (mx>>(length-l)&mx)<<i*ltmp = m & maskm ^= tmp<<l&valres |= tmpi += 1return resdef 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 = 0x80000000low = 0x7fffffffmask = 0x9908b0dfstate = curfor i in range(3,-1,-1):tmp = state[i+624]^state[(i+397)%624]#根据新的结果推前一个state的结果# recover Y,tmp = Yif tmp & high == high:tmp ^= masktmp <<= 1tmp |= 1else:tmp <<=1# recover highest bitres = 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 = Yif tmp & high == high:tmp ^= masktmp <<= 1tmp |= 1else:tmp <<=1res |= (tmp)&lowstate[i] = resreturn statef = 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] * 624self.mt[0] = seed#这里的mt[]数组就是我们的状态stateself.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 invertdef _int32(x):return int(0xFFFFFFFF & x)def init(seed):mt = [0] * 624mt[0] = seedfor i in range(1, 624):mt[i] = _int32(1812433253 * (mt[i - 1] ^ mt[i - 1] >> 30) + i)return mtseed = 2080737669def invert_right(res,shift):tmp = resfor i in range(32//shift):res = tmp^res>>shiftreturn _int32(res)def recover(last):n = 1<<32inv = invert(1812433253,n)#求逆元for i in range(623,0,-1):last = ((last-i)*inv)%nlast = invert_right(last,30)return laststate = 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]*624i = 0while i<length:ind = i//32expont = i%32state[ind] = 1<<(31-expont)s = (3,tuple(state+[0]),None)yield sstate[ind] = 0i += 1def 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 rowdef buildBox():b = matrix(GF(2),length,length)rg = getRow()for i in tqdm(range(length)):b[i] = next(rg)return bdef 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 padflag = 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 RandCrackrandom.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 RandCrackrc=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 RandCrackrc = 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 RandCrackrc = 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 RandCrackrc = 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 ExtendMT19937Predictorpredictor = 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 ExtendMT19937Predictornumbers = [random.getrandbits(64) for _ in range(1024)]predictor = ExtendMT19937Predictor()for _ in range(78):#78*(256/32)=624predictor.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.mzph.cn/diannao/48827.shtml

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

相关文章

安全防御:过滤技术

目录 一、URL过滤 URL过滤的方式 二、HTTP与HTTPS HTTP协议获取URL的方式 HTTP协议做控制管理的流程 HTTPS 1&#xff0c;配置SSL的解密功能 2&#xff0c;直接针对加密流量进行过滤 需求&#xff1a; 三、DNS过滤 四、内容过滤 文件过滤技术 文件过滤技术的处理流…

抖音私信卡片制作教程,使用W外链创建抖音/快手/小红书卡片

在数字营销和社交媒体日益繁荣的今天&#xff0c;利用外部链接&#xff08;W外链平台&#xff09;为抖音平台创建卡片已成为一种有效的推广手段。抖音卡片不仅可以直接将观众导向目标网页或产品&#xff0c;还能提高用户的参与度和品牌的曝光度。下面&#xff0c;我们将详细介绍…

java-selenium 截取界面验证码图片并对图片文本进行识别

参考链接 1、需要下载Tesseract工具并配置环境变量&#xff0c;步骤如下 Tesseract-OCR 下载安装和使用_tesseract-ocr下载-CSDN博客 2、需要在IDEA中导入tess4j 包&#xff1b;在pom.xml文件中输入如下内容 <!--导入Tesseract 用于识别验证码--><dependency>&l…

微信小程序开发:基础架构与配置文件

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

基于支持向量机(SVM)的数据回归预测

代码原理及流程 支持向量机&#xff08;SVM&#xff09;是一种强大的机器学习算法&#xff0c;既可以用于分类问题&#xff0c;也可以用于回归问题。在回归问题中&#xff0c;SVM 的目标是找到一个函数&#xff0c;使得预测值与实际值之间的误差最小化&#xff0c;并且保持在一…

set(集合),multiset容器及pair队组的创建

1.set的基本概念&#xff1a;所有元素再插入时自动按升序排序&#xff0c;set/multiset属于关联式容器&#xff0c;底层结构是用二叉树实现的 set与multiset区别&#xff1a; set中不允许容器中有重复的元素 multiset允许容器中有重复的元素 2.set的构造函数 3.set的大小和…

python—爬虫爬取视频样例

下面是一个使用Python爬虫爬取视频的基本例子。创建一个Python爬虫来爬取视频通常涉及到几个步骤&#xff1a;发送HTTP请求、解析网页内容、提取视频链接、下载视频文件。 import jsonimport requests from lxml import etreeif __name__ __main__:# UA伪装head {"User…

数字图像处理笔记(二)---- 像素加图像统计特征

系列文章目录 文章目录 系列文章目录前言一、认识数字图像二、图像的数学描述二、图像的统计特征总结 前言 慕课视频地址 一、认识数字图像 图像分为模拟图像和数字图像。要想获得数字图像需要通过采样量化编码等过程。 量化和采样的过程是将模拟信号转化为数字信号。编码的过…

JVM常用工具中jmap实现手动进行堆转储(heap dump文件)并使用MAT(Memory Analyzer Tool)进行堆分析-内存消耗分析

场景 JVM-常用工具(jps、jstat、jinfo、jmap、jhat、jstack、jconsole、jvisualvm)使用&#xff1a; JVM-常用工具(jps、jstat、jinfo、jmap、jhat、jstack、jconsole、jvisualvm)使用_jvm分析工具-CSDN博客 上面讲了jmap的简单使用。 下面记录其常用功能&#xff0c;实现堆…

sqlite数据库,轻量级数据库的使用

什么是sqlite数据库 sqlite是具有零配置、无服务的特点&#xff0c;遵循 ACID 规则&#xff0c;是一款备受欢迎的轻量级数据库。 tips&#xff1a;ACID 规则即&#xff0c;A&#xff08;原子性&#xff09;、C&#xff08;一致性&#xff09;、I&#xff08;独立性&#xff0…

前端npm下载依赖 idealTree:vue3-demo: sill idealTree buildDeps解决方案

第一步 第二步 第三步 第四步 直接输入npm install就可以安装依赖了

SpringBoot源码(1)ApplicationContext和BeanFactory

1、调用getBean方法 SpringBootApplication public class SpringBootDemoApplication {public static void main(String[] args) {ConfigurableApplicationContext applicationContext SpringApplication.run(SpringBootDemoApplication.class, args);applicationContext.get…

Java强软弱虚引用的特点以及应用场景(面试重点)

强&#xff1a;即使OOM也不回收软&#xff1a;内存溢出前回收弱&#xff1a;只要垃圾收集就死虚&#xff1a;对垃圾收集没关系&#xff0c;只有得到通知&#xff08;插眼&#xff0c;也操作不了对象、只能看到它还活着&#xff09; 一、软引用 代码示例&#xff1a; public cl…

【C++开源】GuiLite:超轻量UI框架-入门

开发环境说明 使用visual Studio 2022进行开发 下载源码 从如下的网址进行源码和示例代码的下载: GitHub源码网址为:idea4good/GuiLite示例代码路径为:idea4good/GuiLiteExample使用方法 GuiLite是一个仅有头文件的一个库,使用的时候直接include到自己的UIcode.cpp文件…

十七、(正点原子)Linux LCD驱动

一、Framebuffer设备 在 Linux 中应用程序通过操作 RGB LCD 的显存来实现在 LCD 上显示字符、图片等信息。 先来看一下裸机 LCD 驱动如下&#xff1a; ①、初始化 I.MX6U 的 eLCDIF 控制器&#xff0c;重点是 LCD 屏幕宽(width)、高(height)、 hspw、 hbp、 hfp、 vspw…

【Python】连接MySQL数据库:详细教程与示例代码

文章目录 1. 安装必要的库2. 建立与MySQL的连接3. 执行SQL查询4. 插入数据5. 更新数据6. 删除数据7. 错误处理8. 小结 在数据驱动的开发中&#xff0c;连接数据库是一个至关重要的技能。Python作为一门强大的编程语言&#xff0c;提供了多种方式连接并操作MySQL数据库。本文将详…

【时时三省】(C语言基础)函数和数组

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ——csdn时时三省 函数 跟数学里面的函数很相似 数组 一组相同类型的元素的集合 比如把5个整形1-5存起来 int arr&#xff3b;10&#xff3d;&#xff1d;&#xff5b;1&#xff0c;2&#xff0c;3&#x…

浏览器打开抽奖系统html

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>在线抽奖 随机选取 自动挑选</title> <script src"https://libs.baidu.com/jquery/1.10.2/jquery.min.js"></script> <style> body {…

antdesgin table 组件下载成excel

文章目录 发现宝藏一、需求二、报错 发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【宝藏入口】。 一、需求 原组件如下&#xff0c;需要添加下载功能 import React, { useState } from rea…

Spring Boot + Spring Cloud 入门

运行配置 java -jar spring-boot-config-0.0.1-SNAPSHOT.jar --spring.profiles.activetest --my1.age32 --debugtrue "D:\Program Files\Redis\redis-server.exe" D:\Program Files\Redis\redis.windows.conf "D:\Program Files\Redis\redis-cli.exe" &q…