第 16 章 CTF 逆向实战模式
CTF 逆向是逆向技术的"练武场":题目刻意设计,模式高度重复。本仓库沉淀了 60+ 种实战模式,本章按题型整理最核心的套路。
📍 知识点地图 | 主题:CTF逆向模式 | 前置:第1-9章 | 后续:第51章 | 核心概念:模式库、诱饵flag、内存转储、angr速攻
16.1 CTF 逆向题型地图
| 题型 | 核心套路 | 快速通道 |
|---|---|---|
| flag 校验 | 找比较点 → 反推 | angr / 内存转储 |
| 自定义 VM | 还原指令集 → 解释器重写 | Unicorn/angr/人工 |
| 加壳/自解密 | dump 内存 → 分析解密后 | Frida/Unicorn |
| 反调试 | 识别 → 绕过(第 8 章) | LD_PRELOAD/Qiling |
| 算法还原 | 识别模式 → 脚本反推(第 9 章) | Z3/Ciphey |
| 移动端 | APK/iOS 专项(第 10-11 章) | jadx/Frida |
| 固件/硬件 | 提取仿真(第 15 章) | binwalk/QEMU |
| 隐写组合 | 提取 → 变换 → OCR | 多工具链 |
16.2 万能解题流程(逆向题默认流程)
1. file + checksec + strings(快速捡漏)
2. 运行观察(ltrace/strace/直接跑带输入)
3. 静态分析定位主逻辑
4. 找比较点 → 决定反推策略
5. 写脚本求解(Python/angr/Z3)
6. 验证 flag
16.3 高频模式速查(本仓库精选)
1. 内存转储拿 flag(最常用)
思路: 让程序自己算出答案,然后在最终比较处 dump
b *main+OFFSET → 输入任意长度正确的输入 → x/s $rsi
2. 诱饵 flag
多个假目标排前面。找最终成功提示的引用点,在最终比较断点。
3. 比较方向判定
transform(flag) == stored → 反向变换 stored
transform(stored) == flag → flag 就是变换结果,正向套用
4. 已知明文 XOR
flag{ 开头 → 用前 5 字节推 XOR key。
5. 隐藏模拟器 opcode(0xFun 案例)
特征: 程序像模拟器, 但部分 opcode 未公开
对策: 用 fuzz/暴力枚举 opcode → 观察哪个"没反应"→ 那就是隐藏功能
或 LD_PRELOAD 拦截关键调用观察
6. 多阶段加载器
RC4 门 + VM 约束 / 两阶段自解密
对策: 逐阶段 dump(Unicorn hook 解密点)/ 找到 key 逐层解
7. 指令计数器作为加密状态
每个字节的变换依赖之前所有指令(状态路径相关)
对策: 逐字节模拟(Unicorn 从输入开头跑到当前位置)
8. 死分支反分析
cmp [x], 0x1; je real # 恒假
对策: patch 比较值 0x1 → 0x0(字节 83 7d f4 01 → 83 7d f4 00)
9. 键盘 LED 摩斯码(ioctl)
ioctl 控制 LED 闪烁 → 抓 ioctl 调用时序 → 解码摩斯
10. 决策树混淆
大量 if-else 树函数, 每个叶子一个常量
对策: 提取所有比较常量 → 组合还原 → 用 z3/约束求解
11. 线程竞态利用
多线程共享状态 + 整数溢出 → 篡改关键值
对策: 理解线程同步窗口 → 构造触发序列
12. 数学收敛位图
程序"计算"flag → 渲染成位图 → OCR
对策: 复现计算 → 渲染 → 识别
16.4 冷门语言/格式速查(出现即秒杀)
| 目标 | 对策 |
|---|---|
| Python 字节码 | uncompyle6 / pycdc / dis 手动 |
| WASM | wasm2c / wabt / Ghidra |
| Brainfuck | 特征分析:输入读取点切段,逐段反推 |
| RISC-V | qemu-riscv64-static + gdb-multiarch |
| BPF | 开启 JIT → 内核日志拿 JIT 后的 x64 汇编 |
| TensorFlow 模型 | 逆神经网络(sigmoid 层求逆) |
| 字体/图片隐写 | fonttools ttx / 像素提取 |
| GLSL shader VM | 跟踪 VRAM 写入 |
| ESP32/Xtensa | 用 ROM 符号表(esp32.rom.ld) |
| MBR/引导 | QEMU -S 停住 + GDB 0x7c00 |
16.5 angr 速攻法(能被自动解的就不手解)
import angr
p = angr.Project("./checker", auto_load_libs=False)
s = p.factory.entry_state()
sm = p.factory.simulation_manager(s)
sm.explore(find=0x401234, avoid=0x401111)
print(sm.found[0].posix.dumps(0))
适用判断:输入 → 纯逻辑校验 → 输出对错。有系统调用/随机/复杂反调试时 angr 会难,换 Qiling/人工。
16.6 边界情况(题目设计者最爱)
□ 输入长度检查严格 → 先确定长度再爆破
□ flag 格式约束(flag{...})→ 用作过滤条件
□ 多解/无解 → 用可打印字符 + 格式约束缩小
□ 大小端陷阱 → 对比字节序
□ 符号扩展坑 → 8 位/32 位运算扩展
□ 循环边界状态更新 → 逐轮推演
16.7 完整实战演示:一道题的端到端解题
以一道综合了"反调试 + XOR + 查表 + 比较"的 CTF 逆向题为蓝本,完整走一遍解题流程。
题目:baby_rev(ELF 64 位,flag 格式 flag{...},32 字节)
第 1 步:侦察与快速捡漏(2 分钟)
$ file baby_rev
baby_rev: ELF 64-bit LSB executable, x86-64, not stripped
$ strings baby_rev | grep -iE "flag|key|secret"
"Wrong answer!"
"Correct!"
# 没直接找到 flag 明文,但看到:
$ strings baby_rev | grep -i "flag{" # 空 → flag 需要算
第 2 步:运行观察(2 分钟)
$ ltrace ./baby_rev
ptrace(0, 0, 0, 0) = -1 # ← 反调试!返回 -1(正常程序 ptrace 会成功)
puts("Wrong answer!") = 14 # 输入任意字符直接错
发现反调试:程序检测到被跟踪(ltrace 也是 ptrace 家族),直接走失败分支。
第 3 步:绕过反调试(5 分钟)
# 方案 A:直接运行(不跟踪),程序正常接受输入
$ ./baby_rev
input> AAAA
Wrong answer!
# 方案 B:Qiling 模拟,ptrace 永远返回 0
$ python3
>>> from qiling import Qiling
>>> ql = Qiling(["./baby_rev"], rootfs="./rootfs")
>>> ql.set_syscall("ptrace", lambda ql, *a: 0) # 骗过反调试
>>> ql.run() # 程序以为没被调试,正常跑
第 4 步:静态分析主逻辑(15 分钟)
$ r2 -A baby_rev
[0x...]> afl
... 0x00400780 5 22 sym.func_check # 可疑
... 0x00400810 1 42 sym.func_encrypt # 更可疑!
[0x...]> s sym.func_encrypt; pdf
反汇编看到的加密逻辑(用 Ghidra 出伪代码更直观):
void func_encrypt(char *in, char *out) {
for (int i = 0; i < 32; i++) {
out[i] = SBOX[in[i] ^ 0x42 ^ i]; // XOR + 查表
}
}
// main 里: func_encrypt(input, buf); memcmp(buf, cipher, 32)
第 5 步:从 .rodata 提取数据并写脚本(10 分钟)
# 提取 SBOX(256 字节)和 cipher(32 字节)
$ r2 -q -c 'px 256 @ 0x4020a0' baby_rev > sbox.txt
$ r2 -q -c 'px 32 @ 0x4021a0' baby_rev > cipher.txt
# solve.py
SBOX = bytes.fromhex("...") # 从 sbox.txt 解析
cipher = bytes.fromhex("...") # 从 cipher.txt 解析
rev = [0] * 256
for i in range(256):
rev[SBOX[i]] = i
flag = bytearray()
for i in range(32):
c = rev[cipher[i]] # 反向查表
c ^= i # 反向位置 XOR
c ^= 0x42 # 反向 XOR
flag.append(c)
print(flag.decode()) # flag{c0ngr4t5_y0u_51mpl1fy_7h15}
第 6 步:验证(1 分钟)
$ ./baby_rev
input> flag{c0ngr4t5_y0u_51mpl1fy_7h15}
Correct!
本题复盘(可迁移到所有题)
| 步骤 | 用时 | 复用价值 |
|---|---|---|
| strings 捡漏 | 2min | 永远第一步 |
| ltrace 观察 | 2min | 同时暴露反调试和错误分支 |
| Qiling 反调试绕过 | 5min | 不 patch 二进制的最优雅绕过 |
| 静态识别 4 层变换 | 15min | XOR→查表→比较是 CTF 最主流组合 |
| 脚本反推 | 10min | 层序倒着解,逐层验证 |
| 验证 | 1min | 成功提示是最权威的证据 |
动手练习
- 从 pwn.college / CTFtime 找一个 pwnable.kr 或简单 reverse 题,用第 16.2 流程完整解一道。
- 练习"诱饵 flag"识别:构造两个比较点,找到正确的那个。
- 用 angr 自动解 3 个不同的 flag 校验程序。
- 复盘一次:你解题的每一步对应本章哪个模式?
深入阅读
- 仓库:
skills/reverse-engineering/patterns.md(基础模式库:VM、Nanomites、自修改、S-Box、SECCOMP) - 仓库:
skills/reverse-engineering/patterns-ctf.md(Part 1:模拟器 opcode、SPN、图像 XOR、RC4+VM) - 仓库:
skills/reverse-engineering/patterns-ctf-2.md(Part 2:自解密、ZIP+XOR、决策树、GF(2^8)) - 仓库:
skills/reverse-engineering/patterns-ctf-3.md(Part 3:Z3 电路、LED 摩斯、字体利用、BPF JIT) - 仓库:
skills/reverse-engineering/field-notes.md(CTF Case Notes 全量) - 仓库:
CTF-Sandbox-Orchestrator/(40+ CTF 子技能,比赛时用总控路由)