[NPUCTF2020]Mersenne twister 已知明文推测随机数seed

from hashlib import *
from itertools import *
from binascii import hexlify , unhexlify

flag="npuctf{123456789012345678}"
seed=73991
assert len(flag) == 26
assert flag[:7] == 'npuctf{'
assert flag[-1] == '}'

XOR = lambda s1 ,s2 : bytes([x1 ^ x2 for x1 ,x2 in zip(s1 , s2)])

class mt73991:
    def __init__(self , seed):
        self.state = [seed] + [0] * 232
        self.flag = 0
        self.srand()
        self.generate()
    def srand(self):
        for i in range(232):
            self.state[i+1] = 1812433253 * (self.state[i] ^ (self.state[i] >> 27)) - i
            self.state[i+1] &= 0xffffffff


    def generate(self):
        for i in range(233):
            y = (self.state[i] & 0x80000000) | (self.state[(i+1)%233] & 0x7fffffff)
            temp = y >> 1
            temp ^= self.state[(i + 130) % 233]
            if y & 1:
                temp ^= 0x9908f23f
            self.state[i] = temp
    def getramdanbits(self):
        if self.flag == 233:
            self.generate()
            self.flag = 0
        bits = self.Next(self.state[self.flag]).to_bytes(4 , 'big')
        self.flag += 1
        return bits
        
    def Next(self , tmp):
        tmp ^= (tmp >> 11)
        tmp ^= (tmp << 7) & 0x9ddf4680
        tmp ^= (tmp << 15) & 0xefc65400
        tmp ^= (tmp >> 18) & 0x34adf670   
        return tmp

def encrypt(key , plain):
    tmp = md5(plain).digest()
    return hexlify(XOR(tmp , key))

if __name__ == "__main__":
    flag = flag.encode()
    random = mt73991(seed)
    f = open('./ciphertext.txt' , 'wb')
    for i in flag: 
        key = b''.join([random.getramdanbits() for _ in range(4)])
        cipher = encrypt(key , chr(i).encode())
        f.write(cipher)

从main函数中的内容可知,只要我们知道了每次加密用到的key,就能逆encrypt函数恢复出明文

在每次加密中,key都是随机生成的新的四个随机字符相加,生成方式与seed有关,所以主要是来寻找seed,找到seed就可知所有key

self.state = [seed] + [0] * 232

self.srand()
self.generate()

bits = self.Next(self.state[self.flag]).to_bytes(4 , 'big')

因为self.flag首先是0

从这里可以看出state的第一位首先是seed,然后由seed经过srand和generate函数再生成新的随机数,再依靠这个随机数生成key,那么我们首先要找到生成key的随机数,再根据这个随机数来爆破出seed

to_bytes(4 , 'big')随机生成四位随机字符

利用已知明文,也就是前缀npuctf,我们提取出来这个‘n',以及这个n生成的前32位密文,来恢复出第一次生成的十六个字符的key

 找到第一次生成的key,取前四位:

from hashlib import *
from itertools import *
from binascii import hexlify , unhexlify
from Crypto.Util.number import *
import string

XOR = lambda s1 ,s2 : bytes([x1 ^ x2 for x1 ,x2 in zip(s1 , s2)])

cipher='cef4876036ee8b55aa59bca043725bf350a5e491debdef7ef7d63e9609a288ca1e2'
m=md5(b'n').digest()
c=unhexlify(cipher[:32])
key=XOR(c,m)
print(key)
key0=key[:4]
key0=bin(bytes_to_long(key0))[2:].zfill(32)

找到key的前四位后,我们逆Next函数来找到生成key前四位的随机数

def Next(self , tmp):
        tmp ^= (tmp >> 11)
        tmp ^= (tmp << 7) & 0x9ddf4680
        tmp ^= (tmp << 15) & 0xefc65400
        tmp ^= (tmp >> 18) & 0x34adf670 
        return tmp

