[NPUCTF2020]Mersenne twister

[NPUCTF2020]Mersenne twister

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

from flag import flag ,seed

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('./cipher.txt' , 'wb')
    for i in flag:
        key = b''.join([random.getramdanbits() for _ in range(4)])
        cipher = encrypt(key , chr(i).encode())
        f.write(cipher)

容易知道要求出seed才能解密。

通过分析可知,加密过程中一个字符对应 4 个随机数,根据题目给出的已知flag格式,可以求得前28个随机数和最后4个随机数(也就是第101~104)。通过这些随机数,写出Next()的逆过程,可以求出相应的generate之后的state。

Next逆过程如下:

浅析MT19937伪随机数生成算法 - 安全客,安全资讯平台 (anquanke.com)

# 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_state(tmp):
    tmp = inverse_right_mask(tmp, 18,0x34adf670)
    tmp = inverse_left_mask(tmp, 15, 0xefc65400)
    tmp = inverse_left_mask(tmp, 7, 0x9ddf4680)
    tmp = inverse_right(tmp, 11)
    return tmp

仔细观察generate()

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

轮新的状态值【i】由这一轮的【i】(提供最高位)和【i+1】(提供除最高bit位)拼接,右移1位后异或【(i + 130) % 233】而来。随后根据y & 1选择要不要异或0x9908f23f。

而此时我们正好已知state[0]和state[103],于是我们可以猜测state[104]。

  1. 根据y&1的情况(也就是state[104]最低 bit 位)选择是否进行异或。
  2. 异或state[0]。
  3. 求出相应情况的y。
  4. 猜测state[104]最高 bit 位的情况,求出state[104]。

至此我们得到了srand()之后的state[104],那怎么求出seed呢?

仔细观察srand()

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

我们可以从这里逆出seed。& 0xffffffff是不是就是mod (0xffffffff+1),于是/1812433253就相当于乘它的逆元。而self.state[i] ^ (self.state[i] >> 27)这步操作,只需要再执行一遍就可以求出原来的state[i]。

相应代码如下

def recover_seed(seed):
    mod = 0xffffffff + 1
    inv = int(invert(1812433253,mod))
    init = [0]*104 + [seed]
    for i in range(103, -1, -1):
        init[i] = ((init[i+1] + i)*inv) % mod
        init[i] = init[i] ^ (init[i] >> 27)
    return init[0]

求出了seed之后还有进行判断是否是对的,只需要再执行一遍srand()和generate(),得到的结果跟已知的generate()之后的state[]做对比就行了。

求出了seed之后,后面的解密就很简单了。

总体代码如下:

import hashlib
from binascii import hexlify , unhexlify
from gmpy2 import invert

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

# 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_state(tmp):
    tmp = inverse_right_mask(tmp, 18,0x34adf670)
    tmp = inverse_left_mask(tmp, 15, 0xefc65400)
    tmp = inverse_left_mask(tmp, 7, 0x9ddf4680)
    tmp = inverse_right(tmp, 11)
    return tmp

with open(r'cipher.txt', 'rb') as f:
    cipher = unhexlify(f.readline())
#print(len(hashlib.md5(b'n').digest()))#16bits

key1 = XOR(hashlib.md5(b'n').digest(), cipher[:16])
state0 = recover_state(int.from_bytes(key1[:4], 'big'))
print(state0)
key2 = XOR(hashlib.md5(b'}').digest(), cipher[-16:])
state103 = recover_state(int.from_bytes(key2[-4:], 'big'))
print(state103)

#用state[104]恢复seed
def recover_seed(seed):
    mod = 0xffffffff + 1
    inv = int(invert(1812433253,mod))
    init = [0]*104 + [seed]
    for i in range(103, -1, -1):
        init[i] = ((init[i+1] + i)*inv) % mod
        init[i] = init[i] ^ (init[i] >> 27)
    return init[0]

#生成初始state
def srand(seed):
    state = [seed] + [0]*232
    for i in range(232):
        state[i+1] = 1812433253 * (state[i] ^ (state[i] >> 27)) - i
        state[i+1] &= 0xffffffff

    return state

#检验
def check(state):
    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
    if state[0] == state0 and state[103] == state103:
        return True
    else:
        False

#恢复generate之前的state[104]
def dec_generate(a,b):#a为state0,b为state103
    maybe_list = []
    #if state[104]最低bit为0
    temp = b
    temp ^= a
    y = (temp << 1) + 0
    #猜 state[104]最高bit为0,1
    may1 = 0x80000000 + (y & 0x7fffffff)
    may2 = y & 0x7fffffff
    # if state[104]最低bit为1
    temp ^= 0x9908f23f
    temp ^= a
    y = (temp << 1) + 1
    # 猜 state[104]最高bit为0,1
    may3 = 0x80000000 + (y & 0x7fffffff)
    may4 = y & 0x7fffffff
    maybe_list = [may1, may2, may3, may4]
    print(f'maybe_list = {maybe_list}')
    for each in maybe_list:
        seed = recover_seed(each)
        state = srand(seed)
        if check(state.copy()) == True:
            return seed

    return -1

seed = dec_generate(state0, state103)
print(f'seed = {seed}')

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 decrypt(key, cipher):
    tmp = XOR(key, cipher)
    for i in range(20, 127):
        if hashlib.md5(chr(i).encode()).digest() == tmp:
            return chr(i)


random = mt73991(seed)
flag = ''
for i in range(0, len(cipher), 16):
    key = b''.join([random.getramdanbits() for _ in range(4)])
    ch = decrypt(key, cipher[i:i+16])
    flag += ch

print(flag)

参考:

2022DASCTF MAY 出题人挑战赛 - gla2xy’s blog (gal2xy.github.io)

浅析MT19937伪随机数生成算法 - 安全客,安全资讯平台 (anquanke.com)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值