名称: aflpp 类型: 模糊测试器 描述: > 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 "用法: $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 插件的版本匹配。
使用消毒剂
./afl++ <host/docker> AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
另见: 有关详细消毒剂配置、常见问题和高级标志,请参见 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 作业,其余无消毒剂)
开始活动
启动主模糊测试器(在后台):
./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 &
消毒剂集成
消毒剂对于发现不导致立即崩溃的内存损坏错误至关重要。
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)不支持 ASan,由于 20TB 虚拟内存预留。
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
常见消毒剂问题
| 问题 | 解决方案 |
|---|---|
| ASan 减慢模糊测试 | 在多核设置中仅使用 1 个 ASan 作业 |
| 堆栈耗尽 | 使用 ASAN_OPTIONS=stack_size=... 增加堆栈 |
| GCC 版本不匹配 | 确保系统 GCC 与 AFL++ 插件版本匹配 |
另见: 有关全面消毒剂配置和故障排除,请参见 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, "用法: %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 作业 |
现实世界示例
示例:libpng
模糊测试 libpng 演示了用静态库模糊测试 C 项目:
# 获取源码
curl -L -O https://downloads.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar xf libpng-1.6.37.tar.xz
cd libpng-1.6.37/
# 安装依赖项
apt install zlib1g-dev
# 配置和构建静态库
export CC=afl-clang-fast CFLAGS=-fsanitize=fuzzer-no-link
export CXX=afl-clang-fast++ CXXFLAGS="$CFLAGS"
./configure --enable-shared=no
export AFL_LLVM_CMPLOG=1
export AFL_USE_ASAN=1
make
# 下载工具
curl -O https://raw.githubusercontent.com/glennrp/libpng/f8e5fa92b0e37ab597616f554bee254157998227/contrib/oss-fuzz/libpng_read_fuzzer.cc
# 链接模糊测试器
export AFL_USE_ASAN=1
$CXX -fsanitize=fuzzer libpng_read_fuzzer.cc .libs/libpng16.a -lz -o fuzz
# 准备种子和字典
mkdir seeds/
curl -o seeds/input.png https://raw.githubusercontent.com/glennrp/libpng/acfd50ae0ba3198ad734e5d4dec2b05341e50924/contrib/pngsuite/iftp1n3p08.png
curl -O https://raw.githubusercontent.com/glennrp/libpng/2fff013a6935967960a5ae626fc21432807933dd/contrib/oss-fuzz/png.dict
# 开始模糊测试
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz
示例:CMake 基项目
project(BuggyProgram)
cmake_minimum_required(VERSION 3.0)
add_executable(buggy_program main.cc)
add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -O2 -fsanitize=fuzzer-no-link)
target_link_libraries(fuzz -fsanitize=fuzzer)
构建和模糊测试:
# 构建非工具化二进制
./afl++ <host/docker> cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
./afl++ <host/docker> cmake --build . --target buggy_program
# 构建模糊测试器
./afl++ <host/docker> cmake -DCMAKE_C_COMPILER=afl-clang-fast -DCMAKE_CXX_COMPILER=afl-clang-fast++ .
./afl++ <host/docker> cmake --build . --target fuzz
# 模糊测试
./afl++ <host/docker> afl-fuzz -i seeds -o out -- ./fuzz
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 低 exec/sec (<1k) | 未使用持久模式 | 创建 LLVMFuzzerTestOneInput 风格工具 |
| 低稳定性 (<85%) | 非确定性代码 | 通过 stdin 或文件输入模糊测试程序,或创建此类工具 |
| GCC 插件错误 | GCC 版本不匹配 | 确保系统 GCC 与 AFL++ 构建匹配并安装 gcc-$GCC_VERSION-plugin-dev |
| 未发现崩溃 | 需要消毒剂 | 用 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