给出逆Next代码:

def reverse1(x):
    x1=x[:18]
    tmp=eval('0b'+x[:14])&(eval('0b'+bin(0x34adf670)[2:][-14:]))
    x2=eval('0b'+x[-14:])^tmp
    x2=bin(x2)[2:].zfill(14)
    return x1+x2
def reverse2(x):
    x2=x[-15:]
    x1=(eval('0b'+x2)&eval('0b'+bin(0xefc65400)[2:][2:17]))^eval('0b'+x[2:17])
    tmp=eval('0b'+bin(x1)[-2:])&eval('0b'+bin(0xefc65400)[2:][:2])^eval('0b'+x[:2])
    return bin(tmp)[2:].zfill(2)+bin(x1)[2:].zfill(15)+x2
def reverse3(x):
    x3=x[-7:]
    x2=(eval('0b'+x3)&eval('0b'+bin(0x9ddf4680)[2:][-14:-7]))^eval('0b'+x[-14:-7])
    x1=(x2&eval('0b'+bin(0x9ddf4680)[2:][-21:-14]))^eval('0b'+x[-21:-14])
    x0=(x1&eval('0b'+bin(0x9ddf4680)[2:][-28:-21]))^eval('0b'+x[-28:-21])
    tmp=bin(x0)[2:].zfill(7)+bin(x1)[2:].zfill(7)+bin(x2)[2:].zfill(7)+x3
    ans=(eval('0b'+tmp[3:28]+'0000000')&0x9ddf4680)^eval('0b'+x)
    return bin(ans)[2:].zfill(32)
def reverse4(x):
    x1=x[:11]
    x2=eval('0b'+x1)^eval('0b'+x[11:22])
    tmp='00000000000'+(x1+bin(x2)[2:].zfill(11))[:21]
    ans=eval('0b'+tmp)^eval('0b'+x)
    return bin(ans)[2:].zfill(32)

这里四次逆Next的原理差不多,讲解一下最复杂的一部分:

reverse3:

tmp ^= (tmp << 7) & 0x9ddf4680

我们设这一行中传入的tmp经过移位、按位与、异或后得到新的tmp的称作密文,以此便于区分

我们要逆这一行代码,首先发现是tmp左移7位,经过按位与运算后与原tmp异或,由按位与和异或的性质可以得到tmp的后7位不变,这七位左移7位成为了[-14:-7]位,这些位数再与0x9ddf4680的[-14:-7]位按位与,再与密文的[-14:-7]位异或,就得到了原来的tmp的[-14:-7]位。、

这下我们恢复了tmp的[-14:-7]位,加上原来已知的后七位,后十四位都是已知的了,利用上面的方法,依次恢复出全部的32位,其他reverse原理相同

恢复seed生成的随机数后,开始爆破seed

def srand():
        for i in range(232):
            state[i+1] = 1812433253 * (state[i] ^ (state[i] >> 27)) - i
            state[i+1] &= 0xffffffff
def generate():
    for i in range(233):
        y = (state[i] & 0x80000000) | (state[(i+1)%233] & 0x7fffffff)
        temp = y >> 1
        temp ^= state[(i + 130) % 233]
        if y & 1:
            temp ^= 0x9908f23f
        state[i] = temp

seed=0
while True:
    if seed%10000==0:
        print(seed)
    state = [seed] + [0] * 232
    srand()
    generate()
    if state[0]!=res:
        seed+=1
    if state[0]==res:
        break
    
print(seed)
#1668245885

需要爆很长时间

得到seed后就简单多了

利用seed传入随机数生成器,得到每次的key,

def encrypt(key , plain):

    tmp = md5(plain).digest()

    return hexlify(XOR(tmp , key))

在encrypt函数中,将明文md5然后与key异或,由于key每次都不一样,我们先把明文md5的值打表以便后续使用 

printable=string.printable
dict={}

for i in printable:
    tmp = md5(i.encode()).digest()
    dict[tmp]=i

