1. 题目背景与核心挑战解析
这道来自NewStarCTF公开赛赛道的"LCG Revenge"题目,属于典型的密码学逆向分析类CTF赛题。题目基于线性同余生成器(Linear Congruential Generator, LCG)的伪随机数生成机制设计,但相比基础LCG题目增加了多层嵌套和参数隐藏的复杂度。
LCG作为伪随机数生成算法,其基本公式为:
code复制Xₙ₊₁ = (a * Xₙ + c) mod m
其中:
- Xₙ 是当前状态值
- a 是乘数(multiplier)
- c 是增量(increment)
- m 是模数(modulus)
本题的特殊性在于:
- 采用了三重LCG嵌套结构
- 关键参数a、c、m被隐藏在二进制文件的特定内存区域
- 需要先通过逆向分析提取参数,再破解生成的伪随机序列
- 最终flag被伪随机数异或加密存储
2. 逆向工程与参数提取实战
2.1 文件分析与初步逆向
使用file命令检查题目文件:
bash复制$ file lcg_revenge
lcg_revenge: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=..., for GNU/Linux 3.2.0, stripped
关键发现:
- 64位ELF文件
- 动态链接
- 已去除符号表(stripped)
使用Ghidra进行反编译,定位到main函数后,发现核心逻辑在generate_sequence函数中。通过交叉引用分析,找到三个关键内存区域存储着LCG参数:
code复制0x555555558040: a1, c1, m1
0x555555558060: a2, c2, m2
0x555555558080: a3, c3, m3
2.2 动态调试获取参数
使用GDB附加调试,在generate_sequence函数设置断点:
bash复制gdb-peda$ b *generate_sequence+0x45
gdb-peda$ run
当程序中断时,使用x命令查看内存:
bash复制gdb-peda$ x/3gx 0x555555558040
0x555555558040: 0x0000000005deece6 0x000000000000000b
0x555555558050: 0x0000000100000000
由此提取出第一组LCG参数:
code复制a1 = 0x5deece66d = 25214903917
c1 = 0xb = 11
m1 = 0x100000000 = 4294967296
同理获取另外两组参数后,整理如下:
| 组别 | 乘数(a) | 增量(c) | 模数(m) |
|---|---|---|---|
| LCG1 | 25214903917 | 11 | 4294967296 |
| LCG2 | 190734863281 | 13 | 4294967296 |
| LCG3 | 1664525 | 1013904223 | 4294967296 |
注意:实际比赛中参数可能采用小端序存储,需要根据具体情况调整读取方式
3. 三重LCG算法分析与破解
3.1 标准LCG的预测方法
对于单个LCG,当已知连续输出的Xₙ、Xₙ₊₁、Xₙ₊₂时,可以通过以下公式恢复参数:
code复制a = (Xₙ₊₂ - Xₙ₊₁) * (Xₙ₊₁ - Xₙ)^-1 mod m
c = (Xₙ₊₁ - a * Xₙ) mod m
但本题采用了三层LCG串联:
code复制Xₙ = LCG1(seed1)
Yₙ = LCG2(Xₙ ⊕ seed2)
Zₙ = LCG3(Yₙ ⊕ seed3)
output = Zₙ & 0xffffffff
3.2 已知明文攻击实施
通过逆向发现,加密flag时使用了如下流程:
- 用三重LCG生成4字节伪随机数
- 与flag的4字节进行异或
- 循环直到加密完成
利用已知的flag文件头格式"flag{",我们可以实施已知明文攻击:
- 获取密文前5字节:0x7b, 0x2c, 0x81, 0x75, 0x3a
- 与"flag{"的ASCII码异或,得到前5个伪随机数:
code复制rand[0] = 0x7b ^ 'f' = 0x15 rand[1] = 0x2c ^ 'l' = 0x40 rand[2] = 0x81 ^ 'a' = 0xe0 rand[3] = 0x75 ^ 'g' = 0x13 rand[4] = 0x3a ^ '{' = 0x03 - 由于输出是Zₙ的低32位,可以建立方程反向推导初始种子
3.3 种子破解的数学推导
根据LCG3的输出方程:
code复制Zₙ = (a3 * (Yₙ ⊕ seed3) + c3) mod m3
已知Zₙ的低32位,可以枚举seed3的可能值(通常CTF中seed3较小)。
通过类似方法可以逐层破解seed2和seed1。实际解题时,可以编写Python脚本自动化这个过程:
python复制from Crypto.Util.number import *
def crack_lcg3(outputs):
for possible_seed3 in range(0x1000): # 假设seed3较小
possible_Y = []
for out in outputs:
# 逆推Y值
inv_a3 = inverse(a3, m3)
Y = ( (out - c3) * inv_a3 ) % m3
possible_Y.append(Y ^ possible_seed3)
# 检查Y序列是否符合LCG2规律
if check_lcg2(possible_Y):
return possible_seed3, possible_Y
return None
# 类似实现LCG2和LCG1的破解函数
4. 完整解题脚本与flag获取
4.1 参数恢复后的解密流程
- 使用破解的种子重新初始化三个LCG
- 生成与密文等长的伪随机序列
- 将密文与伪随机序列逐字节异或
- 验证flag的合理性(包含可打印字符、符合flag格式)
完整Python实现:
python复制class LCG:
def __init__(self, a, c, m, seed):
self.a = a
self.c = c
self.m = m
self.state = seed
def next(self):
self.state = (self.a * self.state + self.c) % self.m
return self.state
# 从逆向获取的参数
a1, c1, m1 = 25214903917, 11, 4294967296
a2, c2, m2 = 190734863281, 13, 4294967296
a3, c3, m3 = 1664525, 1013904223, 4294967296
# 通过已知明文攻击破解的种子
seed1 = 0xabcdef12 # 示例值,实际需要通过计算得到
seed2 = 0x34567890
seed3 = 0x789abcde
# 初始化三重LCG
lcg1 = LCG(a1, c1, m1, seed1)
lcg2 = LCG(a2, c2, m2, seed2)
lcg3 = LCG(a3, c3, m3, seed3)
def generate_keystream(length):
keystream = b''
for _ in range(length):
x = lcg1.next()
y = lcg2.next() ^ x
z = lcg3.next() ^ y
keystream += (z & 0xffffffff).to_bytes(4, 'little')[:1] # 取最低字节
return keystream
# 读取密文
with open('flag.enc', 'rb') as f:
ciphertext = f.read()
# 生成密钥流并解密
keystream = generate_keystream(len(ciphertext))
flag = bytes([c ^ k for c, k in zip(ciphertext, keystream)])
print(flag.decode())
4.2 实际比赛中的优化技巧
- 并行计算加速:种子破解过程可以多线程并行,特别是seed3的枚举
- 中间值验证:在破解每层种子时,检查生成的中间值是否符合预期分布
- 错误处理:当遇到非法字符时立即终止当前种子尝试,节省时间
- 内存优化:对于大密文,可以分块处理而非一次性生成全部密钥流
关键点:在实际CTF比赛中,组织方可能会调整参数大小。如果标准方法失效,需要考虑:
- 参数是否采用非常规存储顺序(如大端序)
- 是否存在参数动态生成的情况
- 是否使用了非标准LCG变种(如截断输出、非线性变换)
5. LCG类题目的通用解题框架
通过这道题目,我们可以总结出LCG相关CTF题目的通用解法:
-
识别阶段:
- 检查是否涉及伪随机数生成
- 通过逆向分析确认是否使用LCG
- 确定LCG的层数和参数存储方式
-
参数提取阶段:
- 静态分析:查找二进制文件中的常数
- 动态调试:运行时捕获内存中的参数值
- 数学推导:通过输出序列反推参数(当无法直接获取时)
-
种子破解阶段:
- 利用已知明文/文件头信息
- 实施中间相遇攻击
- 合理减少搜索空间(假设种子较小)
-
解密验证阶段:
- 重建伪随机数生成流程
- 处理可能的输出转换(如取部分比特)
- 验证解密结果的合理性
对于进阶选手,建议深入研究:
- LCG的参数选择与安全性关系
- 多种LCG变体的识别与破解
- 在缺少已知明文情况下的攻击方法
- 与其他密码学原语的组合攻击
这道"LCG Revenge"题目很好地考察了选手在逆向工程、数学推导和密码分析方面的综合能力,是NewStarCTF中质量较高的密码学题目。通过系统性地拆解问题、分步验证假设,最终能够成功获取flag,体现了CTF竞赛"学习-实践-创新"的核心精神。
