mt19937

前言

最近见到的mt19937问题有点多,有的做起来也挺棘手的,还是要总结整理一下子。

前置

梅森旋转算法

定义:梅森旋转算法(Mersenne twister)是一个伪随机数发生算法。由松本真和西村拓士在1997年开发,基于有限二进制字段上的矩阵线性递归。可以快速产生高质量的伪随机数,修正了古典随机数发生算法的很多缺陷。

实现过程主要分为三个阶段:

第一阶段:获得基础的梅森旋转链;

第二阶段:对于旋转链进行旋转算法;

第三阶段:对于旋转算法所得的结果进行处理;

mt19937

值得一提的是,python里的random库就是用这种算法

代码如下:

def _int32(x):
    return int(0xFFFFFFFF & x)
 
class MT19937:
    # 根据seed初始化624的state
    def __init__(self, seed):
        self.mt = [0] * 624
        self.mt[0] = seed
        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()
        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
        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

观察上述代码,其实可以发现大体就分为以下四个部分:

1、_int32(x)模块

返回一个32位的二进制代码

2、init_(self, seed)

基于已知的seed生成624个state块,将state的第一个数值定位seed,代码中的623个循环就是通过state间的变换求出剩下的state块

3、extract_number(self)

通过此模块来得到不同的伪随机数。首先要进行判断,如果self.mti指向第一个state,则可以直接开始twist;否则,就要进入下边的伪随机数生成过程:用通过seed求得的state值进行代码中的变换,返回需要的伪随机数

4、twist(self)

如果只有上边的代码,那最多也就是生成624个不同的伪随机数,但是加上了twist的代码就可以生成2**(32)-1个不同的伪随机数了。

以下的部分代码全部来自https://www.anquanke.com/post/id/205861#h2-2

笔者只是以自己的语言进行重述,方便个人理解

1.逆向extract_number函数:

y1=y^y>>18

异或从低位开始,所以这部不影响y的高18位,y1的高18位就是y的高18位,y高18位右移与y低18位异或得到y1的低18位,这样我们就能知道y的高36位了,以此类推,一定能够还原

