name: aflpp type: fuzzer description: > AFL++是AFL的一个分支,具有更好的模糊测试性能和高级功能。 用于C/C++项目的多核模糊测试。
AFL++
AFL++是原始AFL模糊测试器的一个分支,提供更好的模糊测试性能和更多高级功能,同时保持稳定性。与libFuzzer相比,一个主要优势是AFL++在多核运行模糊测试活动方面有稳定的支持,使其成为大规模模糊测试的理想选择。
何时使用
| 模糊测试器 | 最适合 | 复杂度 |
|---|---|---|
| AFL++ | 多核模糊测试,多样化变异,成熟项目 | 中等 |
| libFuzzer | 快速设置,单线程,简单框架 | 低 |
| LibAFL | 自定义模糊测试器,研究,高级用例 | 高 |
选择AFL++当:
- 您需要多核模糊测试以最大化吞吐量
- 您的项目可以使用Clang或GCC编译
- 您想要多样化的变异策略和成熟工具
- libFuzzer已达到瓶颈,您需要更多覆盖率
- 您正在测试生产代码库,这些代码库受益于并行执行
快速开始
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// 使用模糊测试器提供的数据调用您的代码
check_buf((char*)data, size);
return 0;
}
编译和运行:
# 先设置AFL++包装脚本(见安装)
./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<br>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 -ti \
--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++
安全警告:afl-system-config和afl-persistent-config脚本需要root权限并禁用操作系统安全功能。不要在生成系统或开发环境上模糊测试。使用专用虚拟机。
系统配置
每次重启后运行以获得每秒最多15%的执行次数增加:
./afl++ <host/docker> afl-system-config
为获得最佳性能,禁用内核安全缓解措施(需要grub引导程序,Docker中不支持):
./afl++ host afl-persistent-config
update-grub
reboot
./afl++ <host/docker> afl-system-config
用cat /proc/cmdline验证 - 输出应包含mitigations=off。
编写框架
框架结构
AFL++支持libFuzzer风格的框架:
#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// 1. 如果需要,验证输入大小
if (size < MIN_SIZE || size > MAX_SIZE) return 0;
// 2. 使用模糊测试数据调用目标函数
target_function(data, size);
// 3. 返回0(非零保留用于未来使用)
return 0;
}
框架规则
| 做 | 不 |
|---|---|
| 在运行之间重置全局状态 | 依赖前次运行的状态 |
| 优雅处理边缘情况 | 在无效输入时退出 |
| 保持框架确定性 | 使用随机数生成器 |
| 释放分配的内存 | 创建内存泄漏 |
| 验证输入大小 | 处理无界输入 |
另见: 关于详细框架编写技术、处理复杂输入的模式和高级策略,请参见fuzz-harness-writing技术技能。
编译
AFL++提供多种编译模式,有不同的权衡。
编译模式决策树
选择编译模式:
- LTO模式(
afl-clang-lto):最佳性能和插桩。首先尝试这个。 - LLVM模式(
afl-clang-fast):如果LTO无法编译,回退到此。 - GCC插件(
afl-gcc-fast):适用于需要GCC的项目。
基本编译(LLVM模式)
./afl++ <host/docker> afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
GCC编译
./afl++ <host/docker> afl-g++-fast -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
重要: GCC版本必须与用于编译AFL++ GCC插件的版本匹配。
使用Sanitizer
./afl++ <host/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框架时跳过主函数 |
-O2 |
生产优化级别(推荐用于模糊测试) |
-fsanitize=fuzzer |
启用libFuzzer兼容模式并在链接可执行文件时添加模糊测试运行时 |
-fsanitize=fuzzer-no-link |
插桩而不链接模糊测试运行时(适用于静态库和对象文件) |
语料库管理
创建初始语料库
AFL++至少需要一个非空种子文件:
mkdir seeds
echo "aaaa" > seeds/minimal_seed
对于真实项目,收集代表性输入:
- 下载您正在模糊测试格式的示例文件
- 从项目的测试套件中提取测试用例
- 为您的文件格式使用最小的有效输入
语料库最小化
活动后,最小化语料库以仅保留唯一覆盖率:
./afl++ <host/docker> afl-cmin -i out/default/queue -o minimized_corpus -- ./fuzz
另见: 关于语料库创建策略、字典和种子选择,请参见fuzzing-corpus技术技能。
运行活动
基本运行
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz
设置环境变量
./afl++ <host/docker> AFL_FAST_CAL=1 afl-fuzz -i seeds -o out -- ./fuzz
解释输出
AFL++ UI显示实时模糊测试统计信息:
| 输出 | 含义 |
|---|---|
| execs/sec | 执行速度 - 越高越好 |
| cycles done | 队列传递完成的次数 |
| corpus count | 队列中唯一测试用例的数量 |
| saved crashes | 找到的唯一崩溃的数量 |
| stability | 稳定边的百分比(应接近100%) |
输出目录结构
out/default/
├── cmdline # SUT是如何调用的?
├── crashes/ # 使SUT崩溃的输入
│ └── id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4
├── hangs/ # 使SUT挂起的输入
├── queue/ # 复制最终模糊测试器状态的测试用例
│ ├── id:000000,time:0,execs:0,orig:minimal_seed
│ └── id:000001,src:000000,time:0,execs:8,op:havoc,rep:6,+cov
├── fuzzer_stats # 活动统计信息
└── plot_data # 绘图数据
分析结果
查看实时活动统计信息:
./afl++ <host/docker> afl-whatsup out
创建覆盖率图:
apt install gnuplot
./afl++ <host/docker> afl-plot out/default out_graph/
重新执行测试用例
./afl++ <host/docker> ./fuzz out/default/crashes/<test_case>
模糊测试器选项
| 选项 | 目的 |
|---|---|
-G 4000 |
最大测试输入长度(默认:1048576字节) |
-t 1000 |
每个测试用例的超时时间(默认:1000ms) |
-m 1000 |
内存限制,以兆字节为单位(默认:0 = 无限) |
-x ./dict.dict |
使用字典文件指导变异 |
多核模糊测试
AFL++在多核模糊测试方面表现出色,具有两个主要优势:
- 每秒更多执行次数(与物理核心线性缩放)
- 非对称模糊测试(例如,一个ASan作业,其余无sanitizer)
启动活动
启动主要模糊测试器(在后台):
./afl++ <host/docker> afl-fuzz -M primary -i seeds -o state -- ./fuzz 1>primary.log 2>primary.error &
启动次要模糊测试器(根据需要的内核数量):
./afl++ <host/docker> afl-fuzz -S secondary01 -i seeds -o state -- ./fuzz 1>secondary01.log 2>secondary01.error &
./afl++ <host/docker> afl-fuzz -S secondary02 -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error &
监控多核活动
列出所有运行中的作业:
jobs
查看实时统计信息(每秒更新):
./afl++ <host/docker> watch -n1 --color afl-whatsup state/
停止所有模糊测试器
kill $(jobs -p)
覆盖率分析
AFL++通过边插桩自动跟踪覆盖率。覆盖率信息存储在fuzzer_stats和plot_data中。
测量覆盖率
使用afl-plot可视化覆盖率随时间变化:
./afl++ <host/docker> afl-plot out/default out_graph/
改进覆盖率
- 使用字典进行格式感知模糊测试
- 运行更长的活动(cycles_wo_finds表示瓶颈)
- 尝试多核模糊测试的不同变异策略
- 分析覆盖率差距并添加有针对性的种子输入
另见: 关于详细覆盖率分析技术、识别覆盖率差距和系统覆盖率改进,请参见coverage-analysis技术技能。
CMPLOG
CMPLOG/RedQueen是任何模糊测试器中最佳路径约束解决机制。要启用它,模糊测试目标需要为其插桩。在构建模糊测试目标之前设置环境变量:
./afl++ <host/docker> AFL_LLVM_CMPLOG=1 make
编译和链接框架时不需要特殊操作。
要运行带有CMPLOG插桩模糊测试目标的模糊测试器实例,添加-c0到命令行参数:
./afl++ <host/docker> afl-fuzz -c0 -S cmplog -i seeds -o state -- ./fuzz 1>secondary02.log 2>secondary02.error &
Sanitizer集成
Sanitizer对于发现不立即导致崩溃的内存损坏错误至关重要。
AddressSanitizer (ASan)
./afl++ <host/docker> AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
注意: 内存限制(-m)由于20TB虚拟内存预留而不支持ASan。
UndefinedBehaviorSanitizer (UBSan)
./afl++ <host/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++ <host/docker> afl-clang-fast++ -O2 main_stdin.c -o fuzz_stdin
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz_stdin
这比持久模式慢但不需要框架代码。
文件输入模糊测试
对于读取文件的程序,使用@@占位符:
./afl++ <host/docker> afl-clang-fast++ -O2 main_file.c -o fuzz_file
./afl++ <host/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>
", 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++ <host/docker> afl-clang-fast++ -O2 main_arg.c -o fuzz_arg
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz_arg
性能调优
| 设置 | 影响 |
|---|---|
| CPU核心数量 | 与物理核心线性缩放 |
| 持久模式 | 比fork服务器快10-20倍 |
-G输入大小限制 |
更小 = 更快,但可能错过错误 |
| ASan比率 | 每4-8个非ASan作业1个ASan作业 |
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 低exec/sec (<1k) | 未使用持久模式 | 创建LLVMFuzzerTestOneInput风格的框架 |
| 低稳定性 (<85%) | 非确定性代码 | 通过stdin或文件输入模糊测试程序,或创建这样的框架 |
| GCC插件错误 | GCC版本不匹配 | 确保系统GCC与AFL++构建匹配并安装gcc-$GCC_VERSION-plugin-dev |
| 未找到崩溃 | 需要sanitizer | 用AFL_USE_ASAN=1重新编译 |
| 内存限制超出 | ASan使用20TB虚拟 | 使用ASan时移除-m标志 |
| Docker性能损失 | 虚拟化开销 | 为生产模糊测试使用裸机或VM |
相关技能
技术技能
| 技能 | 用例 |
|---|---|
| fuzz-harness-writing | 编写有效框架的详细指南 |
| address-sanitizer | 模糊测试期间的内存错误检测 |
| undefined-behavior-sanitizer | 检测未定义行为错误 |
| fuzzing-corpus | 构建和管理种子语料库 |
| fuzzing-dictionaries | 为格式感知模糊测试创建字典 |
相关模糊测试器
| 技能 | 何时考虑 |
|---|---|
| libfuzzer | 快速原型设计,单线程模糊测试足够 |
| libafl | 需要自定义变异器或研究级功能 |
资源
关键外部资源
AFL++ GitHub Repository 官方仓库,包含全面文档、示例和问题跟踪器。
Fuzzing in Depth AFL++团队的高级文档,涵盖插桩模式、优化技术和高级用例。
AFL++ Under The Hood 技术深入探讨AFL++内部机制、变异策略和覆盖率跟踪机制。
AFL++: Combining Incremental Steps of Fuzzing Research 研究论文描述AFL++架构和相对于原始AFL的性能改进。
视频资源
- Fuzzing cURL - Trail of Bits博客文章,使用AFL++参数模糊测试cURL
- Sudo Vulnerability Walkthrough - LiveOverflow系列,重新发现CVE-2021-3156
- Rediscovery of libpng bug - LiveOverflow视频,发现CVE-2023-4863