mirrorer/afl
GitHub: mirrorer/afl
AFL 是一款基于覆盖率引导的模糊测试工具,通过遗传算法和程序插桩高效发现软件中的安全漏洞和稳定性问题。
Stars: 609 | Forks: 442
# ==================
american fuzzy lop
由 Michal Zalewski 撰写和维护
版权所有 2013, 2014, 2015, 2016 Google Inc. 保留所有权利。
根据 Apache License, Version 2.0 的条款和条件发布。
有关新版本和更多信息,请查看:
http://lcamtuf.coredump.cx/afl/
要与其他用户交流心得或获取主要新功能的通知,
请发送邮件至 。
** 如果您没有时间阅读此文件,请参阅 QuickStartGuide.txt。 **
1. 引导式 Fuzzing 的挑战
Fuzzing 是识别现实世界软件中安全问题最强大且经过验证的策略之一;迄今为止,在安全关键型软件中发现的大部分远程代码执行和权限提升漏洞都归功于它。
不幸的是,Fuzzing 也相对浅显;盲目的随机变异使其极不可能到达被测代码中的某些代码路径,导致某些漏洞完全无法通过这种技术触及。
人们已经做出了许多尝试来解决这个问题。Tavis Ormandy 开创的早期方法之一是语料库蒸馏 (corpus distillation)。该方法依赖于覆盖率信号,从海量、高质量的候选文件语料库中选择一部分有趣的种子,然后通过传统手段对它们进行 fuzz。这种方法效果非常好,但需要这样一个语料库是现成可用的。此外,基本块覆盖率测量对程序状态的理解非常简单,从长远来看,对于引导 fuzzing 工作不太有用。
其他更复杂的研究集中在诸如程序流分析("concolic execution")、符号执行或静态分析等技术上。所有这些方法在实验环境中都非常有前景,但在实际使用中往往存在可靠性和性能问题——目前还不能提供可行的替代“dumb” fuzzing 技术的方案。
2. afl-fuzz 的方法
American Fuzzy Lop 是一个暴力 fuzzer, coupled with an exceedingly simple but rock-solid instrumentation-guided genetic algorithm。它使用一种修改形式的边缘覆盖率 (edge coverage) 来轻松捕捉程序控制流的细微、局部变化。
稍微简化一下,整体算法可以概括为:
1. 将用户提供的初始测试用例加载到队列中,
2. 从队列中取出下一个输入文件,
3. 尝试将测试用例修剪为不改变程序测量行为的最小尺寸,
4. 使用平衡且经过充分研究的各种传统 fuzzing 策略反复变异文件,
5. 如果任何生成的变异导致了插桩记录的新状态转换,则将变异输出作为新条目添加到队列中。
6. 回到 2。
发现的测试用例也会定期剔除,以消除那些被更新的、覆盖率更高的发现所淘汰的用例;并经历其他几个由插桩驱动的工作量最小化步骤。
作为 fuzzing 过程的副作用,该工具创建了一个小型的、自包含的有趣测试用例语料库。这些对于通过种子引导其他劳动或资源密集型的测试机制非常有用——例如,用于对浏览器、办公应用程序、图形套件或闭源工具进行压力测试。
该 fuzzer 经过全面测试,可提供远优于盲目 fuzzing 或仅覆盖率工具的开箱即用性能。
3. 为使用 AFL 插桩程序
当源代码可用时,可以通过一个配套工具注入插桩,该工具可作为第三方代码任何标准构建过程中 gcc 或 clang 的直接替代品。
插桩的性能影响相当温和;结合 afl-fuzz 实现的其他优化,大多数程序的 fuzzing 速度可以与传统工具一样快,甚至更快。
重新编译目标程序的正确方法可能因构建过程的具体情况而异,但一种几乎通用的方法是:
$ CC=/path/to/afl/afl-gcc ./configure
$ make clean all
对于 C++ 程序,您还需要设置 CXX=/path/to/afl/afl-g++。
clang 包装器(afl-clang 和 afl-clang++)可以以相同的方式使用;clang 用户也可以选择利用更高性能的插桩模式,如 llvm_mode/README.llvm 中所述。
在测试库时,您需要找到或编写一个简单的程序,该程序从 stdin 或文件读取数据并将其传递给被测库。在这种情况下,必须将此可执行文件链接到插桩库的静态版本,或者确保在运行时加载正确的 .so 文件(通常通过设置 LD_LIBRARY_PATH)。最简单的选择是静态构建,通常可以通过以下方式实现:
$ CC=/path/to/afl/afl-gcc ./configure --disable-shared
在调用 'make' 时设置 AFL_HARDEN=1 将导致 CC wrapper 自动启用代码加固选项,从而更容易检测简单的内存错误。AFL 附带的辅助库 Libdislocator(参见 libdislocator/README.dislocator)也可以帮助发现堆破坏问题。
PS. 建议使用 ASAN 的用户查看 notes_for_asan.txt 文件以了解重要的注意事项。
4. 插桩仅二进制应用程序
当源代码 *不可用* 时,fuzzer 提供了对黑盒二进制文件进行快速、即时插桩的实验性支持。这是通过以鲜为人知的“用户空间模拟”模式运行的 QEMU 版本完成的。
QEMU 是独立于 AFL 的项目,但您可以通过以下操作方便地构建该功能:
$ cd qemu_mode
$ ./build_qemu_support.sh
有关其他说明和注意事项,请参阅 qemu_mode/README.qemu。
该模式比编译时插桩慢大约 2-5 倍,不太利于并行化,并且可能有一些其他怪癖。
5. 选择初始测试用例
为了正确运行,fuzzer 需要一个或多个起始文件,其中包含目标应用程序通常预期的输入数据的良好示例。有两个基本规则:
- 保持文件小。1 kB 以下是理想的,尽管不是严格必须的。
有关大小为何重要的讨论,请参阅 perf_tips.txt。
- 仅当多个测试用例在功能上彼此不同时才使用它们。使用五十张不同的度假照片来 fuzz 图像库是没有意义的。
您可以在随此工具附带的 testcases/ 子目录中找到许多起始文件的良好示例。
PS. 如果有大量语料库可用于筛选,您可能需要使用 afl-cmin 工具来识别一部分功能不同的文件,这些文件在目标二进制文件中执行不同的代码路径。
6. Fuzzing 二进制文件
Fuzzing 过程本身由 afl-fuzz 工具执行。该程序需要一个包含初始测试用例的只读目录,一个单独的存放发现结果的地方,加上要测试的二进制文件的路径。
对于直接从 stdin 接受输入的目标二进制文件,通常的语法是:
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program [...params...]
对于从文件获取输入的程序,使用 '@@' 标记目标命令行中输入文件名应放置的位置。Fuzzer 将为您替换此项:
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@
您也可以使用 -f 选项将变异数据写入特定文件。如果程序需要特定的文件扩展名等,这很有用。
非插桩二进制文件可以在 QEMU 模式下(在命令行中添加 -Q)或在传统的盲 fuzzer 模式下(指定 -n)进行 fuzz。
您可以使用 -t 和 -m 覆盖已执行进程的默认超时和内存限制;可能需要调整这些设置的罕见目标示例包括编译器和视频解码器。
有关优化 fuzzing 性能的技巧在 perf_tips.txt 中讨论。
请注意,afl-fuzz 首先执行一系列确定性 fuzzing 步骤,这可能需要几天时间,但往往会产生整洁的测试用例。如果您想立即获得快速但粗糙的结果——类似于 zzuf 和其他传统 fuzzer——请在命令行中添加 -d 选项。
7. 解读输出
有关如何解读显示的统计数据和监控进程健康状况的信息,请参阅 status_screen.txt 文件。特别是当任何 UI 元素以红色突出显示时,请务必查阅此文件。
Fuzzing 过程将持续进行,直到您按下 Ctrl-C。至少,您应该允许 fuzzer 完成一个队列周期,这可能需要几个小时到一周左右的时间。
在输出目录中创建了三个子目录并实时更新:
- queue/ - 每个独特执行路径的测试用例,加上用户提供的所有起始文件。这就是第 2 节中提到的合成语料库。
在将此语料库用于任何其他目的之前,您可以使用 afl-cmin 工具将其缩小到更小的尺寸。该工具将找到提供等效边缘覆盖率的更小子集文件。
- crashes/ - 导致被测程序接收到致命信号(例如 SIGSEGV, SIGILL, SIGABRT)的独特测试用例。条目按接收到的信号分组。
- hangs/ - 导致被测程序超时的独特测试用例。在被归类为挂起之前的默认时间限制是 1 秒和 -t 参数值中的较大者。
该值可以通过设置 AFL_HANG_TMOUT 进行微调,但这很少是必须的。
如果相关的执行路径涉及先前记录的故障中未见的状态转换,则崩溃和挂起被视为“独特的”。如果单个错误可以通过多种方式到达,则在过程早期会有一些计数膨胀,但这应该会迅速减少。
崩溃和挂起的文件名与父级、非故障队列条目相关联。这应该有助于调试。
当您无法重现 afl-fuzz 发现的崩溃时,最可能的原因是您没有设置与该工具使用的相同的内存限制。请尝试:
$ LIMIT_MB=50
$ ( ulimit -Sv $[LIMIT_MB << 10]; /path/to/tested_binary ... )
更改 LIMIT_MB 以匹配传递给 afl-fuzz 的 -m 参数。在 OpenBSD 上,还需将 -Sv 更改为 -Sd。
任何现有的输出目录也可用于恢复中止的作业;请尝试:
$ ./afl-fuzz -i- -o existing_output_dir [...etc...]
如果您安装了 gnuplot,您还可以使用 afl-plot 为任何活动的 fuzzing 任务生成一些漂亮的图表。有关其外观示例,请参见 http://lcamtuf.coredump.cx/afl/plot/。
8. 并行化 Fuzzing
afl-fuzz 的每个实例大约占用一个核心。这意味着在多核系统上,并行化是充分利用硬件所必需的。
有关如何在多个核心或多台联网机器上对公共目标进行 fuzzing 的技巧,请参阅 parallel_fuzzing.txt。
并行 fuzzing 模式还提供了一种将 AFL 与其他 fuzzer、符号或 concolic 执行引擎等接口的简单方法;同样,请参阅 parallel_fuzzing.txt 的最后一部分以获取技巧。
9. Fuzzer 字典
默认情况下,afl-fuzz 变异引擎针对紧凑的数据格式进行了优化——例如图像、多媒体、压缩数据、正则表达式语法或 shell 脚本。它不太适用于特别冗长和多余的冗长语言—— notably 包括 HTML、SQL 或 JavaScript。
为了避免构建语法感知工具的麻烦,afl-fuzz 提供了一种方法,可以使用与目标数据类型相关的语言关键字、魔术头或其他特殊标记的可选字典为 fuzzing 过程提供种子——并使用它来即时重建底层语法:
http://lcamtuf.blogspot.com/2015/01/afl-fuzz-making-up-grammar-with.html
要使用此功能,您首先需要以 dictionaries/README.dictionaries 中讨论的两种格式之一创建字典;然后通过命令行中的 -x 选项将其指向 fuzzer。
(该子目录中也提供了几个常用字典。)
无法提供对底层语法的更结构化描述,但 fuzzer 可能会仅根据插桩反馈推断出其中一些。这在实践中确实有效,例如:
http://lcamtuf.blogspot.com/2015/04/finding-bugs-in-sqlite-easy-way.html
PS. 即使没有给出明确的字典,afl-fuzz 也会尝试通过在确定性字节翻转期间非常仔细地观察插桩来提取输入语料库中现有的语法标记。这对某些类型的解析器和语法有效,但不如 -x 模式好。
如果真的很难找到字典,另一种选择是让 AFL 运行一段时间,然后使用作为 AFL 配套工具附带的 token 捕获库。有关内容,请参阅 libtokencap/README.tokencap。
10. 崩溃分类
基于覆盖率的崩溃分组通常会产生一个小的数据集,可以快速手动分类或使用非常简单的 GDB 或 Valgrind 脚本进行分类。
每个崩溃也可以追溯到队列中其父级非崩溃测试用例,从而更容易诊断故障。
话虽如此,重要的是要承认,如果不进行大量调试和代码分析工作,某些 fuzzing 崩溃可能难以快速评估可利用性。为了协助完成此任务,afl-fuzz 支持一种非常独特的“崩溃探索”模式,该模式通过 -C 标志启用。
在此模式下,fuzzer 将一个或多个崩溃测试用例作为输入,并使用其反馈驱动的 fuzzing 策略非常快速地枚举程序中所有可以在保持崩溃状态的同时到达的代码路径。
不会导致崩溃的变异将被拒绝;任何不影响执行路径的更改也将被拒绝。
输出是一个小型的文件语料库,可以非常快速地检查攻击者对故障地址的控制程度,或者是否可能越过最初的越界读取——并查看其下方的内容。
哦,还有一件事:对于测试用例最小化,请尝试 afl-tmin。该工具可以非常简单地操作:
$ ./afl-tmin -i test_case -o minimized_result -- /path/to/program [...]
该工具适用于崩溃和非崩溃测试用例。在崩溃模式下,它将愉快地接受插桩和非插桩二进制文件。在非崩溃,最小化器依赖标准 AFL 插桩来简化文件而不改变执行路径。
最小化器以与 afl-fuzz 兼容的方式接受 -m, -t, -f 和 @@ 语法。
AFL 的另一个新增功能是 afl-analyze 工具。它获取一个输入文件,尝试按顺序翻转字节,并观察被测程序的行为。然后,它根据哪些部分看起来至关重要,哪些不重要,对输入进行颜色编码;虽然不是万无一失的,但它通常可以快速深入了解复杂的文件格式。有关其操作的更多信息可以在 technical_details.txt 的末尾找到。
11. 超越崩溃
Fuzzing 也是发现非崩溃设计和实现错误的一种极好且未被充分利用的技术。通过修改目标程序以在以下情况下调用 abort(),已经发现了相当多有趣的错误:
- 两个大数库在给定相同的 fuzzer 生成输入时产生不同的输出,
- 一个图像库在被要求连续多次解码同一输入图像时产生不同的输出,
- 一个序列化/反序列化库在对 fuzzer 提供的数据进行迭代序列化和反序列化时未能产生稳定的输出,
- 一个压缩库在被要求压缩然后解压特定的 blob 时产生与输入文件不一致的输出。
实现这些或类似的健全性检查通常花费很少的时间;如果您是特定包的维护者,您可以使用 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION(也是与 libfuzzer 共享的标志)或 #ifdef __AFL_COMPILER(仅适用于 AFL)来使此代码成为条件代码。
12. 常识性风险
请记住,与许多其他计算密集型任务类似,fuzzing 可能会对您的硬件和操作系统造成压力。特别是:
- 您的 CPU 将运行发热,并且需要足够的冷却。在大多数情况下,如果冷却不足或停止正常工作,CPU 速度将自动降低。话虽如此,特别是在不太合适的硬件(笔记本电脑、智能手机等)上进行 fuzzing 时,某些东西爆炸并非完全不可能。
- 目标程序最终可能会不稳定地占用大量内存或用垃圾文件填满磁盘空间。AFL 试图强制执行基本的内存限制,但无法阻止每一个可能的事故。底线是,您不应该在无法接受数据丢失风险的系统上进行 fuzzing。
- Fuzzing 涉及数十亿次的文件系统读取和写入。在现代系统上,这通常会被大量缓存,导致相当温和的“物理”I/O——但有许多因素可能会改变这个等式。
您有责任监控潜在的麻烦;在非常繁重的 I/O 下,许多 HDD 和 SSD 的寿命可能会缩短。
在 Linux 上监控磁盘 I/O 的一个好方法是 'iostat' 命令:
$ iostat -d 3 -x -k [...optional disk ID...]
13. 已知限制与改进领域
以下是 AFL 的一些最重要注意事项:
- AFL 通过检查第一个生成的进程是否因信号(SIGSEGV、SIGABRT 等)死亡来检测故障。为这些信号安装自定义处理程序的程序可能需要注释掉相关代码。同样,除非您手动添加一些代码来捕获,否则由 fuzzed 目标生成的子进程中的故障可能会逃避检测。
- 与任何其他暴力工具一样,如果使用加密、校验和、加密签名或压缩来完全包装要测试的实际数据格式,fuzzer 提供的覆盖率有限。
要解决此问题,您可以注释掉相关的检查(参见 experimental/libpng_no_checksum/ 获取灵感);如果这不可能,您也可以编写后处理器,如 experimental/post_library/ 中所述。
- ASAN 和 64 位二进制文件之间存在一些不幸的权衡。这不是由于 afl-fuzz 的任何特定错误;请参阅 notes_for_asan.txt 获取技巧。
- 没有直接支持 fuzzing 网络服务、后台守护程序或需要 UI 交互才能工作的交互式应用程序。您可能需要进行简单的代码更改,使它们以更传统的方式运行。Preeny 也可能提供一个相对简单的选项——请参见:
https://github.com/zardus/preeny
有关修改基于网络的服务的一些有用技巧也可以在以下位置找到:
https://www.fastly.com/blog/how-to-fuzz-server-american-fuzzy-lop
- AFL 不输出人类可读的覆盖率数据。如果您想监控覆盖率,请使用 Michael Rash 的 afl-cov:https://github.com/mrash/afl-cov
- 偶尔,有知觉的机器会反抗它们的创造者。如果这种情况发生在您身上,请查阅 http://lcamtuf.coredump.cx/prep/。
除此之外,请参阅 INSTALL 获取特定于平台的提示。
14. 特别感谢
如果没有以下人员的反馈、错误报告或补丁,afl-fuzz 的许多改进将是不可能的:
Jann Horn Hanno Boeck
Felix Groebert Jakub Wilk
Richard W. M. Jones Alexander Cherepanov
Tom Ritter Hovik Manucharyan
Sebastian Roschke Eberhard Mattes
Padraig Brady Ben Laurie
@dronesec Luca Barbato
Tobias Ospelt Thomas Jarosch
Martin Carpenter Mudge Zatko
Joe Zbiciak Ryan Govostes
Michael Rash William Robinet
Jonathan Gray Filipe Cabecinhas
Nico Weber Jodie Cunningham
Andrew Griffiths Parker Thompson
Jonathan Neuschfer Tyler Nighswander
Ben Nagy Samir Aguiar
Aidan Thornton Aleksandar Nikolich
Sam Hakim Laszlo Szekeres
David A. Wheeler Turo Lamminen
Andreas Stieger Richard Godbee
Louis Dassy teor2345
Alex Moneger Dmitry Vyukov
Keegan McAllister Kostya Serebryany
Richo Healey Martijn Bogaard
rc0r Jonathan Foote
Christian Holler Dominique Pelle
Jacek Wielemborek Leo Barnes
Jeremy Barnes Jeff Trull
Guillaume Endignoux ilovezfs
Daniel Godas-Lopez Franjo Ivancic
Austin Seipp Daniel Komaromy
Daniel Binderman Jonathan Metzman
Vegard Nossum Jan Kneschke
Kurt Roeckx Marcel Bohme
Van-Thuan Pham Abhik Roychoudhury
Joshua J. Drake Toby Hutton
Rene Freingruber Sergey Davidoff
Sami Liedes Craig Young
Andrzej Jackowski Daniel Hodson
谢谢!
15. 联系方式
有问题?疑虑?错误报告?通常可以通过 联系作者。
该项目还有一个邮件列表;要加入,请发送邮件至 。或者,如果您想先浏览存档,请尝试:
https://groups.google.com/group/afl-users
PS. 如果您希望提交原始代码以合并到项目中,请注意 AFL 的大部分版权归 Google 所有。虽然您确实保留对您的贡献的版权,但他们确实要求人们先同意一个简单的 CLA:
https://cla.developers.google.com/clas
抱歉给您带来麻烦。当然,功能请求或错误报告不需要 CLA。
标签:AFL, american fuzzy lop, C/C++, Fuzzer, TLS抓取, 事务性I/O, 代码安全, 代码生成, 安全测试, 客户端加密, 客户端加密, 攻击性安全, 文档安全, 模糊测试框架, 渗透测试工具, 漏洞枚举, 灰盒测试, 覆盖率引导, 误配置预防, 身份验证强制, 软件安全, 逆向工具, 配置审计