关于 aflpp
aflpp 技能指南介绍 AFL++ 模糊测试器,面向需要并行运行测试活动或多种变异策略的项目。内容包括安装、编译、语料管理、活动监控及结果分析。
主要功能
- 01LLVM、GCC 和 LTO 编译模式
- 02支持 libFuzzer 风格测试入口
- 03并行模糊测试活动
- 04语料库最小化与字典引导
- 05崩溃、覆盖率和活动状态分析
适用场景
- 01对生产代码库开展并行模糊测试
- 02在 libFuzzer 覆盖率停滞后继续测试
- 03在 CI 中设置自动停止条件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trailofbits/ skills --skill aflpp安装后在 Claude Code 中输入/aflpp确认已加载。
附带文件agents/openai.yamlassets/trail-of-bits-mark.svg
详细介绍
AFL++
AFL++ 是从原始 AFL 模糊测试工具分支而来的版本,在保持稳定性的同时,提供了更出色的模糊测试性能和更高级的功能。与 libFuzzer 相比,AFL++ 的一大优势是稳定支持在多个核心上运行模糊测试活动,因此非常适合大规模模糊测试工作。
适用场景
| 模糊测试工具 | 最适合的场景 | 复杂度 |
|---|---|---|
| AFL++ | 多核模糊测试、多样化变异、成熟项目 | 中 |
| libFuzzer | 快速设置、单线程、简单的测试框架 | 低 |
| LibAFL | 自定义模糊测试工具、研究、高级用例 | 高 |
以下情况建议选择 AFL++:
- 需要多核模糊测试来最大化吞吐量
- 项目可以使用 Clang 或 GCC 编译
- 希望使用多样化的变异策略和成熟的工具链
- libFuzzer 的覆盖率已停滞,需要进一步提升
- 正在对可从并行执行中受益的生产代码库进行模糊测试
快速开始
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Call your code with fuzzer-provided data
check_buf((char*)data, size);
return 0;
}
编译并运行:
# Setup AFL++ wrapper script first (see Installation)
./afl++ docker afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
mkdir seeds && echo "aaaa" > seeds/minimal_seed
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz
安装
AFL++ 有许多依赖项,包括 LLVM、Python 和 Rust。我们建议使用较新的 Debian 或 Ubuntu 发行版来运行 AFL++ 模糊测试。
| 方法 | 适用场景 | 支持的编译器 |
|---|---|---|
| Ubuntu/Debian 软件源 | 较新的 Ubuntu,仅支持基本功能 | Ubuntu 23.10:Clang 14 和 GCC 13 Debian 12:Clang 14 和 GCC 12 |
| Docker(来自 Docker Hub) | 指定 AFL++ 版本、支持 Apple Silicon | 截至 4.35c:Clang 19 和 GCC 11 |
| Docker(从源码构建) | 测试尚未发布的功能、应用补丁 | 可在 Dockerfile 中配置 |
| 从源码构建 | 避免使用 Docker、需要特定补丁 | 可通过 LLVM_CONFIG 环境变量调整 |
Ubuntu/Debian
安装 afl++ 前,请使用 apt-cache show afl++ 检查软件包对 clang 版本的依赖,并安装匹配版本的 lld(例如 lld-17)。
apt install afl++ lld-17
Docker(来自 Docker Hub)
docker pull aflplusplus/aflplusplus:stable
Docker(从源码构建)
git clone --depth 1 --branch stable https://github.com/AFLplusplus/AFLplusplus
cd AFLplusplus
docker build -t aflplusplus .
从源码构建
请参阅 Dockerfile,了解 Ubuntu 版本要求和依赖项。设置 LLVM_CONFIG 以指定 Clang 版本(例如 llvm-config-18)。
封装脚本设置
创建一个封装脚本,以便在主机或 Docker 上运行 AFL++:
cat <<'EOF' > ./afl++
#!/bin/sh
AFL_VERSION="${AFL_VERSION:-"stable"}"
case "$1" in
host)
shift
bash -c "$*"
;;
docker)
shift
/usr/bin/env docker run -i \
--privileged \
-v ./:/src \
--rm \
--name "afl_fuzzing_$$" \
"aflplusplus/aflplusplus:$AFL_VERSION" \
bash -c "cd /src && bash -c \"$*\""
;;
*)
echo "Usage: $0 {host|docker}"
exit 1
;;
esac
EOF
chmod +x ./afl++
以下示例使用 docker 模式,但需要访问主机内核的系统配置命令除外。将 host 替换进去,即可在本机安装的 AFL++ 上运行其中任意命令。封装脚本会将模式参数之后的所有内容合并为一个 shell 字符串,因此引号不会保留:包含空格的参数(-x "my dict.dict")会被拆分为多个单词。请将此类文件重命名为不含空格的名称,或针对本次运行修改封装脚本。
这里有意省略了 -t。只要 stdin 不是终端,docker run -ti 就会因 the input device is not a TTY 而中止,CI 作业以及由 Agent 或脚本驱动的任何操作都属于这种情况。afl-fuzz 会检测到没有终端,并以普通状态行代替全屏 UI。像 watch 这样要求终端的程序必须在封装脚本的主机端运行。$$ 会展开为封装脚本的 PID,因此并行实例会使用不同的容器名称,而不会争用同一个 afl_fuzzing。docker ps 会截断 COMMAND 列,因此每一行看起来都一样;停止某个实例前,请使用 docker ps --no-trunc 区分各个实例。
安全警告: afl-system-config 和 afl-persistent-config 脚本需要 root 权限,并会禁用操作系统的安全功能。请勿在生产系统或开发环境中进行模糊测试,请改用专用 VM。
系统配置
每次重启后运行,可将每秒执行次数提升最多 15%:
./afl++ host afl-system-config
afl-system-config 会调整其运行所依赖的内核,因此请在托管测试活动的机器上运行。./afl++ docker afl-system-config 会通过包装器的 --privileged 容器应用相同设置;如果只通过 Docker 安装 AFL++,这是唯一的途径。
如需获得最高性能,请禁用内核安全缓解措施(需要 grub 引导加载程序,Docker 不支持):
./afl++ host afl-persistent-config
update-grub
reboot
./afl++ host afl-system-config
使用 cat /proc/cmdline 验证,输出中应包含 mitigations=off。
编写 Harness
Harness 结构
AFL++ 支持 libFuzzer 风格的 harness:
#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// 1. Validate input size if needed
if (size < MIN_SIZE || size > MAX_SIZE) return 0;
// 2. Call target function with fuzz data
target_function(data, size);
// 3. Return 0 (non-zero reserved for future use)
return 0;
}
Harness 规则
| 应做 | 不应做 |
|---|---|
| 在每次运行之间重置全局状态 | 依赖之前运行留下的状态 |
| 妥善处理边缘情况 | 遇到无效输入就退出 |
| 保持 harness 的确定性 | 使用随机数生成器 |
| 释放已分配的内存 | 造成内存泄漏 |
| 验证输入大小 | 处理不受限制的输入 |
**另请参阅:**有关详细的 harness 编写技巧、处理复杂输入的模式, 以及高级策略,请参阅 fuzz-harness-writing 技术技能。
编译
AFL++ 提供多种编译模式,各有不同的权衡。
编译模式决策树
选择编译模式:
- LTO 模式(
afl-clang-lto):性能和插桩效果最佳。请优先尝试。 - LLVM 模式(
afl-clang-fast):LTO 编译失败时的备用选择。 - GCC 插件(
afl-gcc-fast):适用于需要 GCC 的项目。
基本编译(LLVM 模式)
./afl++ docker afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
GCC 编译
./afl++ docker afl-g++-fast -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
**重要:**GCC 版本必须与用于编译 AFL++ GCC 插件的版本一致。
使用 Sanitizer
./afl++ docker AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
**另请参阅:**有关 Sanitizer 的详细配置、常见问题和高级标志, 请参阅 address-sanitizer 和 undefined-behavior-sanitizer 技术技能。
构建标志
注意,-g 并非必需,AFL++ 编译器默认会添加该标志。
| 标志 | 用途 |
|---|---|
-DNO_MAIN=1 | 使用 libFuzzer harness 时跳过 main 函数 |
-O2 | 生产环境优化级别(模糊测试推荐) |
-fsanitize=fuzzer | 启用 libFuzzer 兼容模式,并在链接可执行文件时添加模糊测试运行时 |
-fsanitize=fuzzer-no-link | 进行插桩,但不链接模糊测试运行时(适用于静态库和目标文件) |
Corpus 管理
创建初始 Corpus
AFL++ 至少需要一个非空种子文件:
mkdir seeds
echo "aaaa" > seeds/minimal_seed
对于实际项目,请收集有代表性的输入:
- 下载你要进行模糊测试的格式的示例文件
- 从项目的测试套件中提取测试用例
- 为你的文件格式使用最小的有效输入
Corpus 最小化
测试活动结束后,精简 corpus,仅保留覆盖范围不同的内容:
./afl++ docker afl-cmin -i out/default/queue -o minimized_corpus -- ./fuzz
**另请参阅:**有关 corpus 创建策略、字典和种子选择, 请参阅 fuzzing-corpus 技术技能。
运行测试活动
基本运行
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz
设置环境变量
./afl++ docker AFL_FAST_CAL=1 afl-fuzz -i seeds -o out -- ./fuzz
解读输出
无论采用哪种模式,AFL++ 都会报告这些统计数据,但解读方式取决于所用模式。包装器的 docker run -i 不会为容器分配 TTY,因此 afl-fuzz 会退出全屏 UI,改为在日志中写入纯文本状态行。下方字段会显示在日志和 state/<instance>/fuzzer_stats 中。若要使用交互式 UI,请在终端中运行 host 模式,或在手动监控的运行中为包装器添加 -t。
| 输出 | 含义 |
|---|---|
| execs/sec | 执行速度,越高越好 |
| cycles done | 已完成的队列遍历次数 |
| corpus count | 队列中不同测试用例的数量 |
| saved crashes | 发现的不同崩溃数量 |
| stability | 稳定边的百分比(应接近 100%) |
输出目录结构
out/default/
├── cmdline # How was the SUT invoked?
├── crashes/ # Inputs that crash the SUT
│ └── id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
├── hangs/ # Inputs that hang the SUT
├── queue/ # Test cases reproducing final fuzzer state
│ ├── id:000000,time:0,execs:0,orig:minimal_seed
│ └── id:000001,src:000000,time:0,execs:8,op:havoc,rep:6,+cov
├── fuzzer_stats # Campaign statistics
└── plot_data # Data for plotting
分析结果
查看实时活动统计信息:
./afl++ docker afl-whatsup out
创建覆盖率图表。aflplusplus 镜像已自带 gnuplot-nox;在主机模式下,请先使用 apt install gnuplot 安装 gnuplot。
./afl++ docker afl-plot out/default out_graph/
重新执行测试用例
传入 out/default/crashes/ 中的一个文件名:
./afl++ docker ./fuzz out/default/crashes/id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
Fuzzer 选项
| 选项 | 用途 |
|---|---|
-G 4000 | 测试输入的最大长度(默认值:1048576 字节) |
-t 1000 | 每个测试用例的超时时间,单位为毫秒(默认值:1000ms) |
-m 1000 | 内存限制,单位为兆字节(默认值:0 = 不限) |
-x ./dict.dict | 使用字典文件来引导变异 |
重要环境变量
AFL++ 有许多环境变量,但大多数只适用于少数场景。以下是实际使用中重要的变量。
始终设置这些变量
# Every campaign should use tmpfs — SSDs will thank you, and it's faster
AFL_TMPDIR=/dev/shm
设置 AFL_TMPDIR 可免费提升性能,且没有任何缺点——不设置它会损耗 SSD 并降低模糊测试速度。
慢速目标
# Speeds up calibration ~2.5x — use when targets are slow (e.g., >10 ms/exec)
AFL_FAST_CAL=1
AFL_FAST_CAL 可在几乎不损失精度的情况下缩短校准时间。特别推荐用于慢速目标,因为这类目标的校准通常会耗费很长时间。
多核活动
# On the primary (-M) instance only — needed for afl-cmin, not for fuzzing itself
AFL_FINAL_SYNC=1
# On all instances — cache test cases in memory (default: 50 MB, good range: 50-250 MB)
AFL_TESTCACHE_SIZE=100
AFL_FINAL_SYNC 会指示主实例在停止时从所有次要实例执行最终导入。这不会影响模糊测试过程本身——只有在之后运行 afl-cmin 进行语料库最小化时才有意义,可确保主实例的队列包含完整的合并语料库。AFL_TESTCACHE_SIZE 会在内存中缓存测试用例,以减少磁盘 I/O;默认值为 50 MB,对于大多数活动而言,50-250 MB 的取值效果良好。
CI/自动化模糊测试
# Fail fast if fuzzing isn't finding anything
AFL_EXIT_ON_TIME=3600 # 1 hour with no new paths = stop
# Or run until "done" (all queue entries processed)
AFL_EXIT_WHEN_DONE=1
# Headless environments
AFL_NO_UI=1
在 CI 中进行不设限的模糊测试会浪费资源。请设置时间限制或使用退出条件。
应避免的变量
| 变量 | 不使用的原因 |
|---|---|
AFL_NO_ARITH | 可能降低二进制格式的覆盖率,但对基于文本的目标可能有用 |
AFL_SHUFFLE_QUEUE | 仅适用于特殊配置,通常弊大于利 |
AFL_DISABLE_TRIM | 修剪很有价值,不要无故禁用 |
多核模糊测试
AFL++ 在多核模糊测试方面表现出色,主要有两个优势:
- 每秒执行次数更多(随物理核心数线性增长)
- 非对称模糊测试(例如,一个 ASan 作业,其余不使用检测器)
启动活动
启动主 Fuzzer(在后台运行):
./afl++ docker afl-fuzz -M primary -i seeds -o state -- ./fuzz 1>primary.log 2>primary.error </dev/null &
启动次要 Fuzzer(数量与核心数相同):
./afl++ docker afl-fuzz -S secondary01 -i seeds -o state -- ./fuzz 1>secondary01.log 2>secondary01.error </dev/null &
./afl++ docker afl-fuzz -S secondary02 -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error </dev/null &
</dev/null 是必需的,并非装饰。docker run -i 会让客户端继续读取自身的标准输入,而读取终端的后台进程会收到 SIGTTIN,其默认操作是停止进程——因此,不进行重定向时,这些作业会在 jobs 中显示为 Stopped,且永远不会执行模糊测试。
监控多核活动
列出所有正在运行的作业:
jobs
查看实时统计信息。watch 需要终端,而 docker run -i 不会提供终端,因此应将整个调用封装起来,而不是在容器内运行 watch。每次刷新都会启动一个容器,这就是刷新间隔设为 5 秒而非 1 秒的原因:
watch -n5 --color ./afl++ docker afl-whatsup state/
停止所有 Fuzzer
kill $(jobs -p)
覆盖率分析
AFL++ 会通过边插桩自动跟踪覆盖率。覆盖率信息存储在 fuzzer_stats 和 plot_data 中。
测量覆盖率
使用 afl-plot 可视化覆盖率随时间的变化:
./afl++ docker afl-plot out/default out_graph/
提高覆盖率
- 使用字典进行格式感知的模糊测试
- 延长活动运行时间(cycles_wo_finds 表示是否已进入平台期)
- 使用多核模糊测试尝试不同的变异策略
- 分析覆盖率缺口并添加有针对性的种子输入
另请参阅: 有关详细的覆盖率分析技巧、如何识别覆盖率缺口以及系统性提高覆盖率的方法,请参阅 coverage-analysis 技术技能。
CMPLOG
CMPLOG/RedQueen 是所有模糊测试器中最好的路径约束求解机制。 要启用它,需要为模糊测试目标进行相应插桩。 构建模糊测试目标之前,设置环境变量:
./afl++ docker AFL_LLVM_CMPLOG=1 make
编译和链接测试框架无需特别操作。
要使用经过 CMPLOG 插桩的模糊测试目标运行模糊测试器实例,请在类似命令的参数中添加 -c0:
./afl++ docker afl-fuzz -c0 -S cmplog -i seeds -o state -- ./fuzz 1>cmplog.log 2>cmplog.error </dev/null &
Sanitizer 集成
Sanitizer 对于发现不会立即导致崩溃的内存损坏错误至关重要。
AddressSanitizer(ASan)
./afl++ docker AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
**注意:**由于需要预留 20TB 虚拟内存,ASan 不支持内存限制(-m)。
UndefinedBehaviorSanitizer(UBSan)
./afl++ docker AFL_USE_UBSAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer,undefined harness.cc main.cc -o fuzz
常见 Sanitizer 问题
| 问题 | 解决方案 |
|---|---|
| ASan 会拖慢模糊测试 | 在多核配置中仅使用 1 个 ASan 作业 |
| 栈耗尽 | 使用 ASAN_OPTIONS=stack_size=... 增大栈 |
| GCC 版本不匹配 | 确保系统 GCC 与 AFL++ 插件版本一致 |
**另请参阅:**有关全面的 sanitizer 配置和故障排除, 请参阅 address-sanitizer 技术技能。
高级用法
提示和技巧
| 提示 | 帮助原因 |
|---|---|
| 尽可能使用 LLVMFuzzerTestOneInput 测试框架 | 如果模糊测试活动的稳定性至少达到 85%,这是最高效的模糊测试方式。如果达不到,请尝试标准输入或文件输入模糊测试 |
| 使用字典 | 帮助模糊测试器发现特定格式的关键词和魔数字节 |
| 设置合理的超时时间 | 防止系统负载导致误报 |
| 限制输入大小 | 更大的输入不一定能探索更多空间 |
| 监控稳定性 | 稳定性低表明存在非确定性行为 |
标准输入模糊测试
AFL++ 可以对从 stdin 读取输入且未使用 libFuzzer 测试框架的程序进行模糊测试:
./afl++ docker afl-clang-fast++ -O2 main_stdin.c -o fuzz_stdin
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz_stdin
这种方式比持久模式慢,但不需要编写测试框架代码。
文件输入模糊测试
对于读取文件的程序,请使用 @@ 占位符:
./afl++ docker afl-clang-fast++ -O2 main_file.c -o fuzz_file
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz_file @@
为提高性能,可以使用 fmemopen 从内存创建文件描述符。
参数模糊测试
使用 argv-fuzz-inl.h 对命令行参数进行模糊测试:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __AFL_COMPILER
#include "argv-fuzz-inl.h"
#endif
void check_buf(char *buf, size_t buf_len) {
if(buf_len > 0 && buf[0] == 'a') {
if(buf_len > 1 && buf[1] == 'b') {
if(buf_len > 2 && buf[2] == 'c') {
abort();
}
}
}
}
int main(int argc, char *argv[]) {
#ifdef __AFL_COMPILER
AFL_INIT_ARGV();
#endif
if (argc < 2) {
fprintf(stderr, "Usage: %s <input_string>\n", argv[0]);
return 1;
}
char *input_buf = argv[1];
size_t len = strlen(input_buf);
check_buf(input_buf, len);
return 0;
}
下载头文件:
curl -O https://raw.githubusercontent.com/AFLplusplus/AFLplusplus/stable/utils/argv_fuzzing/argv-fuzz-inl.h
编译并运行:
./afl++ docker afl-clang-fast++ -O2 main_arg.c -o fuzz_arg
./afl++ docker afl-fuzz -i seeds -o out -- ./fuzz_arg
性能调优
| 设置 | 影响 |
|---|---|
| CPU 核心数 | 随物理核心数线性扩展 |
| 持久模式 | 比 fork 服务器快 10-20 倍 |
-G 输入大小限制 | 越小速度越快,但可能漏掉错误 |
| ASan 比例 | 每 4-8 个非 ASan 作业配置 1 个 ASan 作业 |
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 执行速度低(<1k 次/秒) | 未使用持久模式 | 创建 LLVMFuzzerTestOneInput 风格的测试框架 |
| 稳定性低(<85%) | 存在非确定性代码 | 通过 stdin 或文件输入对程序进行模糊测试,或创建相应的测试框架 |
| GCC 插件错误 | GCC 版本不匹配 | 确保系统 GCC 与 AFL++ 构建版本一致,并安装 gcc-$GCC_VERSION-plugin-dev |
| 未发现崩溃 | 需要 Sanitizer | 使用 AFL_USE_ASAN=1 重新编译 |
| 超出内存限制 | ASan 使用 20TB 虚拟内存 | 使用 ASan 时移除 -m 标志 |
| Docker 性能下降 | 虚拟化开销 | 在生产环境模糊测试中使用裸机或虚拟机 |
相关技能
技术技能
| 技能 | 使用场景 |
|---|---|
| fuzz-harness-writing | 编写有效测试框架的详细指南 |
| address-sanitizer | 在模糊测试期间检测内存错误 |
| undefined-behavior-sanitizer | 检测未定义行为错误 |
| fuzzing-corpus | 构建和管理种子语料库 |
| fuzzing-dictionaries | 创建用于格式感知模糊测试的字典 |
相关模糊测试器
| 技能 | 适用情况 |
|---|---|
| libfuzzer | 快速原型开发,单线程模糊测试已足够 |
| libafl | 需要自定义变异器或研究级功能 |
资源
重要外部资源
AFL++ GitHub 仓库 官方仓库,提供全面的文档、示例和问题跟踪器。
深入了解模糊测试 AFL++ 团队编写的进阶文档,介绍插桩模式、优化技巧和高级用例。
AFL++ 内部原理 深入介绍 AFL++ 的内部实现、变异策略和覆盖率跟踪机制。
AFL++:整合模糊测试研究的渐进成果 介绍 AFL++ 架构及其相较于原版 AFL 的性能提升的研究论文。
视频资源
- 对 cURL 进行模糊测试 - Trail of Bits 博客文章,介绍如何使用 AFL++ 对 cURL 参数进行模糊测试
- Sudo 漏洞演练 - LiveOverflow 系列视频,重新发现 CVE-2021-3156
- 重新发现 libpng 漏洞 - LiveOverflow 视频,介绍如何发现 CVE-2023-4863