白盒模糊测试必须有源码并用AFL++编译器插桩(如afl-clang-fast),无法对现成二进制反向插桩;无源码时需选qemu_mode、frida_mode或unicorn_mode等灰盒路径,并手动处理输入重定向、崩溃捕获与环境依赖。

直接结论:对复杂命令行二进制程序做白盒模糊测试,必须有源码 + AFL++ 编译器插桩(如 afl-clang-fast);若无源码,只能走灰盒路径(qemu_mode、frida_mode 或 unicorn_mode),但需手动处理输入重定向、崩溃信号、环境依赖等关键环节。
白盒测试必须用源码编译插桩,不能对现成二进制“反向插桩”
很多人误以为可以拿一个已编译好的 ./target 直接喂给 afl-fuzz 就能白盒 fuzz —— 这不可能。AFL++ 的白盒能力(精确基本块覆盖率、CmpLog、NeverZero 等)全部依赖编译时注入的桩代码,运行时无法补上。
- 必须拿到源码,用
CC=afl-clang-fast CXX=afl-clang-fast++ ./configure --disable-shared && make clean all重新构建;--disable-shared防止动态链接库逃逸插桩范围 - 若目标含第三方静态库(如
libpng.a),需确保它们也用同一套 AFL++ 编译器构建,否则覆盖率会断层 - 常见错误:
Segmentation fault (core dumped)且 UI 显示 “no instrumentation detected” —— 说明插桩失败,大概率是用了系统默认gcc编译了部分目标,或make过程跳过了某些子目录
灰盒测试选模式要看目标是否可执行、是否可控输入流
没有源码时,afl-fuzz 能否跑起来,取决于你能否把输入“塞进”程序并捕获崩溃。命令行程序比网络服务/库函数更可控,但也容易踩坑。
-
qemu_mode:最通用,支持 x86_64/arm64,但要求目标能在当前架构下**原生运行**(比如你不能在 x86_64 主机上用 qemu_mode fuzz 一个未交叉编译的 ARM Android.so);启用方式:sudo afl-system-config && make qemu_mode,然后用afl-fuzz -Q -i in -o out -- ./target @@ -
frida_mode:适合 Android/iOS/macOS 上的闭源二进制,依赖 Frida server 注入,对命令行程序需额外写 loader harness 绑定 stdin/stdout;易被 ASLR 或 Frida hook 失败阻断 -
unicorn_mode:只模拟 CPU 指令流,不依赖 OS 环境,但要求你明确提取目标函数起始/结束地址、准备寄存器上下文、手动实现输入映射(例如把文件内容写入模拟内存某地址);性能差,但能 fuzz 固件片段或 stripped 的裸二进制
命令行程序的输入与崩溃信号必须显式适配
AFL++ 默认假设程序从 stdin 或文件读取输入,并在出错时用 SIGSEGV/SIGABRT 崩溃。复杂命令行程序常破坏这个假设。
立即学习“C++免费学习笔记(深入)”;
- 如果程序从
argv[1]读文件名(如./target input.bin),必须用@@占位:afl-fuzz -i in -o out -- ./target @@;否则 AFL++ 不知道往哪写测试用例 - 如果程序内部调用
signal(SIGSEGV, handler)并吞掉崩溃,AFL++ 就收不到 crash 信号;需 patch 二进制禁用 handler,或加-n参数强制非 fork 模式(牺牲速度换可见性) - 如果程序依赖环境变量、工作目录或临时文件,必须在
afl-fuzz启动前用export或cd预置,或改用afl-fuzz -w /tmp/afl_workdir指定独立工作区
持久化模式(Persistent Mode)是提速关键,但 harness 必须重写
对命令行程序启用 Persistent Mode 可提升 3–5 倍速度,但它不是开关选项,而是要你把目标逻辑抽成一个可循环调用的函数,并用 AFL++ 提供的宏包裹。
- 不能直接对
main()开启;需新建harness.cpp,把原main()中解析参数、读文件、调用核心逻辑的部分拆出来,封装为LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)或类似接口 - 示例关键段:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { // 把 data 当作输入文件内容,喂给目标解析逻辑 parse_config_from_buffer((char*)data, size); // 替换原程序中 fopen/fread 调用 return 0; } - 编译时仍用
afl-clang-fast++,但链接目标不再是原./target,而是这个 harness;若原程序逻辑太深(如含多线程、fork 子进程),Persistent Mode 可能失效或产生误报
真正难的不是启动 afl-fuzz,而是让目标程序在 AFL++ 的约束下“诚实暴露崩溃”。尤其当它用自定义内存分配器、检查校验和、或依赖外部服务时,必须逐层剥离干扰——这些细节不会报错,只会让 fuzz 效果归零。


















