# [CH] Where Is My Bit
###### tags: `Writeup` `Crypto` `Chinese`
> [name=Curious]
## 思路和解法
> 這篇 Writeup 會直接引用這兩個連結裡的符號
> - How To Crack MT19937 : [Link](https://jazzy.id.au/2010/09/22/cracking_random_number_generators_part_3.html)
> - Python Impletement : [Link](https://github.com/Curious-Lucifer/CTFLib/blob/master/Crypto/PRNG.py#L21)
分析一下 `server.py` 可以發現我們需要去預測 `random.randrange(0x80000000, 0xFFFFFFFF)`,去翻一下 `random.randrange` 的程式碼,可以知道最後會呼叫到 `random.getrandbits(31)`,所以說這題主要目標是預測 `random.getrandbits(31)`
如果再去翻一下 `random.getrandbits` 的 [code](https://github.com/python/cpython/blob/ce558e69d4087dd3653207de78345fbb8a2c7835/Modules/_randommodule.c#L487) 的話,可以發現 `random.getrandits(31)` 會先用 MT19937 產生一個 32 bits 的偽隨機數,然後把最後一個 bit 拿掉。
首先因為我們拿到的 random number 只有最後一個 bit 不知道,而且最後一個 bit 不是 1 就是 0,所以可以先假設最後一個 bit 是 0。接下來要把 random number 丟給 `rand_to_state` 轉換成 state number。
`rand_to_state` 的第一步是 `tmp = un_bitshift_right_xor(rand_num, 18)`,因為 `rand_num = tmp ^ (tmp >> 18)`
```
10110111010111100111111001110010 tmp
10110111010111 tmp >>> 18
10110111010111100101001110100101 tmp ^ (tmp >>> 18) = rand_num
```
當進行 `un_bitshift_right_xor` 時,因為`tmp` 和 `rand_num` 的前 18 bits 是相同的,所以 `tmp` 的前 18 bits 是直接從 `rand_num` 那邊拿取,然後用 `tmp` 前 18 bits 再去推 `tmp` 的後 16 bits。因為 `rand_num` 只有最後一個 bit 不確定,所以 `tmp` 也是只有最後一個 bit 不確定。
接下來是進行 `pre_tmp = un_bitshift_left_xor_mask(tmp, 15, 0xefc60000)`,因為 `tmp = pre_tmp ^ ((pre_tmp << 15) & 0xefc60000)`
```
# 這裡的 `tmp` 跟上面沒有關係,我隨便打的
00111111001110011000000000000000 pre_tmp << 15
11101111110001100000000000000000 0xefc60000
11111111111111111000000000000000 (pre_tmp << 15) & 0xefc60000
10110111010111100111111001110011 pre_tmp
11111111111111111000000000000000 (pre_tmp << 15) & 0xefc60000
01001000101000011111111001110011 pre_tmp ^ ((pre_tmp << 15) & 0xefc60000) = tmp
10011101001011000101011010000000
```
進行 `pre_tmp = un_bitshift_left_xor_mask(tmp, 15, 0xefc60000)` 時,因為 0xefc60000 的最後 15 bits 都是 0,所以 `pre_tmp` 和 `tmp` 的最後 15 bits 是一樣的。透過 `pre_tmp` 的最後 15 bits 可以推論出最後 30 - 16 bits,然後再推出全部。因為 `tmp` 的最後 1 bit 是不確定的,所以說 `pre_tmp` 的最後 1 bit 也不確定,接著因為用 `pre_tmp` 最後 15 bits 來推最後 30 - 16 bits,但是 0xefc60000 在第倒數 16 bit 是 0,所以 `pre_tmp` 最後 1 bit 不確定對於 `pre_tmp` 的倒數 30 - 16 bits 並沒有影響,所以說倒數 30 - 16 bit 沒有不確定的 bit,接著 `pre_tmp` 的倒數 32 - 31 bits 也沒有不確定的 bit。
到這邊為止 `pre_tmp` 只有第 31 個 bit 不確定(正著數從 0 開始,倒著數從 1 開始),如果用以上方法推論到 `state_number`,那 `state_number` 的第 3, 10, 14, 17, 21, 24, 25, 28, 31 個 bit 都是不確定的,而且這邊因為 xor 運算的特性,全部不確定的 bits 要不就一起錯,要不就會一起對。
接著可以看一下 `gen_next_state` 這個函數,我們這邊用 `state[0]` `state[1]` `state[397]` 和下一個 `state[0]` 來思考
```python=
y = (state0 & 0x80000000) + (state1 & 0x7fffffff)
next = y >> 1
next ^= state397
if ((y & 1) == 1):
next ^= 0x9908b0df
next_state0 = next
```
從 code 中可以發現 `y` 其實是取 `state0` 的第 0 個 bit 和 `state1` 的第 1 - 31 個 bit,接著因為 `next` 是 `y >> 1` 的關係,所以不確定的 bit 就被右移了 1 bit,如果可以找到一個不確定的 bit,在 `y` 右移之後對應到的 `state397` 和 `next_state0` 的 bit 都是確定的,而且 0x9908b0df 對應的 bit 是 0,就可以解方程式知道那個不確定的 bit 對不對,然後根據前面的推論,如果不對,那 `rand_num` 的最後一個 bit 就是 1。
我找到的那個不確定的 bit 是第 10 個 bit,`state1` 第 10 個 bit 右移之後到 `next` 的第 11 個 bit,且 `state397` 和 `next_state0` 的第 11 個 bit 都是確定的,且 0x9908b0df 的第 11 個 bit 是 0,所以比較用 `state0` 和 `state1` 算出來 `next_state0` 的第 11 bit 和 `next_state0` 本身的第 11 bit,如果不同的話那 `state1` 就要用 `rand_num` 且最後一個 bit 是 1 來重推一次。
由以上方法就可以把整個 `state` 推完,當知道整個 `state` 後,就可以預測以後的 `rand_num` 了。
Solve Script :
```python=
def un_bitshift_right_xor(value: int, shift: int):
"""
- input : `value (int)`, `shift (int)`
- output : `result (int)` , `value = (result >> shift) ^ result`
"""
i = 0
result = 0
while ((i * shift) < 32):
partmask = int('1' * shift + '0' * (32 - shift), base = 2) >> (shift * i)
part = value & partmask
value ^= (part >> shift)
result |= part
i += 1
return result
def un_bitshift_left_xor_mask(value: int, shift: int, mask: int):
"""
- input : `value (int)`, `shift (int)`, `mask (int)`
- output : `result (int)` , `value = ((result << shift) & mask) ^ result`
"""
i = 0
result = 0
while ((i * shift) < 32):
partmask = int('0' * (32 - shift) + '1' * shift, base = 2) << (shift * i)
part = value & partmask
value ^= (part << shift) & mask
result |= part
i += 1
return result
def rand_to_state(value: int):
"""
- input : `value (int)`
- output : `value (int)` , for MT19937
"""
value = un_bitshift_right_xor(value, 18)
value = un_bitshift_left_xor_mask(value, 15, 0xefc60000)
value = un_bitshift_left_xor_mask(value, 7, 0x9d2c5680)
value = un_bitshift_right_xor(value, 11)
return value
def state_to_rand(value: int):
"""
- input : `value (int)`
- output : `value (int)` , for MT19937
"""
value ^= (value >> 11)
value ^= (value << 7) & 0x9d2c5680
value ^= (value << 15) & 0xefc60000
value ^= (value >> 18)
return value
def gen_next_state(state: list[int]):
"""
- input : `state (list[int])` , `state` will be changed to next state
- output : None
"""
assert len(state) == 624
for i in range(624):
y = (state[i] & 0x80000000) + (state[(i + 1) % 624] & 0x7fffffff)
next = y >> 1
next ^= state[(i + 397) % 624]
if ((y & 1) == 1):
next ^= 0x9908b0df
state[i] = next
# =================================
from pwn import *
from tqdm import trange
r = remote('lotuxctf.com', 30003)
r.recvline()
def get_random():
r.sendlineafter(b'> ', b'-1')
return int(r.recvline().strip().split(b'is ')[1].decode()) - 0x80000000
randlist = [get_random() for _ in trange(624 * 2)]
state = [rand_to_state(randlist[0] << 1)]
for i in range(624):
s_test = rand_to_state(randlist[i + 1] << 1)
s_xor = rand_to_state(randlist[i + 397] << 1)
s_new = rand_to_state(randlist[i + 624] << 1)
if (bin((s_test >> 1) ^ s_xor)[2:].rjust(32, '0')[11] != bin(s_new)[2:].rjust(32, '0')[11]):
state.append(rand_to_state((randlist[i + 1] << 1) | 1))
else:
state.append(s_test)
state, next_state0 = state[:-1] ,state[-1]
gen_next_state(state)
state[0] = next_state0
gen_next_state(state)
for i in range(100):
r.sendlineafter(b'> ', str((state_to_rand(state[i]) >> 1) + 0x80000000).encode())
r.recvline()
r.interactive()
```
{%hackmd M1bgOPoiQbmM0JRHWaYA1g %}