---
name: re-fuzzing
description: >
  覆盖率引导模糊测试：AFL++/libFuzzer/honggfuzz、插桩与语料初始化、
  afl-fuzz 运行参数、覆盖率(afl-cov)、字典/结构化输入。
  触发词：fuzz、模糊测试、AFL、afl-fuzz、libFuzzer、honggfuzz、
  覆盖率、corpus、语料、dictionary、挖洞、fuzzing。
capabilities: [fuzzing]
---

# 覆盖率引导模糊测试（AFL++ / libFuzzer / honggfuzz）

## 何时使用 / 何时不用

- 用：目标有明确输入面（文件解析 / 库函数 / 网络协议解析），要自动化找崩溃；有源码可插桩（或无源码愿意用 QEMU 模式）；需要持续回归找新 bug
- 不用：目标无输入解析（纯算法/纯逻辑 → 符号执行 [[re-angr]] / [[re-z3]]）；已有崩溃只需分析（→ [[re-crash-triage]]）；目标是内核/驱动（走 [[re-kernel]] 域思路，不在用户态 fuzz 范围）
- 不用：只有单个崩溃样本要复现（直接走 [[re-crash-triage]]，无需重跑 fuzz）

## 工具准备

所有工具先验证再使用。fuzz 是动态执行，一律在沙箱内跑（[[re-analyze/platform-tips]] 最高原则，见 [[re-sandbox]]）。

### AFL++ —— 覆盖率引导 fuzz 主力

- Debian/Ubuntu: `sudo apt install afl++`
- Fedora: `sudo dnf install american-fuzzy-lop american-fuzzy-lop-clang`（包即 AFL++ fork）；或源码编译拿最新版（见下）
- Arch: `sudo pacman -S afl++`
- macOS: `brew install afl++`
- Windows: WSL2 内 `sudo apt install afl++`（AFL++ 官方支持 WSL；Windows 本机不可直接跑）
- 源码编译（推荐，版本最新、含全部模式）:
  ```sh
  git clone https://github.com/AFLplusplus/AFLplusplus && cd AFLplusplus
  make distrib        # 需要 clang/LLVM 工具链
  sudo make install
  ```
- 验证: `afl-fuzz -h`（打印 usage 即 OK）；`afl-clang-fast --version`

### clang / LLVM —— libFuzzer 宿主 + ASAN 编译器

- Debian/Ubuntu: `sudo apt install clang`
- Fedora/RHEL: `sudo dnf install clang`
- Arch: `sudo pacman -S clang`
- macOS: Xcode 自带（`xcode-select --install` 补命令行工具），或 `brew install llvm`
- Windows: WSL2 内 Linux 版；本机 Visual Studio 的 clang-cl 亦可（MSVC 支持 `/fsanitize=fuzzer`）
- 验证: `clang --version`

### libFuzzer —— 库函数 / 单函数 fuzz（随 clang 附带）

- 无需单独安装：clang 自带，`-fsanitize=fuzzer` 即启用
- 验证:
  ```sh
  clang -fsanitize=fuzzer -x c /dev/null -o /tmp/fztest && /tmp/fztest -runs=1
  ```
  输出含 `INFO: libFuzzer` 即 OK（`/tmp/fztest` 用完可删）

### honggfuzz —— 硬件计数器 / 多线程 fuzz 备选

- Debian/Ubuntu: 无官方包 → 源码编译：
  ```sh
  sudo apt install build-essential binutils-dev libunwind-dev libblocksruntime-dev clang
  git clone https://github.com/google/honggfuzz && cd honggfuzz && make
  ```
- Fedora/RHEL: `sudo dnf install honggfuzz`
- Arch: `sudo pacman -S honggfuzz`（或 AUR 包 `honggfuzz`）
- macOS: 无 brew 公式 → 源码编译（需 Xcode + libblocksruntime）
- Windows: WSL2 内 Linux 版
- 验证: `honggfuzz --help`

### afl-cov —— 覆盖率统计（gcov/lcov 前端）