o = 66666666666666666666666666666666666666666
y = o^o>>18
# 控制位移的次数
for i in range(len(bin(o)[2:])//18):
    y = y^(y>>18)
print(y==o)
#True

同理可将剩下的部分还原

完整代码

o = 66666666666666666666666666666666666666666
 
# right shift inverse
def inverse_right(res, shift):
    tmp = res
    bits=len(bin(res)[2:])
    for i in range(bits // shift):
        tmp = res ^ tmp >> shift
    return tmp
 
 
# right shift with mask inverse
def inverse_right_mask(res, shift, mask):
    tmp = res
    bits=len(bin(res)[2:])
    for i in range(bits // shift):
        tmp = res ^ tmp >> shift & mask
    return tmp
 
# left shift inverse
def inverse_left(res, shift):
    tmp = res
    bits=len(bin(res)[2:])
    for i in range(bits // shift):
        tmp = res ^ tmp << shift
    return tmp
 
 
# left shift with mask inverse
def inverse_left_mask(res, shift, mask):
    tmp = res
    bits=len(bin(res)[2:])
    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
 
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
 
y = extract_number(o)
print(recover(y) == o)
#True

这里找了今年山东电子学会网络安全赛道的一道题目来举个例子

from Crypto.Util.number import *

flag = getPrime(32)

def init(flag):
    s = [0] * 100
    s[0] = flag
    for i in range(1, 100):
        s[i] = 0xFFFFFFFF & (1812433253 * (s[i - 1] ^ s[i - 1] >> 30) + i)
    return s

state = init(flag)

def enc(y):
    y = y ^ y >> 11
    y = y ^ y << 7 & 2636928640
    y = y ^ y << 15 & 4022730752
    y = y ^ y >> 18
    return y&0xffffffff

c = enc(state[-1])
print(c)
#1047573452

这题有两步,前一步enc函数就是常见的extract_number函数

将init之后的最后一个伪随机数代入extract_number

可以直接逆

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 recover1(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

y = 1047573452
print(recover1(y))
print(extract_number(recover1(y))==y)

2、预测随机数

只要有前624个数,可以求到对应的state,接着可以实现预测

import random

print(random.getrandbits(32))
state = random.getstate()
y = random.getrandbits(32)
random.setstate(state)
x = random.getrandbits(32)
print(x == y)

这里拿我给星盟出的纳新题来做个示范

import random
from Crypto.Cipher import AES
def padding(str):
    while len(str) < 16:
        str += b'\x00'
    return str
flag = b'xmcve{xxxxx}'
flag = padding(flag)
f = open("random.txt",'w')
for i in range(624):
    f.write(str(random.getrandbits(32)))
    f.write('\n')
key = padding(str(random.getrandbits(32)).encode())
aes = AES.new(key,AES.MODE_CBC)
cip = aes.encrypt(flag)
print(cip)

#b'\x03\xef\x8f\x1f\x85|\xca\x9a\xde\xdd6]\xbb\x88\x7f\x00'

其实就是随机生成了624个数,让预测下一个,AES没啥意思

可以直接用逆向extract_number的方法来做

from random import Random
from Crypto.Cipher import AES

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))
    return gen

f = open("random",'r').readlines()
prng = []
for i in f:
    i = i.strip('n')
    prng.append(int(i))

g = clone_mt(prng[:624])
for i in range(624):
    g.getrandbits(32)#产生前624个随机数,让state状态到生成flag前

def padding(msg):
    return msg + bytes([0 for i in range((16 - len(msg))%16)])


key = padding(str(g.getrandbits(32)).encode())

aes = AES.new(key,AES.MODE_CBC)
cip =b'\x03\xef\x8f\x1f\x85|\xca\x9a\xde\xdd6]\xbb\x88\x7f\x00'
flag = aes.decrypt(cip)
print(flag)

当然我比较倾向于用randcrack

这里就要介绍以下randcrack库了

randcrack

工作原理
该生成器基于MersenneTwister(梅森算法),能够生成具有优异统计特性的数字(与真正的随机数无法区分)。但是,此生成器的设计目的不是加密安全的。您不应在关键应用程序中用作加密方案的PRNG。
原理如下。
它从生成器获得前624个32位数字,并获得Mersenne Twister矩阵的最可能状态,即内部状态。从这一点来看,发电机应该与裂解器同步。
如何使用
将生成器生成的32位整数准确地输入cracker非常重要,因为它们无论如何都会生成,但如果您不请求它们,则会删除它们。 同样,您必须在出现新种子之后,或者在生成624 ∗ 32位之后,准确地为破解程序馈电,因为每个624 ∗ 32位数字生成器都会改变其状态,并且破解程序设计为从某个状态开始馈电。

RandCrack中有一个方法是predict_getrandbits(),在给出的随机数数量多时,可以预测下一个随机数。

from Crypto.Util.number import *
from Crypto.Cipher import AES
from randcrack import RandCrack
def padding(msg):
    return msg + bytes([0 for i in range((16 - len(msg))%16)])

rc = RandCrack()
with open('random.txt','r') as f:
    f = f.read().splitlines()
    # print(len(f))
    for i in f:
        rc.submit(int(i))
key = padding(str(rc.predict_getrandbits(32)).encode())

aes = AES.new(key,AES.MODE_CBC)
cip = b'\x03\xef\x8f\x1f\x85|\xca\x9a\xde\xdd6]\xbb\x88\x7f\x00'
flag = aes.decrypt(cip)
print(flag)

很方便,当然还有更方便的,待会儿再说

3、逆向求之前的随机数

在已知连续624个随机数时,可以还原state,预测后边的随机数,当然也可以获得624个随机数之前的随机数,可以考虑去逆向twist获得前一组的state,进而获得已知的624个随机数前边的624个随机数

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

回溯函数

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

我并没有在实践中用过backtrace,唯一一次遇到关于回溯的是HWS的那道题,也是我为什么要整理这篇文章

import hashlib
import random
from os import urandom


class Random(object):
    def __init__(self):
        self._id = random.getrandbits(128)
        self._code = random.getrandbits(256)

    @property
    def id(self):
        return hex(self._id)[2:].zfill(32)

    @property
    def code(self):
        return hex(self._code)[2:].zfill(64)

random.seed(urandom(32))
random_list=[Random() for _ in range(53)]

id=[]
code=[]
for i in range(1, len(random_list)):
    id.append(random_list[i].id)
    code.append(random_list[i].code)
print(id)
print(code)

flag=hashlib.md5((random_list[0].id+random_list[0].code).encode()).hexdigest()
print('flag{%s}' %flag)
'''
id
code
'''

依照badmonkey师傅的方法,我最开始的想法是拆

如题可见已知的id是52个128位随机数、code是52个256位随机数,拆开的话正好是624个32位数,满足预测和回溯的标准。

之后需要求最开始的一组id和code,那就往前推12个32位数组合就可以了,但并没有实现(应该是我改的不太严谨吧,呜呜)

之后在查阅资料的时候找到了集成库

https://github.com/NonupleBroken/ExtendMT19937Predictor

import hashlib
from extend_mt19937_predictor import ExtendMT19937Predictor

id = 
code = 

predictor = ExtendMT19937Predictor()

for i in range(len(id)):
    id_ = int(id[i], 16)
    code_ = int(code[i], 16)
    predictor.setrandbits(id_, 128)
    predictor.setrandbits(code_, 256)

tmp_id = []
tmp_code = []
for i in range(53):
    tmp_code.append(hex(predictor.backtrack_getrandbits(256))[2:].zfill(64))
    tmp_id.append(hex(predictor.backtrack_getrandbits(128))[2:].zfill(32))

tmp_id = tmp_id[::-1]
tmp_code = tmp_code[::-1]

assert tmp_id[1:] == id
assert tmp_code[1:] == code

flag=hashlib.md5((tmp_id[0]+tmp_code[0]).encode()).hexdigest()
print('flag{%s}' %flag)

setrandbits之后顺水推舟

去集成库中翻看相关代码想去弄清原理

    def setrandbits(self, y, bits):
        if bits % 32:
            raise ValueError("number of bits must be a multiple of 32")
        if not 0 <= y < 2 ** bits:
            raise ValueError("invalid state")
        while bits > 0:
            self._setrand_int32(y & 0xffffffff)
            y >>= 32
            bits -= 32

    def _setrand_int32(self, y):
        """
        Receive the target PRNG's outputs and reconstruct the inner state.
        when 624 consecutive DOWRDs is given, the inner state is uniquely determined.
        """
        assert 0 <= y < 2 ** 32

        if self._is_enough:
            if self._check:
                r = self._predict_getrand_int32()
                if y != r:
                    raise ValueError("this rand number is not correct: %d. should be: %d" % (y, r))
                else:
                    return
            else:
                if self._mti == 0:
                    self._twist()

        self._mt[self._mti] = self._untemper(y)
        self._mti = (self._mti + 1) % N

        if self._mti == 0 and not self._is_enough:
            self._is_enough = True

    def _predict_getrand_int32(self):
        if self._mti == 0:
            self._twist()
        y = self._temper(self._mt[self._mti])
        self._mti = (self._mti + 1) % N
        return y

    def _backtrack_getrand_int32(self):
        self._mti = (self._mti - 1) % N
        y = self._temper(self._mt[self._mti])
        if self._mti == 0:
            self._untwist()
        return y
    def backtrack_getrandbits(self, bits):
        if bits < 0:
            raise ValueError("number of bits must be greater than zero")
        if not self._is_enough:
            raise ValueError("number of set bits is not enough")
        y = 0
        while bits > 0:
            t = self._backtrack_getrand_int32()
            shift = 32
            if bits % 32:
                shift = bits % 32
                t >>= 32 - shift
            y |= t << (bits - shift)
            bits -= shift
        return y

其实本质上还是逆向twist,不深究了,果然现成工具会使人变得懒惰

4、逆向init函数

这一部分,是根据state,去逆向seed

init函数如下:

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

mt[i] = _int32(1812433253 * (mt[i - 1] ^ mt[i - 1] >> 30) + i)

乘号后边的部分是可逆的,跟逆向extract_number函数一个道理,能知道高60位数,然后有限次内一定可以还原

而_int32就是取低32位,相当于%2**32

而1812433253跟2**32没有除1以外的公因子,因此存在模逆,那逆向的操作就是先求模逆,再重复逆向extract_number的操作

举个例子,还是电子学会那题,这次是逆向init函数

def init(flag):
    s = [0] * 100
    s[0] = flag
    for i in range(1, 100):
        s[i] = 0xFFFFFFFF & (1812433253 * (s[i - 1] ^ s[i - 1] >> 30) + i)
    return s

可以看到,题目中的函数对mt19937进行了魔改,不过程度并不大,区别就是这里的init只生成了100个伪随机数,那相对的,我们把100个数倒着递回去就能恢复了呗

c=1819357814
#回溯
from gmpy2 import invert

def init(flag):
    s = [0] * 100
    s[0] = flag
    for i in range(1, 100):
        s[i] = 0xFFFFFFFF & (1812433253 * (s[i - 1] ^ s[i - 1] >> 30) + i)
    return s

def _int32(x):
    return int(0xFFFFFFFF & x)

def invert_right(res,shift):
    tmp = res
    for i in range(32//shift):
        res = tmp^res>>shift
    return _int32(res)

def recover2(last):
    n = 1<<32
    inv = invert(1812433253,n)
    for i in range(99,0,-1):
        last = ((last-i)*inv)%n
        last = invert_right(last,30)
    return last

print(recover2(c))
print(init(recover2(c))[-1]==c)

拓展题型

对于这部分的内容,我其实是模棱两可的,也只靠着照葫芦画瓢,在本地打通过类似的题目

bytectf2022 cardshark

#!/usr/bin/env python3.9
# -*- coding: utf-8 -*-
import string
import random
import socketserver
import signal
from os import urandom
from hashlib import sha256
# from flag import FLAG
FLAG = b'ByteCTF{1234}'

BANNER = rb"""
.--------.--------.--------.--------.     .--------.--------.--------.--------.--------.
| C.--.  | A.--.  | R.--.  | D.--.  |.-.  | S.--.  | H.--.  | A.--.  | R.--.  | K.--.  |
|  :/\:  |  (\/)  |  :():  |  :/\:  (( )) |  :/\:  |  :/\:  |  (\/)  |  :():  |  :/\:  |
|  :\/:  |  :\/:  |  ()()  |  (__)  |'-.-.|  :\/:  |  (__)  |  :\/:  |  ()()  |  :\/:  |
|  '--'C |  '--'A |  '--'R |  '--'D | (( ))  '--'S |  '--'H |  '--'A |  '--'R |  '--'K |
`--------`--------`--------`--------'  '-'`--------`--------`--------`--------`--------'
"""


class Card:
    def __init__(self):
        random.seed(urandom(32))
        self.cards = []
        for t in ('Hearts', 'Spades', 'Diamonds', 'Clubs'):
            for p in ('J', 'Q', 'K', 'A'):
                self.cards.append(f'{p} {t}')

    def deal(self):
        n = random.getrandbits(4)
        return self.cards[n]


class Task(socketserver.BaseRequestHandler):
    def _recv_all(self):
        BUFF_SIZE = 1024
        data = b''
        while True:
            part = self.request.recv(BUFF_SIZE)
            data += part
            if len(part) < BUFF_SIZE:
                break
        return data.strip()

    def send(self, msg, newline=True):
        if isinstance(msg, str):
            msg = msg.encode()
        if newline:
            msg += b'\n'
        self.request.sendall(msg)

    def recv(self, prompt='> '):
        self.send(prompt, newline=False)
        return self._recv_all()

    def proof_of_work(self):
        random.seed(urandom(32))
        alphabet = string.ascii_letters + string.digits
        proof = ''.join(random.choices(alphabet, k=32))
        hash_value = sha256(proof.encode()).hexdigest()
        self.send(f'sha256(XXXX+{proof[4:]}) == {hash_value}')
        nonce = self.recv(prompt='Give me XXXX > ')
        if len(nonce) != 4 or sha256(nonce + proof[4:].encode()).hexdigest() != hash_value:
            return False
        return True

    def timeout_handler(self, signum, frame):
        raise TimeoutError

    def handle(self):
        try:
            # self.send(BANNER)
            #
            # signal.signal(signal.SIGALRM, self.timeout_handler)
            # signal.alarm(60)
            #
            # if not self.proof_of_work():
            #     self.send('Wrong!')
            #     return

            card = Card()
            coin = 5200
            count = 0

            self.send('Greetings! I will give you my secret, if you can guess my card 200 times in a row. '
                      'One coin, one chance.')

            signal.alarm(3600)

            while coin > 0:
                coin -= 1
                c = card.deal()
                r = self.recv(prompt='Your guess > ').decode('l1')
                if r == c:
                    count += 1
                    self.send(f'Correct! Your progress: {count}/200.')
                    if count >= 200:
                        self.send('You are the Card Shark! Flag is yours:')
                        self.send(FLAG)
                        break
                else:
                    count = 0
                    self.send(f'Sorry! My card is {c}.')

            if coin == 0:
                self.send('You have no money! See you another day.')

            self.send('Bye!')

        except TimeoutError:
            self.send('Timeout!')
        except:
            pass
        finally:
            self.request.close()


class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
    pass


if __name__ == '__main__':
    HOST, PORT = '0.0.0.0', 10000
    print(HOST, PORT)
    server = ForkedServer((HOST, PORT), Task)
    server.allow_reuse_address = True
    server.serve_forever()

要求是连续猜对200次才送flag

还是随机数预测,每次给4位数

python中的getrandbits(4)其实截取的是getrandbits(32)的最高4位,那我们就需要4992个数,构造T才能还原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()

最后的利用(来自佬)

from random import Random
from sage.all import *
from tqdm import tqdm
from string import *
from pwn import *
import hashlib

sh = remote('0.0.0.0',10000)
context.log_level = 'debug'

# passpow
def baopo():
    rev = sh.recvuntil("sha256(XXXX+")
    tmp= sh.recv(28).decode()
    sh.recvuntil(" == ")
    res = sh.recv(64).decode()
    str = string.ascii_letters + string.digits

    for i1 in str:
        for i2 in str:
            for i3 in str:
                for i4 in str:
                    plain = i1 + i2 + i3 + i4 + tmp
                    x=i1 + i2 + i3 + i4
                    encode = hashlib.sha256(plain.encode()).hexdigest()
                    if encode == res:
                        sh.recvuntil("Give me XXXX > ")
                        sh.sendline(str(x))
def recoverState(leak):
    x = T.solve_left(vector(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)
    return state

def backfirst(state):
    high = 0x80000000
    low = 0x7fffffff
    mask = 0x9908b0df
    tmp = state[623] ^ state[396]
    if tmp & high == high:
        tmp = mask ^ tmp
        tmp <<= 1
        tmp |= 1
    else:
        tmp <<= 1
    return int((1 << 32 - 1) | tmp & low), int(tmp & low)

def pwn(leak):
    state = recoverState(leak)
    L = [leak[i] for i in range(400)]
    prng = Random()
    guess1, guess2 = backfirst(state)
    print(guess1, guess2)
    state[0] = guess1
    s = state
    prng.setstate((3, tuple(s + [0]), None))
    g1 = [int(j) for j in ''.join([bin(prng.getrandbits(4))[2:].zfill(4) for i in range(100)])]
    print(g1,L)
    if g1 == L:
        print("first")
        prng.setstate((3, tuple(s + [0]), None))
        return prng

    state[0] = guess2
    s = state
    prng.setstate((3, tuple(s + [0]), None))
    g2 = [int(j) for j in ''.join([bin(prng.getrandbits(4))[2:].zfill(4) for i in range(100)])]
    if g2 == L:
        print("second")
        prng.setstate((3, tuple(s + [0]), None))
        return prng

length = 19968 // 4
T = sage.all.load('Matrix')

baopo()

cards = []
for t in ('Hearts', 'Spades', 'Diamonds', 'Clubs'):
    for p in ('J', 'Q', 'K', 'A'):
        cards.append(f'{p} {t}')
        
def get_data():
    sh.recvuntil("guess > ")
    sh.sendline("1")
    sh.recvuntil("My card is ")
    card = sh.recvline(False)[:-1].decode()
    res = bin(cards.index(card))[2:].zfill(4)
    return res

leaks = ''
for i in range(4992):
    leaks += get_data()

leak = [int(i) for i in leaks]
my_random = pwn(leak)
leaks = ''
for i in tqdm(range(length)):
    tmp=str[bin(my_random.getrandbits(4))[2:].zfill(4)]
    leaks.append(int(tmp))

for i in range(201):
    ans = cards[my_random.getrandbits(4)]
    sh.recvuntil("guess > ")
    sh.sendline(ans)

sh.interactive()
# sh.close()
posted @ 2023-08-08 20:23  Liooooo  阅读(485)  评论(0编辑  收藏  举报