Historical
原文源自 Historical Notes。
本文讨论了 AFL(American Fuzzy Lop)一些高级设计决策的合理性。这些内容源自与 Rob Graham 的讨论。
Influence
简而言之,afl-fuzz 主要受到 Tavis Ormandy 在 2007 年所做工作的启发。Tavis 进行了一些极具说服力的实验,使用 gcov 块覆盖率从大量数据集中选择最优测试用例,并将这些测试用例作为传统模糊测试工作流程的起点。
所谓“有说服力“的意思是:捕获大量有趣的漏洞。
与此同时,AFL 的作者 (后面简称为 “我”) 和 Tavis 都对进化式模糊测试感兴趣。Tavis 有自己的实验,我而则在开发一个名为 bunny-the-fuzzer 的工具,该工具发布于 2007 年左右。
Bunny 使用了一种与 afl-fuzz 差异不大的代际算法,但也试图推理各种输入位与程序内部状态之间的关系,希望能从中获得一些额外价值。推理/关联部分可能部分受到了 Will Drewry 和 Chris Evans 在同一时期进行的其他项目的影响。
状态关联方法在理论上听起来非常诱人,但最终使模糊测试器变得复杂、脆弱且难以使用;每个其他目标程序都需要进行一些调整。由于 Bunny 的表现并不比更简单的暴力工具好多少,我最终决定放弃它。你仍然可以在以下链接找到它的原始文档:
https://code.google.com/p/bunny-the-fuzzer/wiki/BunnyDoc
在那时也有不少独立工作。最值得注意的是,在当年,Jared DeMott 有一个关于基于覆盖率的模糊器的 Defcon 演讲,该模糊器依赖于覆盖率作为适应度函数。
Jared 的方法与 afl-fuzz 的做法并不完全相同,但大体上属于同一范畴。他的模糊器试图通过单个输入文件明确求解最大覆盖率;相比之下,afl 只是选择那些能做新事情的情况(这能产生更好的结果——参见 Technical Details)。
几年后,Gabriel Campana 发布了 fuzzgrind,这是一个纯粹依赖 Valgrind 和约束求解器来最大化覆盖率而无需任何暴力搜索的工具;微软研究院的人则广泛讨论了他们尚未公开的、基于求解器的 SAGE 框架。
在过去六七年里,我也看到了不少关于智能模糊测试(主要关注符号执行)的学术论文,还有几篇讨论了遗传算法的概念验证应用,目标都是一致的。我并不认为这些实验大多有多实用;我怀疑其中许多受到了“bunny-the-fuzzer”诅咒的影响,在纸面上和精心设计的实验中看起来很酷,但在最终测试中却无法在经过良好模糊测试的真实世界软件中发现新的、有价值的漏洞。
在某些方面,“酷炫”解决方案需要与之竞争的基准比看起来要令人印象深刻得多,这使得竞争对手难以脱颖而出。以一个单一的例子来说,可以看看 Gynvael 和 Mateusz Jurczyk 的工作,他们将“笨拙”的模糊测试应用于 ffmpeg——现代浏览器和媒体播放器中一个重要且关键的组件:
http://googleonlinesecurity.blogspot.com/2014/01/ffmpeg-and-thousand-fixes.html
在同等复杂度的软件中,毫不费力地获得与最先进的符号执行相当的结果似乎仍然相当不可能,到目前为止在实践中也未被证明。
但我跑题了;归根结底,归因很难,而赞美 AFL 背后的基本概念可能是在浪费时间。关键往往在于那些常被忽视的细节,这带我们来到…
Design goals for afl-fuzz
简而言之,我认为当前的 afl-fuzz 实现解决了其他工具似乎无法解决的几个问题:
Speed
当你的“聪明“方案需要耗费大量资源时,想要靠蛮力取胜确实很难。如果你的检测手段能将发现漏洞的概率提高 10 倍,但运行速度却慢了 100 倍,那对用户来说就是亏本买卖。
为了避免一开始就处于劣势,afl-fuzz 的设计目的是让你以接近其原生速度的程度对大部分目标进行模糊测试——即使它没有增加价值,你也不会损失太多。
此外,该工具利用代码插装技术,通过多种方式实际减少工作量:例如,通过仔细修剪语料库或在输入文件中跳过非功能性但不可修剪的区域。
Rock-solid Reliability
如果你的方法脆弱且容易意外失败,就很难与“蛮力”竞争。自动化测试之所以有吸引力,是因为它简单易用且可扩展;任何违背这些原则的做法都是一种得不偿失的权衡,意味着你的工具会被更少使用,结果也会更不稳定。
目前 (作者当时),大多数基于符号执行、污点跟踪或复杂语法感知插桩的方法,在面对真实目标时都相当不可靠。更重要的是,它们的失败模式可能让它们比“笨办法”工具还要糟糕,而这种退化对于经验不足的用户来说往往难以察觉和纠正。
相比之下,afl-fuzz 的设计目标就是坚如磐石,主要方法就是保持简单。 实际上,它的核心就是一个非常优秀的传统模糊测试器,并采用了一系列经过深入研究、效果显著的策略。那些“高级”的部分,只是帮助它把精力集中在最重要的地方。
Simplicity
测试框架的作者,可能是唯一真正理解该工具所有设置项影响的人——也是唯一能精准调校这些参数的人。 然而,即便最基础的模糊测试框架,也往往附带无数需要操作者在测试前自行猜测的调节旋钮和模糊测试比例。这种设计往往弊大于利。
AFL 的设计目标正是尽可能避免这种情况。 你真正需要调整的只有三个参数:输出文件路径、内存限制,以及是否覆盖默认的自动校准超时设置。其余部分理应即开即用。如果出现问题,友好清晰的错误提示会直接指出可能原因和解决方案,让你迅速回到正轨。
Chainability
大多数通用模糊测试工具难以直接用于资源密集型或强交互型目标——要么得专门开发进程内定制化模糊器,要么就得投入巨额算力(其中大部分都浪费在与待测代码无关的任务上)。
AFL 则另辟蹊径:它允许用户针对轻量级目标(比如独立的图像解析库)生成精简的高价值测试用例集,这些用例后续既能接入人工测试流程,也能配合 UI 测试框架使用。
正如 Technical Details 中所述,AFL 的卓越表现并非源自对某个单一计算机科学理论体系的系统化应用,而是通过不断尝试各种小型、互补的技术方法——这些方法经实践验证能稳定产生超越随机猜测的成果。代码插桩只是这个工具箱中的一部分,但远非最重要的组成部分。
归根结底,关键在于 afl-fuzz 的设计初衷就是发现那些“酷炫“的漏洞,而且它确实有着相当可靠的实绩证明这一点。