逆向工程实战手册 第 16 章

第 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 成功提示是最权威的证据

动手练习

  1. 从 pwn.college / CTFtime 找一个 pwnable.kr 或简单 reverse 题,用第 16.2 流程完整解一道。
  2. 练习"诱饵 flag"识别:构造两个比较点,找到正确的那个。
  3. 用 angr 自动解 3 个不同的 flag 校验程序。
  4. 复盘一次:你解题的每一步对应本章哪个模式?

深入阅读

  • 仓库: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 子技能,比赛时用总控路由)