Skip to content
 编辑

The afl-fuzz approach

afl-fuzz 使用方法

AFL++ 是一款暴力模糊测试工具,它结合了一种极其简单但非常可靠的基于插桩技术完成覆盖率引导的遗传算法。AFL++ 采用一种改进的边覆盖率(edge coverage)度量,能够轻松捕捉到程序控制流中细微的、局部性的变化。

注:如果您想深入了解 AFL++ 的最新工作原理,我们推荐您阅读这篇博客文章:https://blog.ritsec.club/posts/afl-under-hood/

简而言之,afl-fuzz 的核心算法流程可概括为:

  1. 将用户提供的初始测试用例加载到输入队列中

  2. 从队列中取出下一个种子文件

  3. 尝试将测试用例裁剪至不影响程序测量行为的最小尺寸

  4. 使用一套均衡且经过充分研究的传统变异策略反复对种子文件进行变异

  5. 若生成的变异样本触发了插桩记录到新状态转换(即覆盖率提高),则将该变异样本作为新条目加入种子队列

  6. go to 2

​已发现的测试用例会定期进行筛选,剔除那些被更新、更高覆盖率样本所淘汰的冗余用例,同时还会经历若干其他基于插桩技术的优化步骤,以减少冗余工作。

作为模糊测试过程的附带结果,该工具会生成一套精简且自包含的优质测试用例集。这些测试用例对于驱动其他依赖大量人力或计算资源的测试流程极具价值——例如用于浏览器、办公软件、图形套件或闭源工具的压力测试。

afl-fuzz 经过充分验证,在开箱即用的性能表现上远超传统的盲模糊测试(blind fuzzing)或仅基于覆盖率的工具。

理解状态界面

本节提供了状态界面的概述,以及针对用户界面(UI)中显示的任何警告信息和红色提示文字的故障排除提示。

如需要通用使用手册,请参阅 README.md

​关于颜色显示的说明​​

状态屏幕和错误消息使用颜色来保持可读性,并帮助您快速定位关键信息。例如,红色文字通常表示“请参阅本文档”:-)

但需注意,只有当您的终端使用传统的 Unix 配色方案(黑底白字)或类似配色方案时,UI 才能正确渲染颜色​​。

如果您启用了反色显示(黑底白字),建议更改终端设置,例如:

对于 GNOME 终端,请转到“编辑 Edit”>“配置文件 Profile ”首选项,选择“颜色 colors ”选项卡,然后从内置方案列表中选择“白底黑字 white on black ”。 对于 MacOS X 的终端应用,通过“Shell”>“新建窗口 New Window ”菜单使用“ Pro ”配色方案打开一个新窗口(或将“ Pro ”设置为默认方案)。 若您坚持保留当前配色方案,您可以通过编辑 config.h 文件,将 USE_COLORS 注释掉,然后执行 make clean all 重新编译。

目前我们还没找到既简单又不会产生副作用的替代方案,对此我们深表歉意。

言归正传,让我们来谈谈状态界面上的具体内容……

状态栏

american fuzzy lop ++3.01a (default) [fast] {0}

状态栏第一行显示 afl-fuzz 当前运行模式​​(正常模式为 “ american fuzzy lop ”,崩溃探索模式为 “ peruvian rabbit mode ”)以及 AFL++ 的版本号。版本号旁边设有横幅标志——如果没有通过 -T 参数手动设置,则默认显示被测程序二进制文件名称,或者在并行模糊测试中则会显示 -M / -S (主/次节点)名称。倒数第二项显示当前采用的功耗调度模式(默认为“ fast ”模式)。最后一项是 CPU ID.

进程计时

  +----------------------------------------------------+
  |        run time : 0 days, 8 hrs, 32 min, 43 sec    |
  |   last new find : 0 days, 0 hrs, 6 min, 40 sec     |
  | last uniq crash : none seen yet                    |
  |  last uniq hang : 0 days, 1 hrs, 24 min, 32 sec    |
  +----------------------------------------------------+