既然知道了key,将密文按顺序与key异或,得到tmp,再将tmp传入dict查表,得到明文

flag=''
seed=1668245885
random=mt73991(seed)
c=[]
f=open('cipher.txt').read()
for i in range(0,len(f),32):
    c.append(f[i:i+32])
c=c[:-1]
for i in c:
    key = b''.join([random.getramdanbits() for _ in range(4)])
    xor=unhexlify(i)
    tmp=XOR(xor,key)
    flag+=dict[tmp]

print(flag)

完整题目代码:

from hashlib import *
from itertools import *
from binascii import hexlify , unhexlify
from Crypto.Util.number import *
import string

XOR = lambda s1 ,s2 : bytes([x1 ^ x2 for x1 ,x2 in zip(s1 , s2)])

cipher='cef4876036ee8b55aa59bca043725bf350a5e491debdef7ef7d63e9609a288ca1e2'
m=md5(b'n').digest()
c=unhexlify(cipher[:32])
key=XOR(c,m)
print(key)
key0=key[:4]
key0=bin(bytes_to_long(key0))[2:].zfill(32)
#print(key0)



def reverse1(x):
    x1=x[:18]
    tmp=eval('0b'+x[:14])&(eval('0b'+bin(0x34adf670)[2:][-14:]))
    x2=eval('0b'+x[-14:])^tmp
    x2=bin(x2)[2:].zfill(14)
    return x1+x2
def reverse2(x):
    x2=x[-15:]
    x1=(eval('0b'+x2)&eval('0b'+bin(0xefc65400)[2:][2:17]))^eval('0b'+x[2:17])
    tmp=eval('0b'+bin(x1)[-2:])&eval('0b'+bin(0xefc65400)[2:][:2])^eval('0b'+x[:2])
    return bin(tmp)[2:].zfill(2)+bin(x1)[2:].zfill(15)+x2
def reverse3(x):
    x3=x[-7:]
    x2=(eval('0b'+x3)&eval('0b'+bin(0x9ddf4680)[2:][-14:-7]))^eval('0b'+x[-14:-7])
    x1=(x2&eval('0b'+bin(0x9ddf4680)[2:][-21:-14]))^eval('0b'+x[-21:-14])
    x0=(x1&eval('0b'+bin(0x9ddf4680)[2:][-28:-21]))^eval('0b'+x[-28:-21])
    tmp=bin(x0)[2:].zfill(7)+bin(x1)[2:].zfill(7)+bin(x2)[2:].zfill(7)+x3
    ans=(eval('0b'+tmp[3:28]+'0000000')&0x9ddf4680)^eval('0b'+x)
    return bin(ans)[2:].zfill(32)
def reverse4(x):
    x1=x[:11]
    x2=eval('0b'+x1)^eval('0b'+x[11:22])
    tmp='00000000000'+(x1+bin(x2)[2:].zfill(11))[:21]
    ans=eval('0b'+tmp)^eval('0b'+x)
    return bin(ans)[2:].zfill(32)


res=key0
for i in range(1,5):
    res=eval('reverse'+str(i)+'(res)')

res=int(res,2)


#爆破seed:
'''def srand():
        for i in range(232):
            state[i+1] = 1812433253 * (state[i] ^ (state[i] >> 27)) - i
            state[i+1] &= 0xffffffff
def generate():
    for i in range(233):
        y = (state[i] & 0x80000000) | (state[(i+1)%233] & 0x7fffffff)
        temp = y >> 1
        temp ^= state[(i + 130) % 233]
        if y & 1:
            temp ^= 0x9908f23f
        state[i] = temp

seed=0
while True:
    if seed%10000==0:
        print(seed)
    state = [seed] + [0] * 232
    srand()
    generate()
    if state[0]!=res:
        seed+=1
    if state[0]==res:
        break'''
    
#print(seed)
#1668245885

