---
# System prepended metadata

title: CRACKING RANDOM PYTHON LESS THAN 32 BITS
tags: [Writeup_CTF]

---

# CRACKING RANDOM PYTHON LESS THAN 32 BITS
```python3=
import random
import binteger
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

def getbit():
    return random.getrandbits(1)

known = b''
for _ in range(2500):
    word = binteger.Bin([getbit() for _ in range(8)]).int
    known += word.to_bytes(1, "big")

FLAG = b'W1{day-la-flag}'
key = b""
for _ in range(16):
    word = binteger.Bin([getbit() for _ in range(8)]).int
    key += word.to_bytes(1, "big")

cipher = AES.new(key, AES.MODE_ECB)
ciphertext = cipher.encrypt(pad(FLAG, 16))

# out 5f0512a86cd89a47ecd5cb77af672f9137ecb5cd0c6141d0d5cd4257f0b4e72588d87fbc0f4cb0660ee0a4483bfbdf1a
```

### rbtree_mersenne.py
```python3=
class Twister:
    N = 624
    M = 397
    A = 0x9908b0df

    def __init__(self):
        self.state = [ [ (1 << (32 * i + (31 - j))) for j in range(32) ] for i in range(624)]
        self.index = 0
    
    @staticmethod
    def _xor(a, b):
        return [x ^ y for x, y in zip(a, b)]
    
    @staticmethod
    def _and(a, x):
        return [ v if (x >> (31 - i)) & 1 else 0 for i, v in enumerate(a) ]
    
    @staticmethod
    def _shiftr(a, x):
        return [0] * x + a[:-x]
    
    @staticmethod
    def _shiftl(a, x):
        return a[x:] + [0] * x

    def get32bits(self):
        if self.index >= self.N:
            for kk in range(self.N):
                y = self.state[kk][:1] + self.state[(kk + 1) % self.N][1:]
                z = [ y[-1] if (self.A >> (31 - i)) & 1 else 0 for i in range(32) ]
                self.state[kk] = self._xor(self.state[(kk + self.M) % self.N], self._shiftr(y, 1))
                self.state[kk] = self._xor(self.state[kk], z)
            self.index = 0

        y = self.state[self.index]
        y = self._xor(y, self._shiftr(y, 11))
        y = self._xor(y, self._and(self._shiftl(y, 7), 0x9d2c5680))
        y = self._xor(y, self._and(self._shiftl(y, 15), 0xefc60000))
        y = self._xor(y, self._shiftr(y, 18))
        self.index += 1

        return y
    
    def getrandbits(self, bit):
        return self.get32bits()[:bit]

class Solver:
    def __init__(self):
        self.equations = []
        self.outputs = []
    
    def insert(self, equation, output):
        for eq, o in zip(self.equations, self.outputs):
            lsb = eq & -eq
            if equation & lsb:
                equation ^= eq
                output ^= o
        
        if equation == 0:
            return

        lsb = equation & -equation
        for i in range(len(self.equations)):
            if self.equations[i] & lsb:
                self.equations[i] ^= equation
                self.outputs[i] ^= output
    
        self.equations.append(equation)
        self.outputs.append(output)
    
    def solve(self):
        num = 0
        for i, eq in enumerate(self.equations):
            if self.outputs[i]:
                # Assume every free variable is 0
                num |= eq & -eq
        
        state = [ (num >> (32 * i)) & 0xFFFFFFFF for i in range(624) ]
        return state
```

### Solve.py
```python3=
from z3 import *
import rbtree_mersenne
import random
import time

def rbtree_solve(outputs):
    twister = rbtree_mersenne.Twister()
    solver = rbtree_mersenne.Solver()

    for tup in outputs:
        res, bits = tup
        equation = twister.getrandbits(bits)

        for i in range(bits):
            solver.insert(equation[i], (res >> (bits - 1 - i)) & 1)

    state = solver.solve()
    return (3, tuple(state + [0]), None)


def z3_solve(outputs):
    MT = [BitVec(f'm{i}', 32) for i in range(624)]
    s = Solver()
    
    def tamper(y):
        y ^= LShR(y, 11)
        y ^= (y << 7) & 0x9D2C5680
        y ^= (y << 15) & 0xEFC60000
        return y ^ LShR(y, 18)
        
    def getnext():
        x = Concat(Extract(31, 31, MT[0]), Extract(30, 0, MT[1]))
        y = If(x & 1 == 0, BitVecVal(0, 32), 0x9908B0DF)
        MT.append(MT[397] ^ LShR(x, 1) ^ y)
        return tamper(MT.pop(0))
        
    def getrandbits(n):
        return Extract(31, 32 - n, getnext())
        
    s.add([getrandbits(op[1]) == op[0] for op in outputs])
    assert(s.check() == sat)
    state = [s.model()[x].as_long() for x in [BitVec(f'm{i}', 32) for i in range(624)]]
    
    return (3, tuple(state + [0]), None)

def int_to_binary(num):
    binary_string = bin(num)[2:]  
    padded_binary_string = binary_string.zfill(8) 
    return padded_binary_string

if __name__ == "__main__":
    with open('known','rb') as file:
        data = file.read()
    binary = ""
    for i in data:
        binary += str(int_to_binary(i))
    bits = 1
    number = 2500*8

    output = []
    for _ in range(number):
        output.append((int(binary[_]), bits))
    print(output)

    st = time.time()
    random.setstate(rbtree_solve(output))
    for _ in range(number):
        assert output[_][0] == random.getrandbits(bits)
    key = ""
    for _ in range(16*8):
        key += str(random.getrandbits(bits))
    fin = time.time()
    print(f"rbtree took {fin - st} seconds.")
    print(key)

    st = time.time()
    random.setstate(z3_solve(output))
    for _ in range(number):
        assert output[_][0] == random.getrandbits(bits)
    for _ in range(10000):
        assert check[_] == random.getrandbits(bits)
    fin = time.time()
    print(f"z3 took {fin - st} seconds.")

```


```python3=
import random
import binteger
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

key = "00011101111110110010011101011111100111101101001000111110110000001101101010011110010001010101101000011100110100101110010011010001"
integer_value = int(key, 2)
key = integer_value.to_bytes((len(key) + 7) // 8, byteorder='big')


cipher = AES.new(key, AES.MODE_ECB)
ciphertext = cipher.decrypt(bytes.fromhex("5f0512a86cd89a47ecd5cb77af672f9137ecb5cd0c6141d0d5cd4257f0b4e72588d87fbc0f4cb0660ee0a4483bfbdf1a"))
print(ciphertext)
```