这一部分相当直观:它告诉您模糊测试工具已运行的总时长,以及自上次发现新结果(“路径 paths ” / 崩溃 / 挂起)以来的耗时统计。这里的“ paths ”是简写,指那些触发了全新执行路径的测试用例。

关于运行时间:没有严格规定,但大多数模糊测试任务都需要持续运行数天甚至数周。事实上,对于一个中等复杂度的项目,首轮测试通常就需要耗时一天左右。某些情况下,我们甚至会允许测试任务持续运行数月之久。

​需要特别注意的情况:如果测试开始后几分钟内都没有发现新路径,可能存在以下问题:

  1. 目标二进制文件调用方式不正确,导致程序根本无法解析输入文件

  2. 默认内存限制( -m 参数)设置过严,程序因无法分配缓冲区而提前退出

  3. 输入文件明显无效,总是无法通过基础文件头检查

如果长时间没有新路径出现,本区域最终也会显示醒目的红色警告提示。:-)

汇总结果

  +-----------------------+
  |  cycles done : 0      |
  |  total paths : 2095   |
  | uniq crashes : 0      |
  |   uniq hangs : 19     |
  +-----------------------+

本区域首行显示当前已完成​​队列遍历轮次​(queue passes)​的计数——即模糊测试工具迄今为止对所有已发现的值得关注的测试用例进行完整测试(包括变异、执行、循环回到起点)的循环次数。每个模糊测试会话至少应完成一轮完整遍历,理想情况下应持续多个周期。

如前所述,首轮测试通常需要耗时一天以上,请耐心等待。

为了帮助判断何时按下 Ctrl-C (中断测试),系统采用​​颜色编码​​的循环计数器。首轮遍历期间显示为​​洋红色​​;后续轮次若持续发现新路径则变为​​黄色​​;当新发现停止时转为​​蓝色;若长时间未检测到任何活动变化最终变为​​绿色​​。

该区域其余指标一目了然:目前已发现的测试用例(“ paths ”)总数、唯一性故障(崩溃 / 挂起)数量。

您可通过实时查看输出目录中的文件来检查测试用例、崩溃和挂起情况,具体请参阅 #interpreting-output.

轮次内过程

  +-------------------------------------+
  |  now processing : 1296 (61.86%)     |
  | paths timed out : 0 (0.00%)         |
  +-------------------------------------+

本区域显示模糊测试工具在当前队列循环中的实时进度,它显示了当前正在处理的测试用例的 ID ,以及因持续超时而被自动丢弃的输入样本数量。

若首行末尾出现“ * ”标记,则表示当前处理的执行路径未被标记为“优先路径 favored ”(该特性将在后续章节详细说明)。

图覆盖率

  +--------------------------------------+
  |    map density : 10.15% / 29.07%     |
  | count coverage : 4.03 bits/tuple     |
  +--------------------------------------+

本节介绍目标二进制文件中插桩代码观测到的覆盖率统计信息。

状态框首行显示当前输入样本触发的分支元组命中数与位图容量的比例关系。左侧数值表示当前测试用例的覆盖率,右侧数值反映整个测试语料库的总体覆盖率。

需要注意的极端情况:

第二行数值反映分支元组命中次数的离散程度:

总的来说,这两项指标可用于对比不同模糊测试任务在相同插桩二进制文件上的覆盖率差异。

阶段性进展

  +-------------------------------------+
  |  now trying : interest 32/8         |
  | stage execs : 3996/34.4k (11.62%)   |
  | total execs : 27.4M                 |
  |  exec speed : 891.7/sec             |
  +-------------------------------------+

这一部分深入展示了模糊测试工具当前的工作阶段,包含以下可能状态:

剩余字段相对直观:当前阶段的执行计数进度指示、全局执行计数器和当前程序的执行速度基准。执行速度会因测试用例不同而波动,但理想情况下,基准在大部分时间应该超过 500 exec/sec ——若长期低于 100 exec/sec,那么测试工作将非常漫长。

模糊测试工具也会明确警告关于慢速测试目标的问题。如果发生这种情况,请参阅 best_practices.md#improving-speed 以获取提速建议。