class mt73991:
    def __init__(self , seed):
        self.state = [seed] + [0] * 232
        self.flag = 0
        self.srand()
        self.generate()
    def srand(self):
        for i in range(232):
            self.state[i+1] = 1812433253 * (self.state[i] ^ (self.state[i] >> 27)) - i
            self.state[i+1] &= 0xffffffff


    def generate(self):
        for i in range(233):
            y = (self.state[i] & 0x80000000) | (self.state[(i+1)%233] & 0x7fffffff)
            temp = y >> 1
            temp ^= self.state[(i + 130) % 233]
            if y & 1:
                temp ^= 0x9908f23f
            self.state[i] = temp
    def getramdanbits(self):
        if self.flag == 233:
            self.generate()
            self.flag = 0
        bits = self.Next(self.state[self.flag]).to_bytes(4 , 'big')
        self.flag += 1
        return bits
        
    def Next(self , tmp):
        tmp ^= (tmp >> 11)
        tmp ^= (tmp << 7) & 0x9ddf4680
        tmp ^= (tmp << 15) & 0xefc65400
        tmp ^= (tmp >> 18) & 0x34adf670  
        return tmp




printable=string.printable
dict={}

for i in printable:
    tmp = md5(i.encode()).digest()
    dict[tmp]=i

flag=''
seed=1668245885
random=mt73991(seed)
c=[]
f=open('cipher.txt').read()
for i in range(0,len(f),32):
    c.append(f[i:i+32])
c=c[:-1]
for i in c:
    key = b''.join([random.getramdanbits() for _ in range(4)])
    xor=unhexlify(i)
    tmp=XOR(xor,key)
    flag+=dict[tmp]

print(flag)

参考:[NPUCTF2020]Mersenne twister(mt73991伪随机)_前方是否可导?的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Mersenne Twister(梅森旋转算法)是一种广泛使用的伪随机数生成算法,它具有良好的随机性和周期性特性。下面详细说Mersenne Twister算法的工作原理: 1. **初始化种子**: Mersenne Twister算法需要一个种子值来开始生成随机数序列。种子值可以是任意整数,通常使用当前时间戳作为种子。 2. **初始化状态**: 初始种子值通过一个称为“初始化状态”的过程,将其转换为一个内部状态数组。这个数组通常有 624 个元素,并且可以存储 32 位整数。 3. **填充状态数组**: 初始状态数组只是种子值的简单转换,接下来需要通过填充状态数组来生成更多的随机数。填充状态数组的过程是使用一个称为“梅森旋转”的操作,将当前状态数组中的元素与一些位运算和异或操作相结合,得到新的状态值。 4. **生成随机数**: 填充状态数组后,可以从状态数组中提取随机数。通常情况下,每次需要一个随机数时,会从状态数组中选择一个元素作为输出,并对其进行一系列的变换操作,以产生最终的随机数。 5. **重复填充和生成过程**: 当需要更多随机数时,将重复填充状态数组和生成随机数的过程。每次填充状态数组都会更新状态数组中的元素,从而保持随机性。 Mersenne Twister算法的关键之处在于其内部状态数组的维护和梅森旋转操作的使用。这些操作保证了生成的随机数序列具有良好的随机性和周期性特性。 在Python中,Mersenne Twister算法是random模块的默认随机数生成器。可以使用`random`模块中的各种函数和方法来生成随机数。例如: ```python import random random_number = random.random() # 生成0到1之间的随机浮点数 print(random_number) ``` 除了`random()`函数之外,`random`模块还提供了其他一些生成随机数的函数,如`randint()`(生成指定范围内的整数)、`uniform()`(生成指定范围内的随机浮点数)等。 总结来说,Mersenne Twister算法是一种常用的伪随机数生成算法,它通过内部状态数组和梅森旋转操作来生成高质量的随机数。在Python中,可以使用random模块来方便地调用这个算法生成各种类型的随机数

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Paintrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值