Skip to content
 编辑

The afl-fuzz approach

afl-fuzz 使用方法

AFL++ 是一种结合了极其简单但非常可靠的基于插桩的遗传算法的暴力模糊测试工具。它使用一种改进的边覆盖率形式,能够轻松捕捉到程序控制流中的细微的、局部性的变化。

注:如果您对 AFL++ 如何运作的最新深入分析感兴趣,我们推荐您阅读这篇博客文章:https://blog.ritsec.club/posts/afl-under-hood/

简而言之,afl-fuzz 使用的算法总体可以概括为:

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

  2. 从队列中取出下一个输入文件。

  3. 尝试将测试用例缩减到最小,在这个尺寸下,测试用例的输入不会改变所观测到的程序行为

  4. 使用经过平衡且深入研究过的多种传统模糊测试策略,对输入文件进行反复变异。

  5. 如果生成的任何变异导致插桩记录到了新的状态转换,则将变异后的输出作为新条目添加到队列中。

  6. 返回到步骤2。

发现的测试用例还会定期进行筛选,以淘汰那些应被更新、覆盖率更高的测试用例所取代的用例;并且还会通过一些由插桩所驱动的操作以减少工作量。

作为模糊测试过程的一个附带结果,该工具会创建一个小的、自包含的值得关注的测试用例集合。这些测试用例对于为其他需要大量人力或资源投入的测试方案(例如,用于浏览器、办公软件、图形套件或闭源工具的压力测试)提供测试种子非常有用。

afl-fuzz 这个模糊测试工具已经经过了完全测试,能够以远超盲目模糊测试或仅关注覆盖率的测试工具的性能运作。

理解状态界面

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

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

一个与颜色有关的注意事项

状态屏幕和错误消息使用颜色来保持可读性,并吸引您对最重要细节的关注。例如,红色几乎总是意味着“请参阅本文档”:-)

但不幸的是,只有当您的终端使用传统的 Unix 颜色处理(黑底白字)或类似设置时,用户界面才会正确显示。

如果您正在使用反色显示,您可能需要更改设置,例如:

对于 GNOME 终端,请转到“编辑”>“配置文件”偏好设置,选择“颜色”选项卡,然后从内置方案列表中选择“白底黑字”。 对于 MacOS X 的终端应用,通过“Shell”>“新建窗口”菜单使用“ 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    |
  +----------------------------------------------------+

这一部分相当直观:它告诉您模糊测试工具已经运行了多长时间,以及自最新发现以来已经过去了多长时间。这被细分为“路径”(触发新执行模式的测试用例的别称)、崩溃和挂起。

在时间上,没有硬性规定,但大多数模糊测试任务预计会运行数天或数周;事实上,对于一个中等复杂度的项目,第一轮测试可能就需要大约一天的时间。有时有些任务可能会允许运行数月。

这里有一点很重要,需要注意:如果工具在启动后的几分钟内没有找到新的路径,那么您可能没有正确启动目标二进制文件,它根本无法解析模糊测试工具输入的测试用例文件;其他可能的解释是默认内存限制(-m)过于严格,程序在非常早期就因无法分配缓冲区而退出;或者输入的测试文件明显无效,总是无法通过基本头部检查。

如果一段时间内没有新的路径出现,您最终也会在这一部分看到一个大红色的警告。:-)

汇总结果

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

这一部分的第一个字段给出了到目前为止已经完成的队列遍历次数,即模糊测试工具遍历迄今为止发现的所有值得关注的测试用例、使用它们进行模糊测试并返回到最初位置的次数。每个模糊测试过程都应该允许至少完成一个周期;并在理想情况下应该运行更长的时间。

如前所述,第一轮测试可能需要一天或更长的时间,所以请耐心等待。

为了帮助判断何时按下Ctrl-C(中断测试),周期计数器采用了颜色编码。在第一轮测试期间,它显示为洋红色;如果在后续轮次中仍然有新发现,则变为黄色;当结束新发现时变为蓝色;最后,在模糊测试工具长时间没有任何发现后变为绿色。

屏幕这一部分的其余字段应该相当明显:有迄今为止发现的测试用例(“路径”)的数量和去重故障的数量。可以通过浏览输出目录来实时获知这些测试用例、崩溃和挂起情况,请参阅 #理解输出

轮次内过程

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

这个框告诉您模糊测试工具在当前队列周期中的进度:它显示了当前正在处理的测试用例的 ID ,以及因一直超时而被丢弃的输入数量。

第一行有时会显示的“*”后缀意味着当前处理的路径不是“受青睐的”(这是一个稍后会讨论的属性)。

图覆盖率

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

这一部分介绍一些关于目标二进制文件中嵌入的插桩所观察到的覆盖率的内容。

框中的第一行告诉您已经执行到了多少分支元组,以及位图可以容纳的总量。左边的数字描述了当前输入的情况;右边的数字是整个输入测试用例集的值。

要注意极端情况:

另一行处理的是在二进制文件中发现的元组命中次数的可变性。本质上,如果对于所有的输入尝试,每个被执行的分支总是被固定次数地执行,那么这将显示为1.00。随着我们能够为每个分支触发其他命中次数,指针将开始向8.00移动(8位位图中的每个位都被命中),但可能永远不会达到这个极端。