深入发现

  +--------------------------------------+
  | favored paths : 879 (41.96%)         |
  |  new edges on : 423 (20.19%)         |
  | total crashes : 0 (0 unique)         |
  |  total tmouts : 24 (19 unique)       |
  +--------------------------------------+

本区域展示了几项主要面向深度技术用户的进阶指标。该部分包括模糊测试工具基于内置最小化算法评估筛选出的”高价值路径”数量(这些路径将获得更多测试时间),真正实现边覆盖率提升的测试用例数量(区别于仅增加分支命中计数的无效用例),关于崩溃和超时的更详细、更具体的计数器。

请注意,超时计数器与挂起计数器存在重要差异:超时计数包含所有超过预设时限的测试用例,挂起计数仅统计那些超时幅度达到”程序挂起”判定标准的案例(即部分超时用例可能被计入前者但未被后者捕获)。

​模糊测试策略效能分析​​

  +-----------------------------------------------------+
  |   bit flips : 57/289k, 18/289k, 18/288k             |
  |  byte flips : 0/36.2k, 4/35.7k, 7/34.6k             |
  | arithmetics : 53/2.54M, 0/537k, 0/55.2k             |
  |  known ints : 8/322k, 12/1.32M, 10/1.70M            |
  |  dictionary : 9/52k, 1/53k, 1/24k                   |
  |havoc/splice : 1903/20.0M, 0/0                       |
  |py/custom/rq : unused, 53/2.54M, unused              |
  |    trim/eff : 20.31%/9201, 17.05%                   |
  +-----------------------------------------------------+

本节专为深度技术用户设计,展示各模糊测试策略(参见前文所述)的路径产出率与执行次数的比例关系,用于验证不同测试方法的有效性假设。

本部分中裁剪策略( trim )统计数据与其他策略存在差异。首个数值表示从输入文件中成功移除的字节比例,第二个数值显示达成该修剪效果所需的执行次数,第三个数值反映虽无法移除但被判定为”无效字节”的比例(这些字节会被排除在高成本确定性测试步骤之外)。

请注意,当确定性变异模式处于关闭状态时(默认禁用,因效率较低),前五行将显示“ disabled (default, enable with -D )”,即“已禁用(默认,使用-D启用)”。

仅激活的策略才会显示对应的计数器数据。

​路径拓扑分析​​

  +---------------------+
  |    levels : 5       |
  |   pending : 1570    |
  |  pend fav : 583     |
  | own finds : 0       |
  |  imported : 0       |
  | stability : 100.00% |
  +---------------------+

本节第一个字段记录通过引导式模糊测试已到达的“路径深度”。简而言之:用户提供的初始测试用例被视为“层级 1 ”;通过常规模糊测试衍生的用例为“层级 2 ”;以这些衍生用例作为后续模糊测试轮次的输入而得出的测试用例为”层级 3 “,以此类推。因此,最大深度是直观反映 afl-fuzz 插桩引导策略的有效性收益的指标。

下一个字段显示尚未经过任何模糊测试的原始输入数量。同时还给出当前队列周期中真正想要处理的“受青睐( favored )”的条目数(非受青睐的条目可能需要等待多个周期才能被测试)。

接下来是本阶段发现的新路径数量,在进行并行模糊测试时从其他模糊测试工具实例导入的路径数量,以及相同输入却偶尔产生不同行为的变异程度,即 trace 一致性得分。

最后一项相当有趣:它衡量的是观测到的执行轨迹的一致性。

目前,大多数测试目标都能保持 100% 的评分,若看到较低评分,可排查以下原因:

CPU 负载

  [cpu: 25%]

这个微型状态栏显示的是本地系统的显式 CPU 利用率。其计算原理是统计处于“可运行”状态的进程数量,并将其与系统逻辑 CPU 数量进行对比得出。

若数值以绿色显示,说明您当前使用的 CPU 核心少于系统可用核心数,此时可通过并行化处理来提升性能,具体优化建议请参阅 fuzzing_in_depth.md:3c) Using multiple cores

若数值呈红色,说明您的 CPU 可能已处于过载状态,继续增加 fuzzer 实例可能无法带来性能提升。

