新消息:如果你想尝试使用 TriforceAFL 和 TLSF,Richard Johnson 创建了一个 Dockerfile,可以同时安装两者(甚至为你构建一个 Linux 内核)。可在此处获取 https://hub.docker.com/r/moflow/afl-triforce/tags/。
另一项新消息:afl-tmin 现在支持 forkserver 了!
https://github.com/nccgroup/TriforceAFL Jesse Hertz [email protected] Tim Newsham [email protected]
这是 AFL 的一个补丁版本,支持使用 QEMU 进行全系统模糊测试。附带的 QEMU 已更新,可在 x86_64 系统模拟器运行时跟踪分支。新增了额外指令,用于启动 AFL 的 forkserver、进行模糊测试设置,以及标记测试用例的开始和结束。
注意:并非所有 AFL 工具都经过了新变更的测试。以下工具经过了部分测试:
构建:
make
获取覆盖图:
echo hello > /tmp/hello
./afl-showmap -o coverage.txt -QQ --
./afl-qemu-system-trace -kernel ../bzImage
-initrd ../initramfs.cpio.gz -m 1G -nographic
-append "console=ttyS0" -aflFile /tmp/hello
cat coverage.txt
进行模糊测试:
egrep ' (panic|log_store)$' ../mykern/kallsyms ffffffff8108e570 t log_store ffffffff8181064b T panic
mkdir inputs
echo hello > inputs/hello
./afl-fuzz -i inputs -o outputs -QQ --
afl-qemu-system-trace -kernel bzImage -initrd root.cpio.gz
-m 1G -nographic -append "console=ttyS0"
-aflPanicAddr ffffffff8181064b -aflDmesgAddr ffffffff8108e570
-aflFile @@
(注意:与使用 "-Q" 选项时不同,使用 "-QQ" 选项时必须指定 afl-qemu-system-trace 的完整命令行)。
有关如何使用此修改版 AFL 的更多详细信息, 请参阅我们的 Linux 系统调用模糊测试器: https://github.com/nccgroup/TriforceLinuxSyscallFuzzer。
新的 AFL 标志: -QQ - 使用 qemu 进行全系统模拟,而非用户模式(-Q)
新的 QEMU 标志: -aflFile - 包含模糊测试器输入的文件名 -aflPanicAddr - 用于 panic 检测的内核 panic 地址 -aflDmesgAddr - dmesg 日志函数的 Linux 内核地址,用于 检测日志记录并拦截日志消息
新的 QEMU 指令: 0f 24 - aflCall edi=1 startForkserver(esi=enableTicks) 启动 AFL 的 fork server。此后,每个测试用例将在独立的 fork 子进程中运行。 如果 enableTicks 非零,QEMU 在 fork 子进程后将重新启用 CPU 定时器, 否则不会启用。 edi=2 getWork(esi=ptr, edx=sz) 用下一个输入测试用例填充 ptr[0..sz]。返回实际填充的大小(<= sz)。 edi=3 startWork(esi=ptr) 告诉 AFL 开始跟踪。该参数指向一个包含两个四字(quadword)的缓冲区, 分别给出要跟踪代码的起始和结束地址。此范围之外的指令不会被跟踪。 edi=4 doneWork(esi=exitCode) 告诉 AFL 测试用例已完成。如果检测到 panic,AFL 将立即停止测试用例; 否则将一直运行到调用 doneWork 为止。指定的 exitCode 会返回给 AFL。 (代码可以——但目前不会——在测试用例期间检测到任何 dmesg 日志时, 对所有退出码按位或上值 64。)
新的 QEMU 块驱动程序: -drive filename=privmem: 该块驱动程序将驱动器的镜像保存在写时复制(copy-on-write)内存中, 因此更改永远不会持久化到磁盘。某个测试用例所做的更改与其他测试用例隔离。
由 Michal Zalewski [email protected] 编写和维护
版权所有 2013, 2014, 2015, 2016 Google Inc. 保留所有权利。 根据 Apache License 2.0 版本的条款和条件发布。
如需新版本和更多信息,请查看: http://lcamtuf.coredump.cx/afl/
要与其他用户交流心得或获取重大新功能通知, 请发送邮件至 [email protected]。
** 如果你没有时间阅读本文件,请参阅 QuickStartGuide.txt。**
模糊测试(Fuzzing)是识别真实软件中安全问题的最强大、最成熟的策略之一;迄今为止,在安全关键型软件中发现的大多数远程代码执行和权限提升漏洞都归功于它。
遗憾的是,模糊测试也相对浅显;盲目、随机的变异方式很难触及被测代码中的某些代码路径,这使得一些漏洞完全超出了该技术的覆盖范围。
人们曾多次尝试解决这个问题。早期方法之一是由 Tavis Ormandy 率先提出的语料库精炼(corpus distillation)。该方法依靠覆盖率信号,从大量高质量的候选文件语料库中选出一组有意义的种子,然后用传统方式进行模糊测试。这种方法效果非常好,但需要现成可用的语料库。此外,块覆盖率测量只能提供对程序状态非常简单的理解,长期来看对引导模糊测试工作的帮助有限。
其他更复杂的研究则侧重于程序流分析(“concolic execution”)、符号执行或静态分析等技术。所有这些方法在实验环境中都极具前景,但在实际应用中往往存在可靠性和性能问题——目前还无法成为“盲目”模糊测试技术的可行替代方案。
American Fuzzy Lop 是一款暴力模糊测试器,结合了一种极其简单但非常可靠的、由插桩引导的遗传算法。它使用改进形式的边覆盖(edge coverage),能够轻松捕捉程序控制流中细微的局部变化。
简单来说,整体算法可以概括为:
对于已发现的测试用例,还会定期进行清理,剔除那些已被更新、覆盖率更高的结果所取代的条目;此外,还会执行其他几项由插桩驱动的精简优化步骤。
作为模糊测试过程的附带成果,该工具会生成一个小型、自包含的有价值测试用例语料库。这些用例对于为其他需要大量人力或资源的测试方案提供种子输入极为有用——例如,对浏览器、办公应用、图形套件或闭源工具进行压力测试。
该模糊测试器经过全面测试,开箱即用的性能远优于盲目模糊测试或仅基于覆盖率的工具。
当源代码可用时,可以通过一个配套工具注入插桩,该工具可在任何第三方代码的标准构建过程中作为 gcc 或 clang 的直接替代品使用。
该插桩带来的性能影响相当有限;结合 afl-fuzz 实现的其他优化,大多数程序的模糊测试速度都可以达到甚至超过传统工具。
重新编译目标程序的正确方法可能因构建过程的具体情况而异,但一个几乎通用的方法是:
$ 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 包装器自动启用代码加固选项,从而更容易检测简单的内存错误。
附:建议 ASAN 用户查阅 notes_for_asan.txt 文件,了解重要的注意事项。
当源代码 不可用 时,模糊测试器提供对黑盒二进制文件进行快速、即时插桩的实验性支持。这是通过运行在不太为人所知的“用户空间模拟”模式下的 QEMU 版本实现的。
QEMU 是独立于 AFL 的项目,但你可以方便地通过以下方式构建该功能:
$ cd qemu_mode $ ./build_qemu_support.sh
有关更多说明和注意事项,请参阅 qemu_mode/README.qemu。
该模式比编译时插桩大约慢 2-5 倍,不太利于并行化,并且可能还有其他一些怪癖。
为了正常运行,模糊测试器需要一个或多个起始文件,其中包含目标应用通常期望的输入数据的良好示例。有两条基本规则:
保持文件较小。1 kB 以下是理想选择,尽管并非严格要求。有关为什么大小很重要,请参阅 perf_tips.txt。
只有在多个测试用例在功能上彼此不同时才使用它们。用五十张不同的度假照片去模糊测试一个图像库毫无意义。
你可以在本工具附带的 testcases/ 子目录中找到许多很好的起始文件示例。
附:如果有大量数据语料库可供筛选,你可能需要使用 afl-cmin 工具来识别一组功能不同的文件子集,这些文件会触发目标二进制中的不同代码路径。
模糊测试过程本身由 afl-fuzz 工具执行。该程序需要一个包含初始测试用例的只读目录、一个单独存放其发现结果的目录,以及待测试二进制的路径。
对于直接从 stdin 接受输入的目标二进制,常用语法是:
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program [...params...]
对于从文件读取输入的程序,请使用 '@@' 在目标命令行中标记输入文件名应放置的位置。模糊测试器会为你替换该标记:
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@
你还可以使用 -f 选项将变异数据写入特定文件。如果程序期望特定的文件扩展名等,这将非常有用。
未插桩的二进制可以在 QEMU 模式下(在命令行中添加 -Q)或传统的盲目模糊测试模式下(指定 -n)进行模糊测试。
你可以使用 -t 和 -m 覆盖所执行进程的默认超时时间和内存限制;可能需要调整这些设置的少数目标示例包括编译器和视频解码器。
有关优化模糊测试性能的技巧,请参阅 perf_tips.txt。
请注意,afl-fuzz 一开始会执行一系列确定性模糊测试步骤,这可能需要几天时间。如果你想像 zzuf 或 honggfuzz 那样立即获得快速而粗略的结果,请在命令行中添加 -d 选项。
有关如何解读显示的状态信息并监控进程健康状况,请参阅 status_screen.txt 文件。如果任何 UI 元素以红色突出显示,请务必查阅该文件。
模糊测试过程将持续到你按下 Ctrl-C。至少,你应该让模糊测试器完成一个队列周期,这可能需要几个小时到一周左右的时间。
输出目录中会创建三个子目录并实时更新:
queue/ - 每个独特执行路径对应的测试用例,以及用户提供的所有起始文件。这就是第 2 节中提到的合成语料库。
在将此语料库用于任何其他目的之前,你可以使用 afl-cmin 工具将其缩小。该工具会找出一个提供等效边覆盖的更小文件子集。
crashes/ - 导致被测程序收到致命信号(例如 SIGSEGV、SIGILL、SIGABRT)的独特测试用例。条目按收到的信号分组。
hangs/ - 导致被测程序超时的独特测试用例。请注意,当默认(激进)超时设置生效时,由于延迟峰值和其他自然现象,这里可能会有些噪音。
如果相关的执行路径涉及先前记录的故障中未出现过的任何状态转换,则该崩溃和挂起被视为“独特”。如果同一个漏洞可以通过多种方式触发,过程早期会出现一些计数膨胀,但这应该会迅速减少。
崩溃和挂起的文件名与父级(非故障)队列条目相关联,这应有助于调试。
当你无法重现 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 为任何正在进行的模糊测试任务生成漂亮的图表。有关效果示例,请参阅 http://lcamtuf.coredump.cx/afl/plot/。
每个 afl-fuzz 实例大约占用一个 CPU 核心。这意味着在多核系统上,必须进行并行化才能充分利用硬件。有关如何在多个核心或多台联网机器上模糊测试同一目标的技巧,请参阅 parallel_fuzzing.txt。
默认情况下,afl-fuzz 的变异引擎针对紧凑型数据格式进行了优化——例如图像、多媒体、压缩数据、正则表达式语法或 shell 脚本。它不太适合具有特别冗长冗余措辞的语言——尤其是 HTML、SQL 或 JavaScript。
为了避免构建语法感知工具的麻烦,afl-fuzz 提供了一种方式,用可选的语言关键字、魔数头或与目标数据类型相关的其他特殊标记字典来为模糊测试过程注入种子——并在运行时利用它重建底层语法:
http://lcamtuf.blogspot.com/2015/01/afl-fuzz-making-up-grammar-with.html
要使用此功能,你首先需要按照 testcases/README.testcases 中讨论的两种格式之一创建字典;然后在命令行中通过 -x 选项将其指向模糊测试器。
无法提供底层语法更结构化的描述,但模糊测试器很可能仅凭插桩反馈就能推断出部分语法。这在实践中确实有效,例如:
http://lcamtuf.blogspot.com/2015/04/finding-bugs-in-sqlite-easy-way.html
附:即使没有提供显式字典,afl-fuzz 也会在确定性字节翻转期间通过非常密切地观察插桩,尝试提取输入语料库中现有的语法标记。这适用于某些类型的解析器和语法,但远不如 -x 模式效果好。
基于覆盖率的崩溃分组通常会产生一个较小的数据集,可以快速手动分类,或使用非常简单的 GDB 或 Valgrind 脚本进行分类。每个崩溃都可以追溯到队列中其父级(未崩溃)测试用例,从而更容易诊断故障。
话虽如此,重要的是要承认,某些模糊测试崩溃在没有大量调试和代码分析工作的情况下,很难快速评估其可利用性。为协助完成这项任务,afl-fuzz 支持一种非常独特的“崩溃探索”模式,可通过 -C 标志启用。
在此模式下,模糊测试器将一个或多个崩溃测试用例作为输入,并使用其反馈驱动的模糊测试策略,在保持程序处于崩溃状态的同时,非常快速地枚举程序中所有可到达的代码路径。
未导致崩溃的变异会被丢弃;不影响执行路径的任何更改同样会被丢弃。
输出是一个较小的文件语料库,可以非常快速地检查攻击者对故障地址的控制程度,或者是否有可能越过最初的越界读取——并查看其背后的内容。
哦,还有一件事:对于测试用例最小化,可以试试 afl-tmin。该工具的操作方式非常简单:
$ ./afl-tmin -i test_case -o minimized_result -- /path/to/program [...]
该工具同样适用于崩溃和非崩溃测试用例。在崩溃模式下,它可以接受已插桩和未插桩的二进制。在非崩溃模式下,最小化器依靠标准 AFL 插桩来简化文件,同时不改变执行路径。
最小化器接受 -m、-t、-f 和 @@ 语法,兼容方式与 afl-fuzz 相同。
AFL 的另一项新增功能是 afl-analyze 工具。它读取一个输入文件,尝试依次翻转字节,并观察被测程序的行为。然后根据哪些部分看起来是关键部分、哪些不是,对输入进行颜色编码;虽然并非万无一失,但它通常能快速洞察复杂的文件格式。有关其操作的更多信息,请参阅 technical_details.txt 末尾附近的内容。
请记住,与许多其他计算密集型任务一样,模糊测试可能会对硬件和操作系统造成压力。特别是:
你的 CPU 会运行得很热,需要充分的散热。在大多数情况下,如果散热不足或停止正常工作,CPU 速度会自动降低。话虽如此,尤其是在不太合适的硬件(笔记本电脑、智能手机等)上进行模糊测试时,出现硬件损坏也不是完全不可能的。
目标程序可能会异常地占用数 GB 内存,或用垃圾文件填满磁盘空间。AFL 会尝试强制执行基本的内存限制,但无法防止每一种可能的事故。底线是,你不应该在数据丢失风险不可接受的系统上进行模糊测试。- 模糊测试涉及对文件系统的数十亿次读取和写入。在现代系统上,这通常会被大量缓存,导致“物理”I/O 相当有限——但有许多因素可能改变这一状况。你有责任监控潜在问题;在 I/O 非常繁重的情况下,许多 HDD 和 SSD 的寿命可能会缩短。
在 Linux 上监控磁盘 I/O 的一个好方法是使用 'iostat' 命令:
$ iostat -d 3 -x -k [...optional disk ID...]
以下是一些关于 AFL 最重要的注意事项:
AFL 通过检查第一个派生的进程是否因信号(SIGSEGV、SIGABRT 等)而死亡来检测故障。为这些信号安装了自定义处理程序的程序,可能需要注释掉相关代码。同样,由被模糊测试的目标派生的子进程中出现的故障可能会逃避检测,除非你手动添加一些代码来捕获这些故障。
与任何其他暴力破解工具一样,如果加密、校验和、加密签名或压缩被用来完全包装待测试的实际数据格式,模糊测试器提供的覆盖率将非常有限。
要解决这个问题,你可以注释掉相关的检查(参见 experimental/libpng_no_checksum/ 以获得灵感);如果这不可行,你还可以编写一个后处理器,如 experimental/post_library/ 中所解释的那样。
在 ASAN 和 64 位二进制文件方面存在一些不太理想的权衡。这并非 afl-fuzz 的任何特定缺陷所致;有关提示,请参阅 notes_for_asan.txt。
目前不直接支持模糊测试网络服务、后台守护进程或需要 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
除此之外,请参阅 INSTALL 以获取特定平台的提示。
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
谢谢!
有问题?有疑虑?有错误报告?通常可以通过 [email protected] 联系到作者。
该项目还有一个邮件列表;要加入,请发送邮件至 [email protected]。或者,如果你更想先浏览存档,请尝试:
https://groups.google.com/group/afl-users
附言:如果你希望提交原始代码以纳入项目,请注意 AFL 的大部分版权归 Google 所有。虽然你确实保留对贡献内容的版权,但他们要求人们首先同意一份简单的 CLA:
https://cla.developers.google.com/clas
对此带来的麻烦表示抱歉。当然,功能请求或错误报告无需 CLA。