• [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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    容易知道要求出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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    仔细观察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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    轮新的状态值【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
    
    • 1
    • 2
    • 3
    • 4

    我们可以从这里逆出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]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    求出了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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167

    参考:

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

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

  • 相关阅读:
    Android——m3u8视频文件下载
    MATLB|具有储能的经济调度及机会约束和鲁棒优化
    【SAP-ABAP】-权限批导-批量分配角色给具体用户
    使用veth和bridge模拟容器网络
    Scanner例题讲解
    集群搭建(1)
    Ffmpeg-(2):ubuntu系统将ffmpeg添加到系统环境变量中
    [Linux]进程间通信(进程间通信介绍 | 匿名管道 | 命名管道)
    在网络中加入新的模块时,有办法不影响原来的网络层次名字吗
    类和对象(下)
  • 原文地址:https://blog.csdn.net/qq_52193383/article/details/126321781