- 依赖 lcov/gcov: `sudo apt install lcov` / `sudo dnf install lcov` / `sudo pacman -S lcov`（gcov 随 gcc 自带）
- 本体无 pip 包，git clone 直接运行:
  ```sh
  git clone https://github.com/mrash/afl-cov && cd afl-cov
  ```
- 验证: `./afl-cov -V`
- 注意：afl-cov 需要目标用 gcov 插桩编译（`gcc -fprofile-arcs -ftest-coverage`），与 AFL 插桩不兼容——**同一份代码编两次**：一次 afl+ASAN 版跑 fuzz，一次 gcov 版测覆盖率

## 操作步骤

按顺序执行；每步产物（harness 源码、语料目录、fuzz 输出目录）记录路径，供 [[re-crash-triage]] / 报告引用。

1. **目标与插桩选择**：
   - 源码可编译 → AFL++ 编译器包装器 + ASAN：
     ```sh
     CC=afl-clang-fast CXX=afl-clang-fast++ AFL_USE_ASAN=1 ./configure && make
     # 简单单文件目标：
     afl-clang-fast -fsanitize=address -g -O1 -o target target.c
     ```
   - 无源码 → `-Q` QEMU 模式（`afl-fuzz -Q ...`），无需插桩但慢 2-5 倍
   - 目标是库函数 → 写 fuzz target（harness）读文件喂解析 API：
     ```c
     /* fuzz_target.c —— 以 libpng 的 png_read 入口为例 */
     #include <stdint.h>
     extern int my_parse(const char *data, size_t len); /* 目标解析入口 */
     int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
       my_parse((const char *)data, size);
       return 0;
     }
     ```
     libFuzzer 编译：`clang -fsanitize=fuzzer,address -g -O1 -o fuzz_target fuzz_target.c target_lib.c`
     AFL++ 编译同一 harness：`afl-clang-fast -fsanitize=fuzzer,address -o fuzz_target_afl fuzz_target.c`（afl-clang-fast 拦截 `-fsanitize=fuzzer` 并链接自带 driver），运行 `afl-fuzz -i in -o out -- ./fuzz_target_afl @@`
   - 网络目标 → 见坑 5：harness 直调解析函数（数据来自文件），不要 fuzz 整个服务进程
2. **语料初始化**：
   ```sh
   mkdir in out
   # 每个种子是目标可正常解析的最小有效输入（别拿空文件起步）
   cp seed_sample.bin in/
   afl-cmin -i in -o in_min -- ./target @@   # 去重：只留覆盖不同路径的种子
   ```
   - 语料质量比数量重要：10 个覆盖不同解析分支的小样本 > 1000 个同类大文件（见坑 2）
   - 空语料/纯空文件起步：前几千 execs 全在 EOF 分支，解析逻辑迟迟不被覆盖
3. **afl-fuzz 运行参数**：
   ```sh
   afl-fuzz -i in_min -o out -m none -t 1000+ -x dict.txt -- ./target @@
   ```
   - `-m none`：ASAN 程序默认内存限制会误杀，必加（见坑 4）
   - `-t 1000+`：初始超时给足（`+` 号表示随路径增长自适应）
   - `@@` 占位符 = fuzz 生成的文件路径；输入走 stdin 则去掉 `@@`
   - 多核多实例：`-M main` 主实例 + `-S secondary1` 等从实例（共享同一 out 目录）；`afl-whatsup out` 看汇总
   - 续跑：`afl-fuzz -i - -o out ...`（`-i -` 复用已有输出目录）
   - 运行时长：24h+ 才有统计意义，别早期就停；出现崩溃后不打断，另开分析（[[re-crash-triage]] 可用 `afl-tmin` 在 out/crashes 上工作）
