返回
10
0

LCG-RSA的强强联手 WP - 懒羊羊是菜鸡

懒羊羊是菜鸡,2026-06-02 21:14
Markdown
# LCG-RSA的强强联手 — Writeup ## 题目信息 - **题目名称**:LCG-RSA的强强联手 - **题目类型**:Crypto / LCG 参数恢复 + RSA - **Flag 格式**`LHFLAG{...}` ## 题目描述 一次调试日志意外泄露了 LCG 的连续 10 个输出。服务端用同一 LCG 的后续状态生成 RSA 的两个素数 `p``q`,对 flag 进行了加密。已知 `task.py``output.txt`,恢复 flag。 ## 附件要点 ### task.py(核心逻辑) ```python class LCG: def __init__(self, m, a, b, seed): self.m, self.a, self.b, self.x = m, a, b, seed def next(self): self.x = (self.a * self.x + self.b) % self.m return self.x rng = LCG(M, A, B, SEED) leak = [rng.next() for _ in range(10)] # 10 个连续输出泄露 p = next_prime(rng.next()) # 第 11 个输出 → p q = next_prime(rng.next()) # 第 12 个输出 → q n = p * q; e = 65537 c = pow(int.from_bytes(FLAG, "big"), e, n) ``` ### output.txt ``` leak = [10 个 ~512-bit 整数] n, e, c 已知 ``` ## 解题思路 ### 1. 恢复 LCG 模数 m LCG 迭代公式: $$x_{n+1} = a \cdot x_n + b \pmod{m}$$ 定义差分序列: $$t_n = x_{n+1} - x_n$$ 则: $$t_{n+1} = x_{n+2} - x_{n+1} = a(x_{n+1} - x_n) = a \cdot t_n \pmod{m}$$ 因此: $$t_{n+1} \cdot t_{n-1} - t_n^2 \equiv a^2 \cdot t_{n-1}^2 - a^2 \cdot t_{n-1}^2 \equiv 0 \pmod{m}$$ 即 $|t_{n+1}t_{n-1} - t_n^2|$ 是 $m$ 的整数倍。 对多个 $n$ 计算该值并取 GCD,即可恢复 $m$: ```python t = [leak[i+1] - leak[i] for i in range(len(leak) - 1)] multiples = [abs(t[i+1] * t[i-1] - t[i]**2) for i in range(1, len(t) - 1)] m = multiples[0] for u in multiples[1:]: m = gcd(m, u) ``` ### 2. 恢复 a 和 b 由 $t_1 \equiv a \cdot t_0 \pmod{m}$: $$a = t_1 \cdot t_0^{-1} \pmod{m}$$ 由 $x_1 = a \cdot x_0 + b \pmod{m}$: $$b = x_1 - a \cdot x_0 \pmod{m}$$ ### 3. 恢复 seed 并复现 PRNG 由 $\text{leak}[0] = a \cdot \text{seed} + b \pmod{m}$: $$\text{seed} = (\text{leak}[0] - b) \cdot a^{-1} \pmod{m}$$ 用恢复的 `(m, a, b, seed)` 重建 LCG 并验证 leak 完全一致。 ### 4. 生成 p、q 的 LCG 基础值 ```python rng = LCG(m, a, b, seed) _ = [rng.next() for _ in range(10)] # 跳过已泄露的 lcg_p = rng.next() # 第 11 个输出 lcg_q = rng.next() # 第 12 个输出 ``` ### 5. 爆破 next_prime 偏移 $p = \text{next\_prime}(lcg\_p)$ 即 lcg_p 之后的下一个素数。对于 ~512-bit 的大数,素数间隙极小,可直接从 lcg_p 开始递增搜索: ```python p = lcg_p | 1 # 确保是奇数 while n % p != 0: p += 2 q = n // p ``` 实际偏移仅 **124**(即 lcg_p 之后第 62 个奇数)。 ### 6. RSA 解密 ```python phi = (p - 1) * (q - 1) d = pow(e, -1, phi) flag = pow(c, d, n) ``` ## 完整攻击代码 ```python from math import gcd leak = [...] # 10 known outputs n, e, c = ... # from output.txt # Step 1: Recover m t = [leak[i+1] - leak[i] for i in range(9)] multiples = [abs(t[i+1] * t[i-1] - t[i]**2) for i in range(1, 8)] m = multiples[0] for u in multiples[1:]: m = gcd(m, u) # Step 2: Recover a, b a = t[1] * pow(t[0], -1, m) % m b = (leak[1] - a * leak[0]) % m # Step 3: Recover seed seed = (leak[0] - b) * pow(a, -1, m) % m # Step 4: Replay LCG class LCG: def __init__(self, m, a, b, x): self.m, self.a, self.b, self.x = m, a, b, x def next(self): self.x = (self.a * self.x + self.b) % self.m return self.x rng = LCG(m, a, b, seed) _ = [rng.next() for _ in range(10)] # Step 5: Find p lcg_p = rng.next() p = lcg_p | 1 while n % p != 0: p += 2 q = n // p # Step 6: Decrypt phi = (p-1) * (q-1) d = pow(e, -1, phi) flag = pow(c, d, n).to_bytes(200, 'big').lstrip(b'\x00') ``` ## 结果 | 参数 | 值 | |------|-----| | LCG 模数 m | 512-bit | | p 偏移 | offset = 124(从 LCG 输出到素数) | | seed | 成功恢复,leak 验证通过 | ## Flag ``` LHFLAG{lcg_rsa_predictor_9f3a1c7e5b2d} ``` ## 总结 | 要点 | 说明 | |------|------| | **漏洞 1** | LCG 线性结构导致模数可从连续输出中恢复 | | **漏洞 2** | `next_prime` 偏移极小,可直接爆破 | | **攻击链** | leak → LCG 参数恢复 → 预测 p,q → RSA 解密 | | **防御建议** | 使用密码学安全的 CSPRNG(如 `secrets` 模块),不要用 LCG 生成密钥材料 |
暂无回复。你的想法是什么?