一、环境准备:新手最容易翻车
1. 系统与工具选择
- 优先 Ubuntu 20.04/22.04,Windows 原生 Fuzz 体验极差,推荐 WSL2 虚拟机;不要用生产服务器跑 Fuzz。
- 编译器认准 Clang,libFuzzer、AFL++ 均依赖 Clang;GCC 对模糊插桩支持差,别混用。
- 必须安装依赖:clang、llvm、asan、git、make,缺少会导致编译插桩失败。
- 环境隔离:单独虚拟机 / 测试机,Fuzz 会疯狂吃 CPU、内存、磁盘,容易把机器跑卡死。
2. 编译必加 / 必关参数
- 强制加
-g:保留调试符号,崩溃才能看到代码行号,不加等于无法定位漏洞。 - 必开内存检测
-fsanitize=address(ASAN),只开 fuzzer 抓不到堆溢出、释放后复用等高危漏洞。 - 关闭所有日志打印、文件读写、网络连接:printf、写日志、打开文件会把执行速度压到极低。
- 关闭程序自带信号捕获、try-catch、自定义崩溃处理,否则 Fuzzer 检测不到崩溃。
- 关闭优化
-O0调试;稳定后再用-O1,新手不要直接上-O3,堆栈难以看懂。
二、编写 Harness(测试桩)核心红线
新手 90% 跑不出效果都是桩写坏了,遵守以下规则:
- 一个桩只测一个函数 不要在一个入口里同时解析文件 + 网络 + 加密,分支太多,覆盖率几乎不动。
- 逻辑极简,只转发输入 禁止每次循环创建 socket、打开文件、初始化全局大对象,大幅拖慢执行速度。
- 重置全局 / 静态变量 上一轮畸形数据会污染全局缓存,产生大量虚假崩溃,每次调用前清空缓存。
- 不要提前过滤异常输入 不要判断长度、特殊字符直接 return,否则 Fuzz 根本测不到边界漏洞。
- 不捕获崩溃 不写 signal、try catch,程序崩溃要直接抛出给 Fuzzer 识别。
- 兼容空输入、0 长度输入 测试极端边界,很多漏洞出现在空数据场景。
三、种子语料 Corpus 避坑要点
- 不能空目录启动 Fuzz 无种子纯随机变异,很难走到深层业务分支,基本测不出漏洞。
- 种子要小、合法、少量起步 单个文件<10KB,初次准备 5~20 个正常合法样本即可,不用几百个。
- 种子去重,不要混杂多种格式 图片、协议包、二进制文件分开不同目录,混合会降低变异效率。
- 不要用超大文件当种子 解析耗时久,每秒执行次数暴跌。
四、字典 Dict 使用简单原则
- 新手建议配简易字典,提升分支覆盖:文件头、分隔符、
\x00、\xff、括号、关键字。 - 字典控制在几百行以内,冗余内容会拖慢变异。
- 二进制字节用转义写法,如
\x89PNG,不要直接粘贴乱码。
五、运行 Fuzz 时的操作规范
- 先短测,不要一上来跑 24 小时 初次运行 10~30 分钟观察状态,确认无报错、速度正常再长时间跑。
- 监控三个关键指标,出现异常立刻停
- exec/s:每秒执行次数,低于 50 说明桩代码有 IO、耗时逻辑,需要优化;
- coverage:覆盖率持续上涨代表正常;长时间不动 = 种子 / 桩有问题;
- crashes/hangs:数值大于 0 立刻停止,优先复现验证。
- 限制资源,防止机器卡死 加内存限制参数,避免畸形输入无限堆分配导致 OOM 死机。
- 新手先单进程跑通,再尝试多核并行 直接多线程并行,出现崩溃很难定位根因。
- 超时参数合理设置 默认 1000ms,复杂文件解析可改为 2000~5000ms;超时会判定为卡死 DoS 漏洞。
六、崩溃样本验证(新手最容易误判漏洞)
- 出现 crash 必须单独复现,不能直接判定漏洞
bash
./fuzz_target crash-xxxx
- 分清真漏洞 vs 虚假崩溃
- 真实高危漏洞:ASAN 报堆溢出、UAF、空指针、段错误、数组越界;
- 误报:程序主动退出、资源不足、依赖缺失、全局变量污染导致的随机崩溃。
- 崩溃样本最小化 使用工具精简畸形用例,方便开发人员快速定位代码。
- 崩溃去重:相同堆栈只保留一份,节省磁盘。
- 卡死 hang 用例不能忽略,属于拒绝服务风险,同样记录归档。
七、安全红线(绝对不能碰)
- 禁止 Fuzz 线上真实业务接口、数据库、生产服务 畸形数据包会产生脏数据、宕机、业务异常;网络 Fuzz 仅在内网离线服务测试。
- 不要直接 Fuzz 系统驱动、内核、系统关键程序 极易造成蓝屏、死机、系统损坏。
- 不要拿敏感本地文件作为 Fuzz 输入样本,防止文件损坏。
- Fuzz 产生的畸形样本禁止流入正式业务流程。
八、新手高频问题 & 解决办法
- 覆盖率一直不动 缺少有效种子;桩提前过滤异常数据;只测表层函数,未进入核心解析逻辑。
- 执行速度特别慢 桩中有打印、磁盘 IO、网络连接;种子文件过大;循环内重复初始化资源。
- 跑很久没有任何崩溃 目标程序边界校验完善;Fuzz 入口不是风险最高的函数;种子质量差。
- 大量虚假 crash 程序内部捕获信号;全局静态变量未重置;多线程资源未释放。
- 崩溃看不到代码行 编译时没加
-g调试符号。 - 运行一段时间机器 OOM 卡死 未限制进程内存;代码存在无限分配内存漏洞。
九、新手收尾流程规范
- 保存所有崩溃、卡死用例,附带完整 ASAN 报错堆栈;
- 把优质种子、复现用例留存,用于后续回归测试;
- 记录初始覆盖率、执行速度,作为优化基线;
- 开发修复漏洞后,使用同一套用例重新 Fuzz,验证漏洞不复现。
十、心态与学习建议
- Fuzz 不是一键扫漏洞,需要反复优化桩、种子、字典才能出成果;
- 先从小型解析库、本地文件解析程序练手,不要直接上手大型复杂服务;
- 先吃透 libFuzzer,再学习 AFL++、协议 Fuzz,循序渐进。