4. **覆盖率（afl-cov）**：
   - 运行中实时看：`afl-whatsup out`——`paths_total` 增长 + `execs_done` 高 = 正常
   - gcov 版目标（与 fuzz 版本分开编）：
     ```sh
     gcc -fprofile-arcs -ftest-coverage -g -O0 -o target_gcov target.c
     ```
   - 汇总:
     ```sh
     ./afl-cov -d out --live --coverage-cmd "cat AFL_FILE | ./target_gcov" --code-dir .
     ```
     产出在 `out/cov/`，HTML 报告 `out/cov/web/`（lcov/genhtml 生成）
   - 无 gcov 的替代：`afl-showmap -o /tmp/map -- ./target @@` 看单输入覆盖哪些块；对比两个输入的 map 判断是否覆盖新路径
5. **字典 / 结构化输入**：
   - 字典 `-x dict.txt`：关键词（魔数、分隔符、关键字）降低跨格式变异成本；AFL++ 自带大量格式字典（`AFLplusplus/dictionaries/` 含 png/pdf/jpeg/tiff 等，直接 `-x` 引用）
   - 结构化输入（头+长度+数据类格式）：初始种子必须覆盖不同结构变体；长度字段在字典里给常见取值；进阶用自定义 mutator（AFL++ `AFL_CUSTOM_MUTATOR_LIBRARY`，Python 示例在 `utils/python_mutators/`），维护校验和/长度的一致性

## 跨域联合

- [[re-vuln]]：本网关工作流第 2 步固定调用本技能（漏洞挖掘域覆盖率的底座原子）
- [[re-crash-triage]]：本技能产出的崩溃输入（`out/crashes/`）交其分析；最小化/去重（cmin/tmin）在其流程内完成
- [[re-ctf]]：CTF pwn / 赛题二进制的 fuzzing 引用本技能
- [[re-binary-core]]：写 harness 前用反编译确认解析入口与 API 语义（[[re-ghidra]] / [[re-ida]]）
- [[re-netcap]]：网络目标 fuzz 的种子语料来源（真实流量抓包）
- [[re-sandbox]] / [[re-analyze/platform-tips]]：fuzz 是动态执行，默认沙箱内跑

## 常见坑与陷阱

- **无插桩无覆盖率**：现象——`afl-fuzz` 跑起来 execs 暴涨但 paths 数不动、从不出崩溃；原因——目标没被插桩（release 二进制 / 编译器不是 afl-* 包装器 / `@@` 传参错误 fuzz 了无关程序）；对策——开跑前 `afl-showmap -o /tmp/map -- ./target @@` 验证覆盖率有变化；`afl-fuzz` 启动输出会警告 instrumentation 缺失；无源码改用 `-Q` QEMU 模式
- **语料大小失衡**：现象——启动慢、每轮 exec 率低，或早期完全覆盖不到解析逻辑；原因——种子太多太大（队列膨胀）或全是空文件/同一格式变体；对策——`afl-cmin` 去重到几十个覆盖不同分支的最小样本；不同格式变体各留一个代表
- **崩溃去重（cmin/tmin）**：现象——`out/crashes/` 堆积上万个文件，实际是同一个 bug；原因——fuzzer 对每个触发输入都存盘，未按崩溃点区分；对策——用 `afl-tmin` 逐个最小化（`afl-tmin -i crash -o crash.min -- ./target @@`），再按 ASAN 报错类型 + 回溯栈合并同类项（见 [[re-crash-triage]]）
- **sanitizer 配置错误**：现象——真崩溃不报（漏检）或 fuzz 进程被误杀（假崩溃）；原因——没开 ASAN（漏 `AFL_USE_ASAN=1` / `-fsanitize=address`）、`-m none` 未设导致 ASAN 内存限制被杀、ASAN 与老版本 glibc 冲突；对策——统一 `AFL_USE_ASAN=1` + `-m none`；`-t` 给足；先手工跑一个已知崩溃输入确认 ASAN 能报
- **网络目标需 harness**：现象——fuzz 网络服务二进制，输入从 socket 来，`@@` 文件根本喂不进解析逻辑，覆盖率恒为 0；原因——网络程序读 fd 不读文件，fuzz 通道错位；对策——写 harness 把文件内容直接喂给解析函数（fuzz 的是解析器不是网络栈）；真实流量用 [[re-netcap]] 抓包做种子
