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