MT19937

打各种比赛学习到了很多知识点还有脚本,在这里粗略记录一下

通过随机数求state

由于MT19937是线性的,就可以直接通过

s1,19968T19968,19968=b1,19968\textbf{s}_{1,19968}T_{19968,19968} = \textbf{b}_{1,19968}

T是由MT19937本身算法得到的确定的线性关系,b是泄露的足够多的比特,这两个都是已知的
以此来求出s
详细学习参考:2024-同济大学第二届网络安全新生赛CatCTF-wp-crypto

两种方法:

矩阵方程

from sage.all import *
from Crypto.Util.number import *
from tqdm import *
import random

rng = random.Random(1)
Original_state = rng.getstate()
output = [rng.getrandbits(32)for i in range(624)]


RNG = random.Random()
def construct_a_row(RNG):
    row = []
    for i in range(len(output)):
         row += list(map(int, (bin(RNG.getrandbits(32))[2:].zfill(32))))
     return row

L = []
for i in trange(19968):
    state = [0]*624
    temp = "0"*i + "1"*1 + "0"*(19968-1-i)
    for j in range(624):
        state[j] = int(temp[32*j:32*j+32],2)
    RNG.setstate((3,tuple(state+[624]),None))
    L.append(construct_a_row(RNG))

L = Matrix(GF(2),L)
R = []
for i in range(len(output)):
   R += list(map(int, (bin(output[i])[2:].zfill(32))))


R = vector(GF(2),R)

s = L.solve_left(R)
init = "".join(list(map(str,s)))
state = []
for i in range(624):
    state.append(int(init[32*i:32*i+32],2))


assert (3,tuple(state+[624]),None) == Original_state

gf2bv

from sage.all import *
from gf2bv import LinearSystem
from gf2bv.crypto.mt import MT19937
import random

rng = random.Random(1)
Original_state = rng.getstate()
output = [rng.getrandbits(32)for i in range(624)]
lin = LinearSystem([32] * 624)
mt = lin.gens()

rng = MT19937(mt)
zeros = []
for o in output:
    zeros.append(rng.getrandbits(32) ^ int(o))
zeros.append(mt[0] ^ int(0x80000000))

sol = lin.solve_one(zeros)
rng = MT19937(sol)
pyrand = rng.to_python_random()

assert pyrand.getstate() == Original_state

state推seed(Python 的 random 库)

因为有很多师傅写了详细的逆向代码,我就在这里写一下我比较喜欢用的这个函数,可能比较方便点
详细的参考:
MT19937 | DexterJie'Blog
MT19937 - SeanDictionary | 折腾日记

from sage.all import *
from Crypto.Util.number import *
from tqdm import tqdm
from gf2bv import LinearSystem
from gf2bv.crypto.mt import MT19937
#https://github.com/Aeren1564/CTF_Library/blob/master/CTF_Library/Cryptography/MersenneTwister/python_random_breaker.py
from CTF_Library.Cryptography.MersenneTwister import python_random_breaker
import random

seed = random.randint(0, 2**32 - 1)
RNG = random.Random(seed)
# Original_state = RNG.getstate()

