afl-fuzz 使用方法
AFL++ 是一种结合了极其简单但非常可靠的基于插桩的遗传算法的暴力模糊测试工具。它使用一种改进的边覆盖率形式,能够轻松捕捉到程序控制流中的细微的、局部性的变化。
注:如果您对 AFL++ 如何运作的最新深入分析感兴趣,我们推荐您阅读这篇博客文章:https://blog.ritsec.club/posts/afl-under-hood/
简而言之,afl-fuzz 使用的算法总体可以概括为:
-
将用户提供的初始测试用例加载到队列中。
-
从队列中取出下一个输入文件。
-
尝试将测试用例缩减到最小,在这个尺寸下,测试用例的输入不会改变所观测到的程序行为
-
使用经过平衡且深入研究过的多种传统模糊测试策略,对输入文件进行反复变异。
-
如果生成的任何变异导致插桩记录到了新的状态转换,则将变异后的输出作为新条目添加到队列中。
-
返回到步骤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 |
+--------------------------------------+
这一部分介绍一些关于目标二进制文件中嵌入的插桩所观察到的覆盖率的内容。
框中的第一行告诉您已经执行到了多少分支元组,以及位图可以容纳的总量。左边的数字描述了当前输入的情况;右边的数字是整个输入测试用例集的值。
要注意极端情况:
-
绝对数字低于200左右可能意味着以下三种情况之一:程序非常简单;它没有正确插桩(例如链接到了目标库的非插桩副本);或者它在您的输入测试用例上过早地失败了。模糊测试工具会尝试用粉红色标记这一点,以便让您知道。
-
百分比超过70%在极少数情况下可能发生在大量使用模板生成代码的非常复杂的程序中。因为高位图密度使得模糊测试工具更难可靠地辨别新的程序状态,所以我们建议使用
AFL_INST_RATIO=10
或类似的设置重新编译二进制文件并再次尝试(参见 env_variables.md )。模糊测试工具会用红色标记高百分比。除非您在模糊测试极其复杂的软件(如 v8、perl、ffmpeg ),否则您很可能永远看不到这种情况。
另一行处理的是在二进制文件中发现的元组命中次数的可变性。本质上,如果对于所有的输入尝试,每个被执行的分支总是被固定次数地执行,那么这将显示为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 |
+-------------------------------------+
这一部分深入展示了模糊测试工具当前正在做什么。它告诉您当前阶段的状态,可能是以下任何一个:
-
校准( calibration )- 一个预模糊测试阶段,用于检查执行路径以检测异常、建立基线执行速度等。每当有新发现时,都会非常短暂地执行。
-
修剪 L/S( trim L/S )- 另一个预模糊测试阶段,其中测试用例被修剪为仍然产生相同执行路径的最短部分。长度( L )和步进( S )通常与文件大小有关。
-
位翻转 L/S( bitflip L/S )- 确定性位翻转。在任何给定时间,有 L 个位被切换,以 S 位为增量遍历输入文件。当前的 L/S 变体是:
1/1
、2/1
、4/1
、8/8
、16/8
、32/8
。 -
算术 L/8( arith L/8 )- 确定性算术。模糊测试工具尝试向 8 位、16 位和 32 位的值中添加或减去小整数。步进始终是8位。
-
应关注值 L/8( interest L/8 )- 确定性值覆盖。模糊测试工具有一个已知的“值得关注的”8 位、 16 位和 32 位值的列表来尝试。步进是 8 位。
-
额外项( extras )- 确定性注入字典术语。这可以显示为“ user ”或“ auto ”,取决于模糊测试工具是使用用户提供的字典(
-x
)还是自动创建的字典。您还会看到“ over ”或“ insert ”,取决于字典单词是覆盖现有数据还是通过偏移剩余数据来插入以适应其长度。 -
混乱( havoc )- 一种带有堆叠随机调整的近似固定长度周期。此阶段尝试的操作包括位翻转、用随机和“值得关注的”整数进行覆盖、块删除、块复制,以及各种与字典相关的操作(如果首先提供了字典)。
-
拼接( splice )- 在第一个完整队列周期中没有新路径发现后采取的最后一招策略。它相当于“混乱”,但首先会将队列中两个随机输入在某个任意选择的中间点拼接在一起。
-
同步( sync )- 仅在使用
-M
或-S
设置时使用的一个阶段(参见 fuzzing_in_depth.md:3c) Using multiple cores )。不涉及实际的模糊测试,但工具会扫描其他模糊测试工具的输出,并根据需要导入测试用例。首次执行时,可能需要几分钟左右。
剩余字段应该相当直观:有当前阶段的执行计数进度指示、全局执行计数器和当前程序执行速度的基准。这可能会因测试用例的不同而有所波动,但理想情况下,基准在大部分时间应该超过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% 的评分,但当你看到较低的评分时,有几点需要注意:
-
在被测二进制文件中,未初始化内存的使用与某些固有的熵源相结合。这对 AFL 无害,但可能是安全漏洞的迹象。
-
尝试操纵持久性资源,如遗留的临时文件或共享内存对象。这通常是无害的,但你可能想要再次检查以确保程序没有过早退出。磁盘空间不足、SHM 句柄耗尽或其他全局资源不足也可能触发这种情况。
-
触发了实际设计为随机行为的功能。这通常是无害的。例如,在模糊测试 sqlite 时,像
select random();
这样的输入将触发可变的执行路径。 -
多个线程同时以半随机的顺序执行。当“稳定性”指标保持在 90% 左右或以上时,这是无害的,但如果不保持,就可能成为问题。可以尝试以下方法:
-
使用来自 instrumentation 目录的 afl-clang-fast - 它使用了一个线程本地跟踪模型,较少受到并发问题的影响。
-
查看目标是否可以在没有多线程的情况下编译或运行。常见的
./configure
选项包括--without-threads
、--disable-pthreads
或--disable-openmp
。 -
用 GNU Pth ( https://www.gnu.org/software/pth/ ) 替换 pthreads,这将允许您使用确定性调度器。
-
-
在持久模式下,“稳定性”指标的轻微下降是正常的,因为并不是所有代码在重新进入时行为相同;但大幅度下降可能意味着
__AFL_LOOP()
内的代码在后续迭代中没有正确执行(例如,由于状态的清理或重新初始化不完全),并且大部分模糊测试的努力可能白费。
检测到变量行为的路径在 <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。至少,您希望模糊测试工具在没有任何新发现的情况下至少允许一次队列循环,这可能需要几个小时到一周左右的时间。
在输出目录中会创建三个子目录,并实时更新:
-
queue/ - 每个独特执行路径的测试用例,以及用户提供的所有起始文件。这是合成的测试集。
在将此语料库用于其他目的之前,您可以使用 afl-cmin 工具将其缩小到更小的大小。该工具将找到提供等效边缘覆盖的小文件子集。
-
crashes/ - 导致被测试程序收到致命信号的唯一测试用例(例如,SIGSEGV、SIGILL、SIGABRT )。条目按收到的崩溃信号分组。
-
hangs/ - 导致被测试程序超时的去重测试用例。将某些情况分类为挂起的默认时间限制是 1 秒和
-t
参数值中的较大者。可以通过设置AFL_HANG_TMOUT
对该值进行微调,但这通常不需要。
如果相关的执行路径涉及未在先前记录的故障中出现的任何状态转换,则崩溃和挂起被视为“独特的”。如果一个单一的错误可以通过多种方式触发,早期过程中的计数可能会膨胀,但这应该很快会减小。
崩溃和挂起的文件名与父级、非故障的队列条目相关联。这将有助于调试。
可视化
如果您安装了 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 文件中以机器可读格式找到。包括:
- start_time - 表示 afl-fuzz 启动时间的 Unix 时间
- last_update - 与此文件最后更新相对应的 Unix 时间
- run_time - 到此文件最后更新为止的运行时间(以秒为单位)
- fuzzer_pid - 模糊测试进程的 PID
- cycles_done - 到目前为止完成的队列循环次数
- cycles_wo_finds - 没有发现新路径的循环次数
- time_wo_finds - 最长未发现新路径的时间(以秒为单位)
- execs_done - 尝试的 execve() 调用次数
- execs_per_sec - 每秒的整体执行次数
- corpus_count - 队列中条目的总数
- corpus_favored - 被优先考虑的队列条目数量
- corpus_found - 通过本地模糊测试发现的条目数量
- corpus_imported - 从其他实例导入的条目数量
- max_depth - 生成的数据集中的层级数量
- cur_item - 当前处理的条目编号
- pending_favs - 仍然等待模糊测试的优先条目数量
- pending_total - 等待模糊测试的所有条目数量
- corpus_variable - 显示变量行为的测试用例数量
- stability - 一致性位图字节的百分比
- bitmap_cvg - 到目前为止在映射中找到的边缘覆盖的百分比
- saved_crashes - 记录的唯一崩溃数量
- saved_hangs - 遇到的唯一挂起数量
- last_find - 自上次发现以来的秒数
- last_crash - 自上次崩溃以来的秒数
- last_hang - 自上次挂起以来的秒数
- execs_since_crash - 自上次崩溃以来的执行次数
- exec_timeout - -t 命令行值
- slowest_exec_ms - 最慢执行的实际时间(以毫秒为单位)
- peak_rss_mb - 模糊测试期间达到的最大 RSS 使用量(以 MB 为单位)
- edges_found - 找到的边缘数量
- var_byte_count - 非确定性边缘的数量
- afl_banner - 横幅文本(例如,目标名称)
- afl_version - 使用的 AFL++ 版本
- target_mode - 默认模式、持久模式、qemu 模式、unicorn 模式、未插桩模式
- command_line - 用于模糊测试会话的完整命令行
大多数这些信息直接对应于之前讨论的 UI 元素。
此外,您还可以找到一个名为 plot_data
的条目,其中包含大多数字段的可绘制历史记录。如果您安装了 gnuplot
,可以使用随附的 afl-plot
工具将其转化为一个漂亮的进度报告。
附录:使用 StatsD 自动发送指标
在 CI 环境中或运行多个模糊测试工具时,加载每个实例或部署脚本以读取模糊测试统计信息可能很繁琐。使用 AFL_STATSD
(以及其他相关环境变量 AFL_STATSD_HOST
、AFL_STATSD_PORT
、AFL_STATSD_TAGS_FLAVOR
),您可以自动将指标发送到您喜欢的 StatsD 服务器。根据您的 StatsD 服务器,您将能够基于这些指标进行监控、触发警报或执行操作(例如:对新构建的慢执行次数进行警报、崩溃阈值、上次崩溃发现以来的时间 > X 等)。
所选的指标是状态和绘图文件中所有指标的一个子集。列表如下:cycle_done
、cycles_wo_finds
、execs_done
、execs_per_sec
、corpus_count
、corpus_favored
、corpus_found
、corpus_imported
、max_depth
、cur_item
、pending_favs
、pending_total
、corpus_variable
、saved_crashes
、saved_hangs
、total_crashes
、slowest_exec_ms
、edges_found
、var_byte_count
、havoc_expansion
。它们的定义可以在上面的附录中找到。
在使用带有 StatsD 的多个模糊测试实例时,强烈建议设置个性化配置(AFL_STATSD_TAGS_FLAVOR
)以匹配您的 StatsD 服务器。这将允许您查看单个模糊测试工具的性能、检测不良实例、查看每种策略的进展等。