当然,这个基准测试非常简单:它只能反映准备就绪的进程数量,却无法衡量这些进程的资源消耗强度。同时该指标也未区分物理核心、逻辑核心和虚拟化 CPU 的差异,而这三者的性能特征存在显著区别。

如果您想要更精确的测量,可在命令行运行 afl-gotcpu 工具进行检测。

理解输出

请参见 #understanding-the-status-screen 以获取有关如何解读显示的统计信息及监控进程健康状况的信息。如果界面中有任何元素以红色高亮显示,务必查阅该文档​​。

模糊测试将持续运行,直到您按下 Ctrl-C 终止。至少,应让 fuzzer 完成一个完整的队列循环(queue cycle)且不再发现新的路径覆盖——这一过程可能需要数小时到一周不等,具体取决于目标程序的复杂度。

在输出目录中,fuzzer 会创建并实时更新三个子目录:

如果相关的执行路径涉及未在先前记录的故障中出现的任何状态转换,则崩溃和挂起被视为“独特的”。如果一个单一的错误可以通过多种方式触发,早期过程中的计数可能会膨胀,但这应该很快会减小。 如果某次故障(崩溃或挂起)对应的执行路径涉及未在先前记录的故障中出现的任何状态转换,则会被视为“新发现”的 crash 或 hang。若同一漏洞可通过多种路径触发,初期可能会出现重复计数,但随着测试深入,这种情况会迅速减少。

崩溃和挂起用例的文件名会与它们对应的非故障父级 queue 条目保持​​关联,便于后续调试定位问题根源。

可视化

若系统已安装 gnuplot,您还可以使用 afl-plot 为当前运行的模糊测试任务生成直观的统计图表。示例效果可参考 https://lcamtuf.coredump.cx/afl/plot/

如需更直观的图形界面监控,可手动编译安装 ​​afl-plot-ui​​——这是一个基于 ​​GTK​​ 的辅助工具,能够以窗口形式展示 afl-plot 生成的图表。安装步骤如下:

sudo apt install libgtk-3-0 libgtk-3-dev pkg-config
cd utils/plot_ui
make
cd ../../
sudo make install

如果想要了解更多有关使用 StatsD 实现远程监控和指标可视化的内容,参见 rpc_statsd.md

附录:状态和图表文件

如需无人值守运行,部分关键状态信息会以机器可读的格式记录在输出目录下的 ​​fuzzer_stats​​ 文件中。该文件包含以下核心数据:

这些字段大多与状态屏幕(UI)显示的指标直接对应。

此外,文件还包含 ​​plot_data​​ 字段,记录了上述多数指标的历史变化数据。若已安装 gnuplot,可使用配套的 afl-plot 工具将其转换为可视化进度报告。

附录:使用 StatsD 自动发送指标

在持续集成(CI)环境或同时运行多个模糊测试工具时,手动加载每个实例或部署脚本读取统计信息会变得十分繁琐。通过设置 AFL_STATSD 环境变量(以及相关配套变量 AFL_STATSD_HOSTAFL_STATSD_PORTAFL_STATSD_TAGS_FLAVOR),您可以将指标数据自动发送至指定的 StatsD 服务器。根据您的 StatsD 服务配置,您可以实现以下功能:

将能够基于这些指标进行监控、触发警报或执行操作(例如,对新构建的慢执行次数进行警报,当距离上次发现崩溃的时间超过崩溃阈值时告警等)。

所选指标是状态文件和 plot 数据中所有指标的子集,具体包括:cycle_donecycles_wo_findsexecs_doneexecs_per_seccorpus_countcorpus_favoredcorpus_foundcorpus_importedmax_depthcur_itempending_favspending_totalcorpus_variablesaved_crashessaved_hangstotal_crashesslowest_exec_msedges_foundvar_byte_counthavoc_expansion。它们的定义可在上文附录中找到。

若同时运行多个 fuzzer 实例,强烈建议通过 AFL_STATSD_TAGS_FLAVOR 设置与您 StatsD 服务器匹配的 tag 风格。这将允许您区分不同实例的性能数据、快速定位异常实例、追踪各策略的独立进度等。