总的来说,这些值在比较依赖相同插桩二进制文件的几个不同模糊测试作业的覆盖率上很有用。

阶段性进展

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

这一部分深入展示了模糊测试工具当前正在做什么。它告诉您当前阶段的状态,可能是以下任何一个:

剩余字段应该相当直观:有当前阶段的执行计数进度指示、全局执行计数器和当前程序执行速度的基准。这可能会因测试用例的不同而有所波动,但理想情况下,基准在大部分时间应该超过500次执行/秒——如果它保持在100以下,那么测试工作可能会花费非常长的时间。

模糊测试工具也会明确警告你关于慢速测试目标的问题。如果发生这种情况,请参阅 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%                   |
  +-----------------------------------------------------+

这只是另一个针对技术极客的部分,用于追踪之前讨论的每种模糊测试策略所捕获的路径数量与尝试执行次数的比例。这有助于有力地验证关于 afl-fuzz 所采取的各种方法实用性的假设。

本部分中的修剪策略统计数据与其他部分略有不同。这一行的第一个数字显示了从输入测试用例文件中移除的字节比例;第二个数字则对应达到这一目标所需的执行次数。最后,第三个数字显示了虽然无法移除但被认为没有影响,并且从一些成本更高的确定性模糊测试步骤中排除的字节比例。

请注意,当确定性变异模式关闭时(这是默认设置,因为它效率不高),前五行会显示“ 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 所采用的基于插桩引导方法所提供的价值的一个粗略指标。

下一个字段显示了尚未经过任何模糊测试的输入数量。对于模糊测试工具在当前队列周期中真正想要处理的“受青睐”条目,也给出了相同的统计数据(不受青睐的条目可能需要等待几个周期才能获得机会进行测试)。

接下来是在本模糊测试部分中发现的新路径数量,以及在进行并行模糊测试时从其他模糊测试工具实例中导入的路径数量;以及相同输入在测试二进制文件中有时似乎会产生不同行为的程度。

最后一项其实相当有趣:它衡量的是观测到的跟踪记录的一致性。如果程序对于相同的输入数据总是表现出相同的行为,那么它将获得 100% 的评分。当评分较低但仍显示为紫色时,模糊测试过程不太可能受到负面影响。如果评分进入红色区域,你可能就遇到麻烦了,因为 AFL++ 将难以区分输入文件调整所产生的有意义效果和“幻影”效果(即不受输入影响而是程序自身产生的可能是随机的效果)。

目前,大多数测试目标都会获得 100% 的评分,但当你看到较低的评分时,有几点需要注意:

检测到变量行为的路径在 <out_dir>/queue/.state/variable_behavior/ 目录中标记有相应的条目,因此您可以轻松查找。

CPU 负载

  [cpu: 25%]

这个小界面显示了本地系统的显式 CPU 利用率。它通过计算处于“可运行”状态的进程数量,并将其与系统上的逻辑 CPU 数量进行比较来得出。

如果值显示为绿色,说明您使用的 CPU 核心少于系统可用的核心,可能可以通过并行化来提高性能;有关如何实现这一点的提示,请参见 fuzzing_in_depth.md:3c) Using multiple cores

如果值显示为红色,说明您的 CPU 可能超额分配,运行额外的模糊测试工具可能不会带来任何好处。

当然,这个基准测试非常简单;它告诉您有多少进程准备运行,但并没有说明它们可能需要多少资源。它也没有区分物理核心、逻辑核心和虚拟 CPU;这些的性能特征可能会有很大不同。

如果您想要更准确的测量,可以从命令行运行 afl-gotcpu 工具。

理解输出

请参见 #理解状态界面 以获取有关如何解读显示的统计信息和监控进程正常状况的信息。如果任何 UI 元素以红色突出显示,请务必查阅此文件。

模糊测试过程将持续进行,直到您按下 Ctrl-C。至少,您希望模糊测试工具在没有任何新发现的情况下至少允许一次队列循环,这可能需要几个小时到一周左右的时间。

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

如果相关的执行路径涉及未在先前记录的故障中出现的任何状态转换,则崩溃和挂起被视为“独特的”。如果一个单一的错误可以通过多种方式触发,早期过程中的计数可能会膨胀,但这应该很快会减小。

崩溃和挂起的文件名与父级、非故障的队列条目相关联。这将有助于调试。

可视化

如果您安装了 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 服务器,您将能够基于这些指标进行监控、触发警报或执行操作(例如:对新构建的慢执行次数进行警报、崩溃阈值、上次崩溃发现以来的时间 > X 等)。

所选的指标是状态和绘图文件中所有指标的一个子集。列表如下: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。它们的定义可以在上面的附录中找到。

在使用带有 StatsD 的多个模糊测试实例时,强烈建议设置个性化配置(AFL_STATSD_TAGS_FLAVOR)以匹配您的 StatsD 服务器。这将允许您查看单个模糊测试工具的性能、检测不良实例、查看每种策略的进展等。