outs = []
for i in range(19968//2):
    outs.append(RNG.getrandbits(2))


lin = LinearSystem([32] * 624)
mt = lin.gens()
rng = MT19937(mt)
zeros = [mt[0] ^ int(0x80000000)]
for i in tqdm(range(len(outs))):
    zeros.append((rng.getrandbits(2)) ^ outs[i])
sol = lin.solve_one(zeros)
rng = MT19937(sol)
pyrand = rng.to_python_random()
mt_state = pyrand.getstate()

breaker = python_random_breaker()
recovered_seeds = breaker.recover_all_integer_seeds_from_state(mt_state, 32)


assert recovered_seeds[0] == seed

这里得到的state都是MT19937最初的state,所以可以直接恢复出seed,如果不是初始state需要untwist
举例:

from CTF_Library.Cryptography.MersenneTwister import python_random_breaker
import random

seed = random.getrandbits(128)
rng = random.Random(1)

rounds = 629
untwist_rounds = 1 + rounds//624

for i in range(rounds):
    rng.getrandbits(2)

dirty_state = rng.getstate()

breaker = python_random_breaker()
dirty_mt = list(dirty_state[1][:-1])
# clean_mt = [breaker.untemper(rng.getrandbits(32)) for _ in range(624)]
# print(clean_mt)

for i in range(untwist_rounds):
    dirty_mt = breaker.untwist(dirty_mt)

clean_state_tuple = (3, tuple(dirty_mt + [624]), None)

recovered_seeds = breaker.recover_all_integer_seeds_from_state(clean_state_tuple, 128)

print(f"破解结果: {recovered_seeds}")

few_outputs恢复seed

因为位数之间存在线性关系,所以并不一定要有19968bit

from CTF_Library.Cryptography.MersenneTwister import python_random_breaker
import random
def crackme(outputs):
    breaker = python_random_breaker()
    bit_len = 128
    is_exact = 1
    indices = [i for i in
    breaker.get_required_output_indices_for_integer_seed_recovery(bit_len,
    is_exact)]
    cur_outputs = [outputs[i] for i in indices]
    # print(cur_outputs)
    return (breaker.recover_all_integer_seeds_from_few_outputs(bit_len,cur_outputs, True))



if __name__ == "__main__":
    seed = random.getrandbits(128)
    rng = random.Random(seed)
    outputs = [rng.getrandbits(32) for i in range(240)]
    print(outputs)
    print(seed)
    print(crackme(outputs))

MT19937线性关系

MT19937中距离(1,397,624)的生成数存在线性关系,例如:mt[1], mt[397],mt[624]

Mx=0\mathbf{M} \cdot \mathbf{x} = \mathbf{0}

[u1u2u256]数据矩阵 M (256×63)[m1m2m9]Masks 矩阵 K (63×9)=[000000000]全零矩阵  (256×9)\underbrace{ \begin{bmatrix} \leftarrow & \mathbf{u}_1 & \rightarrow \\ \leftarrow & \mathbf{u}_2 & \rightarrow \\ \vdots & \vdots & \vdots \\ \leftarrow & \mathbf{u}_{256} & \rightarrow \end{bmatrix} }_{\text{数据矩阵 } \mathbf{M} \ (256 \times 63)} \cdot \underbrace{ \begin{bmatrix} \uparrow & \uparrow & & \uparrow \\ \mathbf{m}_1 & \mathbf{m}_2 & \dots & \mathbf{m}_9 \\ \downarrow & \downarrow & & \downarrow \end{bmatrix} }_{\text{Masks 矩阵 } \mathbf{K} \ (63 \times 9)} = \underbrace{ \begin{bmatrix} 0 & 0 & \dots & 0 \\ 0 & 0 & \dots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \dots & 0 \end{bmatrix} }_{\text{全零矩阵 } \ (256 \times 9)}

求mask

from sage.all import *
from random import getrandbits

# 初始化有限域 GF(2)
F = GF(2)
m = []

print("[+] Collecting data to find linear dependencies...")

# 1. 采集数据
for _ in range(256):
    # 模拟生成随机数流
    x = [getrandbits(21) for _ in range(1337)]

    # 提取 MT19937 关键位置的数:idx 1, 397, 624
    # 将它们拼接成二进制字符串
    a = "".join(map("{:021b}".format, [x[1], x[397], x[624]]))

    # 转换为 GF(2) 向量并存入列表
    m.append(list(map(F, a)))

# 2. 构建矩阵并计算右核 (Right Kernel)
m = matrix(F, m)
ker = m.right_kernel_matrix()

print(f"[+] Kernel dimensions: {ker.dimensions()}")

# 验证计算出的核是否正确
for row in ker:
    assert all(row * k == 0 for k in m)

# 3. 提取并转换 Masks
# 将 GF(2) 向量转回整数形式
masks = sorted([int("".join(map(str, row)), 2) for row in ker])

print("\n[+] FOUND MASKS (Copy these to your Python script):")
print(f"masks = {masks}")

# 4. 再次验证 (Optional)
print("\n[+] Verifying masks on new data...")
for _ in range(100):
    x = [getrandbits(21) for _ in range(1337)]
    # 拼接校验位
    u = (x[1] << 42) | (x[397] << 21) | x[624]
    for mk in masks:
        # 验证点积是否为 0 (偶数个1)
        assert bin(u & mk).count('1') & 1 == 0
print("[+] Verification successful.")

我们可以通过这个性质来做Decision,判断是不是MT19937生成的数

例题:

for tests in range(1024):
    is_mt_source = (tests % 2 == 0)
    if is_mt_source:
        a = [randrange(q) for i in range(2062)]
    else:
        a = [randbelow(q) for i in range(2062)]

注意:MT的randrange(q),会遇到拒绝采样
例如:
q=1342261q = 1342261,而 221=20971522^{21} = 2097152,接受率 Paccept1.34×1062.09×1060.64P_{accept} \approx \frac{1.34 \times 10^6}{2.09 \times 10^6} \approx 0.64
所以只需要244(3970.64)400(6240.64)244(397*0.64)和400(624*0.64)在这两处一定范围内爆破,就能找到真正的mt[1], mt[397],mt[624]

from random import randrange
from secrets import randbelow
from math import ceil, floor

# 题目给定的模数
q = 1342261

# Mask 指纹 (来自 SageMath)
masks = [673178275939392, 4646537255715348, 9084442103645188,
         36451018107523088, 144115772462219393, 576461989258788866,
         1153168072270545936, 2305913927730724872, 4902405758213194009]

# --- 【新增配置】 设定分界线 ---
THRESHOLD = 450000

# 用于存储得分
c = [[] for _ in range(2)]

# 用于统计正确率
correct_predictions = 0
total_rounds = 1024

print(f"[+] Starting {total_rounds} rounds of distinguishing tests...")
print(f"    Target Modulus: {q}")
print(f"    Decision Threshold: {THRESHOLD}")

for tests in range(total_rounds):
    # --- 1. 黑盒数据生成 ---
    # 偶数轮 (tests % 2 == 0): MT19937 (Target)
    # 奇数轮 (tests % 2 == 1): Secure Random (Reference)
    is_mt_source = (tests % 2 == 0)

    if is_mt_source:
        a = [randrange(q) for i in range(2062)]
    else:
        a = [randbelow(q) for i in range(2062)]

    # --- 2. 核心评分逻辑 ---
    count = 0
    for i in range(1600):
        # 1. 计算 m1
        m1 = sum([(((a[i] << 42) & m).bit_count() & 1) << idx for idx, m in enumerate(masks)])

        t = dict()
        # 2. 搜索 m2
        for j in range(-18, 19):
            m2 = sum([(((a[i + 254 + j] << 21) & m).bit_count() & 1) << idx for idx, m in enumerate(masks)])
            t[m1 ^ m2] = j

        # 3. 搜索 m3 并打分
        for k in range(-24, 25):
            m3 = sum([((a[i + 400 + k] & m).bit_count() & 1) << idx for idx, m in enumerate(masks)])
            if m3 in t:
                # 命中特征!
                count += 100 - abs(t[m3]) - abs(k)

    # --- 3. 判定与统计 ---

    # 判定逻辑:分数高判定为 MT,分数低判定为 Secure
    predicted_is_mt = (count > THRESHOLD)

    # 检查预测是否正确
    if predicted_is_mt == is_mt_source:
        correct_predictions += 1

    # 记录分数用于分布展示
    c[tests & 1].append(count)
    c[0] = sorted(c[0])
    c[1] = sorted(c[1])

    # --- 4. 实时输出 ---
    if (tests + 1) % 100 == 0:
        current_acc = (correct_predictions / (tests + 1)) * 100
        print(f"\n--- Round {tests + 1} / {total_rounds} ---")
        print(f"Current Accuracy: {current_acc:.2f}%  (Threshold: {THRESHOLD})")

        # 打印分数分布,方便你微调 Threshold
        if len(c[1]) > 0:
            print("Secure Scores (Max 5):", c[1][-5:])  # 看看真随机最高得几分
        if len(c[0]) > 0:
            print("MT Scores (Min 5):    ", c[0][:5])  # 看看MT最低得几分

# --- 最终结果 ---
final_acc = (correct_predictions / total_rounds) * 100
print(f"\n[+] Test Finished.")
print(f"[+] Final Accuracy: {final_acc:.2f}%")
print(f"    Correctly Classified: {correct_predictions}/{total_rounds}")

# 最终验证分界线是否合理
max_secure = c[1][-1] if c[1] else 0
min_mt = c[0][0] if c[0] else 0
print(f"\n[Analysis]")
print(f"Max Secure Score: {max_secure}")
print(f"Min MT Score:     {min_mt}")
if max_secure < THRESHOLD < min_mt:
    print(f"Result: The threshold {THRESHOLD} is PERFECT (Separates classes completely).")
else:
    print(f"Result: The threshold {THRESHOLD} might have errors. Suggestion: Set it between {max_secure} and {min_mt}.")

不能保证每次正确率都是百分之百,但是大体没错,做题的时候应该不会卡太死,count和THRESHOLD很接近的可以舍去