0%

afl readme

原文地址:https://lcamtuf.coredump.cx/afl/README.txt

American fuzzy lop

简称AFL,是当下最常用的模糊测试工具,而模糊测试主要用于漏洞挖掘。

由Michal Zalewski lcamtuf@google.com编写和维护

新版本和更多信息请查阅:http://lcamtuf.coredump.cx/afl/

如果你想获得其他用户的使用笔记或者主要新功能的通知,可以通过电子邮件:afl-users+subscribe@googlegroups.com

如果你没有时间读完这篇文章,可先查阅”QuickStartGuide.txt”。

1)引导性模糊测试的难点

Fuzzing是在现实软件中识别安全问题的最强大和被证明的策略之一;到目前为止,在安全关键型软件中发现的绝大多数远程代码执行和提权漏洞都是由它发现的。

不幸的是,fuzzing也相对较浅;盲目的、随机的突变使得它不太可能到达测试代码中的某些代码路径,从而使一些漏洞牢牢地超出了这种技术的范围。

为解决这个问题已作了许多尝试。早期的方法之一——由塔维斯·奥曼蒂开创——是体蒸馏。该方法依靠覆盖信号从大量高质量的候选文件语料库中选择感兴趣的种子子集,然后用传统方法对其进行模糊处理。这种方法非常有效,但需要这样的语料库随时可用。此外,块覆盖度量只提供了对程序状态的一个非常简单的理解,并且在指导长期的模糊工作方面用处不大。

其他更复杂的研究集中在诸如程序流分析(“协同执行”)、符号执行或静态分析等技术上。所有这些方法在实验环境中都非常有前途,但在实际应用中往往存在可靠性和性能问题——而且目前还不能提供“愚蠢的”模糊技术(指模糊测试的盲目性)的可行替代方案。

2)afl-fuzz方法

AFL是一个蛮力的Fuzzer,它的插桩引导遗传算法极其简单但却很可靠。它使用了一种改进的边缘覆盖形式来捕捉程序控制流中细微的、局部的变化。稍微简化一下,整体算法可总结为:

  1. 将用户提供的测试用例加载到队列中
  2. 从队列中获取下一个输入文件
  3. 尝试将测试用例修剪到最小的不会影响程序的测量行为的大小
  4. 使用平衡且经过充分研究的各种传统模糊策略反复修改文件
  5. 如果生成的任何突变导致插装记录的新状态转换,则在队列中添加突变的输出作为新条目
  6. 返回第2步。

被发现的测试用例也被周期性地剔除,以消除那些被更新的、高覆盖率的发现所淘汰的用例;并继续执行其他几个由工具驱动的工作最小化步骤。

作为模糊处理过程的一个副产品,该工具创建了有趣的测试用例的小型自包含语料库。这对于推广其他劳动或资源密集型的测试机制非常有用——例如,用于压力测试浏览器、办公应用程序、图形套件或闭源工具。

这个Fuzzer经过了全面的测试,提供了开箱即用的功能,这远优于盲目的fuzzing或仅覆盖工具(coverage-only)。

3)用于AFL的插桩程序

当源代码可用时,可以通过一个伴生工具注入插装,该工具在任何标准的第三方代码构建过程中作为gcc或clang的临时替代品。

插装对性能的影响相当小;与afl-fuzz实现的其他优化相结合,大多数程序可以像使用传统工具一样快速甚至更快地进行模糊处理。

重新编译目标程序的正确方法可能会因构建过程的具体情况而有所不同,但是一种几乎通用的方法是:

1
2
$ 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)。最简单的选择是静态构建,通常可以通过:

1
$ CC=/path/to/afl/afl-gcc ./configure --disable-shared

在调用“ make”时设置AFL_HARDEN = 1将导致CC包装器自动启用代码强化选项,使其更易于检测简单的内存错误。 Libdislocator,一个AFL附带的帮助程序库(请参阅libdislocator / README.dislocator)也可以帮助发现堆损坏问题。

注:建议ASAN用户查看ASAN.txt文件的注释,以了解重要的警告。

4)二进制程序插桩

当源代码不可用时,AFL为黑盒二进制文件提供了快速、动态插桩的实验性支持。这是通过一个运行在更少人知道的用户模拟空间的QEMU版本来实现的。

QEMU是一个独立于AFL的项目,但是您可以通过以下操作方便地构建该特性:

1
2
$ cd qemu_mode
$ ./build_qemu_support.sh

image-20200615201618394

额外的命令和注意事项,请查阅:qemu_mode/README.qemu.

这种模式比编译时插桩慢2-5倍,不利于并行化,而且可能还有其他一些奇怪之处。

5)选择初始化测试用例

要正确操作,fuzzer需要一个或多个启动文件,其中包含目标应用程序通常期望的输入数据的良好示例。有两个基本规则:

  1. 保持文件小。1 kB以下是理想的,尽管不是严格必需的。有关为什么尺寸很重要的讨论,请参阅perf tips.txt。
  2. 只有当多个测试用例在功能上不同时,才使用它们。使用50张不同的度假照片来模糊一个图片库是没有意义的。

你可以在AFL的安装目录testcases/subdirectory下找到许多很好的启动文件例子。

注:如果有大量数据可用来进行筛选,那么您可能希望使用afl-cmin实用程序来标识功能不同的文件子集,这些文件在目标二进制文件中执行不同的代码路径。

6)模糊测试二进制文件

模糊过程本身是由afl-fuzz实用程序执行的。这个程序需要一个带有初始测试用例的只读目录,一个单独的地方来存储它的结果,加上要测试的二进制文件的路径。

对于直接接受stdin输入的目标二进制文件,通常的语法是:

1
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program [...params...]

对于从文件中获取输入的目标二进制文件,使用‘@@’来标记目标命令行中应该放置输入文件名的位置。然后模糊器会自动为你替换:

1
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@

还可以使用-f选项将变化后的数据写入特定文件。如果程序需要一个特定的文件扩展名,这很有用。

可以在QEMU模式(在命令行中添加-Q)或在传统的盲模糊模式(指定-n)中对未插桩的二进制文件进行模糊处理。

您可以使用-t和-m来覆盖所执行进程的默认超时和内存限制;需要修改这些设置的少数目标包括编译器和视频解码器。

在perf_tips .txt中讨论了优化模糊性能的技巧。

请注意,afl-fuzz首先执行一组确定性模糊步骤,这可能要花费几天时间,但往往会生成整洁的测试用例。如果你想马上得到快捷而又不太好的结果——类似于zzuf和其他传统的模糊器——在命令行中添加-d选项。

7)看懂输出的含义

有关如何解释显示的统计信息和监视进程的运行状况的信息,请参阅status_screen.txt文件。一定要参考这个文件,特别是当任何UI元素用红色突出显示时。

模糊过程会一直执行,直到您按下Ctrl-C。您至少希望允许fuzzer完成一个队列周期,这可能需要几个小时到一周左右的时间。

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

  1. queue/ 每个不同的执行路径的测试用例,加上用户给出的所有启动文件。这是第二节中提到的合成语料库; 在将此语料库用于任何其他目的之前,可以使用afl-cmin工具将其缩小到更小的大小。该工具将找到提供等效边缘覆盖的更小的文件子集。
  2. crashes/ 导致被测试程序接收到一个致命的信号的唯一测试用例(例如,SIGSEGV, SIGILL, SIGABRT)。条目根据接收到的信号进行分组。
  3. hangs/ 导致被测试程序超时的唯一测试用例。在某些东西被归类为挂起之前的默认时间限制是较大的1秒和-t参数的值。该值可以通过设置AFL_HANG_TMOUT进行微调,但这很少是必要的。

如果相关的执行路径包含以前记录的错误中没有的任何状态转换,那么崩溃和挂起被认为是“唯一的”。如果一个bug可以通过多种方式达到,那么在早期会出现一些计数膨胀,但这种情况应该很快就会消失。

崩溃和挂起的文件名与父的、无故障的队列条目相关。这应该有助于调试。

当您不能重现afl-fuzz发现的崩溃时,最有可能的原因是您没有设置与该工具所使用的相同的内存限制。可以尝试修改以下设置:

1
2
$ LIMIT_MB=50
$ ( ulimit -Sv $[LIMIT_MB << 10]; /path/to/tested_binary ... )

更改LIMIT_MB以匹配传递给afl-fuzz的-m参数。 在OpenBSD上,还将-Sv更改为-Sd。

还可以使用任何现有的输出目录恢复中止的作业。以下:

1
$ ./afl-fuzz -i- -o existing_output_dir [...etc...]

如果安装了gnuplot,则还可以使用afl-plot为任何正在进行活动的模糊测试任务生成漂亮的图形。 举例来说,参见http://lcamtuf.coredump.cx/afl/plot/。

8)并行模糊测试

afl-fuzz的每个实例大约占一个核心。这意味着在多核系统上,为了充分利用硬件,必须进行并行。有关如何在多个核或多个联网机器上模糊一个公共目标的技巧,请参阅parallel_fuzzing.txt。

并行模糊模式也提供了一种简单的方法来连接AFL到其他模糊器,符号或共混执行引擎等等; 同样,请参阅parallel_fuzzing.txt的最后一节以获得提示。

9)模糊器字典

默认情况下,afl-fuzz突变引擎针对紧凑的数据格式进行了优化——例如,图像、多媒体、压缩数据、正则表达式语法或shell脚本。它不太适合那些特别冗长和冗余的语言——尤其是HTML、SQL或JavaScript。

为了避免构建语法感知工具的麻烦,afl-fuzz提供了一种方法,为模糊处理过程提供一个可选的字典,其中包含语言关键字、魔法头或与目标数据类型相关联的其他特殊标记,并使用它们在运行中重构底层语法(参阅:http://lcamtuf.blogspot.com/2015/01/afl-fuzz-making-up-grammar-with.html)。

要使用此功能,您首先需要在两者之一中创建字典dictionaries/README.dictionaries中讨论的格式; 然后通过命令行中的-x选项将模糊器指向它。(在该子目录中也提供了一些常用字典。)

没有办法提供底层语法的更结构化的描述,但是fuzzer可能仅基于插装反馈就能找出其中的一部分。这在实践中是可行的,参阅:http://lcamtuf.blogspot.com/2015/04/finding-bugs-in-sqlite-easy-way.html。

注:即使没有给出显式字典,afl-fuzz也会通过在确定的字节翻转期间非常密切地观察插装,尝试从输入语料库中提取现有语法标记。这种方法适用于某些类型的解析器和语法,但不如-x模式好。

如果真的很难获得字典,另一种选择是让AFL运行一段时间,然后使用AFL随附的令牌捕获库。 为此,请参见libtokencap / README.tokencap。

10)崩溃分组

基于覆盖率的崩溃分组通常会生成一个小数据集,可以手动或使用非常简单的GDB或Valgrind脚本快速筛选该数据集。每个崩溃都可以追溯到队列中的父非崩溃测试用例,从而更容易诊断错误。

话虽如此,重要的是要承认,如果没有大量的调试和代码分析工作,一些模糊崩溃很难快速评估可利用性。为了帮助完成这个任务,afl-fuzz支持一个非常独特的“崩溃探索”模式,启用了-C标志。

在这种模式下,fuzzer将一个或多个崩溃测试用例作为输入,并使用它的反馈驱动的模糊策略来非常快速地枚举程序中可以到达的所有代码路径,同时保持程序处于崩溃状态。

不会导致崩溃的突变被排斥;也不会影响执行路径的更改。

输出是一个小的文件语料库,可以非常快速地检查这些文件,以查看攻击者对错误地址的控制程度,或者是否有可能通过初始越界读取——并查看下面隐藏的内容。

对于测试用例最小化,尝试一下afl-tmin。这个工具操作起来很简单:

1
$ ./afl-tmin -i test_case -o minimized_result -- /path/to/program [...]

该工具对崩溃和非崩溃测试用例同样有效。在崩溃模式下,它将愉快地接受检测和非检测的二进制文件。在非崩溃模式下,最小化器依靠标准的AFL插桩来简化文件,而不改变执行路径。

另一个最近添加到AFL的是AFL分析工具。它接受一个输入文件,尝试按顺序翻转字节,并观察被测试程序的行为。然后,它根据哪些部分看起来是关键的,哪些不是关键的,对输入进行颜色编码;虽然不是很完善,但它通常可以提供对复杂文件格式的快速洞察。更多关于其操作的信息可以在technical_details.txt结尾找到。

11)崩溃以外

在发现非崩溃设计和错误实现方面,Fuzzing也是一种很棒但未被充分利用的技术。通过修改目标程序来调用abort(),可以发现很多有趣的错误:

  1. 当给定相同的模糊生成的输入时,两个bignum库会产生不同的输出
  2. 当要求连续解码相同的输入图像时,图像库产生不同的输出
  3. 当迭代序列化和反序列化模糊器提供数据时,序列化/反序列化库无法产生稳定的输出
  4. 当要求压缩和解压特定blob时,压缩库生成与输入文件不一致的输出

实现这些或类似的健全检查通常只需要很少的时间;如果你是一个特定包的维护者,你可以使用#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION(libfuzzer也有的标志)或#ifdef __AFL_COMPILER(仅针对AFL)。

12)通有风险

请记住,与许多其他计算密集型任务类似,模糊测试可能会给硬件和操作系统带来压力。特别是以下几点:

  1. 你的CPU会很热,需要适当的冷却。在大多数情况下,如果冷却不足或停止正常工作,CPU速度将自动节流。也就是说,特别是在不太合适的硬件(笔记本电脑、智能手机等)上发出信号时,爆炸也不是完全不可能的;
  2. 目标程序最终可能会不规律地攫取千兆字节的内存或用垃圾文件填充磁盘空间。AFL试图加强基本的内存限制,但不能防止每一个可能的事故。底线是您不应该在数据丢失的可能性不是可接受的风险的系统上混淆。
  3. 模糊测试涉及对文件系统的数十亿次读取和写入。在现代系统中,这通常会被大量缓存,从而导致适度的物理I/O压力,但是也有很多因素改变这个压力值。因此你应该注意到这个潜在的故障,在物理I/O压力很大的情况下,可能会缩短许多HDD和SDD的寿命。

Linux系统上监视磁盘I/O情况的一个好用的命令是iostat(I/O STATE):

1
$ iostat -d 3 -x -k [...optional disk ID...]

13)已知的缺陷和需要改进的地方

这里有一些对AFL最重要的注意事项:

  1. AFL通过检查第一个衍生进程因信号(SIGSEGV, SIGABRT等)而死亡来检测错误。为这些信号安装自定义处理程序的程序可能需要注释掉相关代码。同样,模糊目标产生的子进程中的错误可能会逃避检测,除非您手动添加一些代码来捕获它。

  2. 与任何其他暴力工具一样,如果使用加密、校验和、加密签名或压缩来完全包装待测试的实际数据格式,则fuzzer提供有限的覆盖范围。

    要解决这个问题,你可以注释掉相关的检查(参阅安装目录下 experimental/libpng_no_checksum/);如果不行,你也可以写一个后处理器(postprocessor),关于什么是后处理器参阅:experimental/post_library/。

  3. 不幸的是,AFL对于ASAN文件和64位二进制文件有一些取舍,这不是因为afl-fuzz的某些特定故障。参阅notes_for_asan.txt。

  4. afl-fuzz并没有提供支持模糊测试的网络服务程序,后台守护程序或需要UI交互才能工作的交互式应用程序。你可以进行简单的代码修改,使其变得更为传统。Preeny也可能提供了一个相对简单的选项,参阅:https://github.com/zardus/preeny。

    修改基于网络的服务的一些有用提示也可以在这里看到:https://www.fastly.com/blog/how-to-fuzz-server-american-fuzzy-lop。

  5. AFL不输出人类可读的覆盖率数据。如果你想监控覆盖率,请使用Michael Rash的afl-cov:https://github.com/mrash/afl-cov。

  6. 有时sentient machines会反抗它们的创造者。(这里我看原文也真看不懂了,sentient machines好像是指专门研究real AI的一个名词)如果发生在你身上,请查看:http://lcamtuf.coredump.cx/prep/。

除此之外,请参阅:INSTALL for platform-specific tips.

14)特别鸣谢

……

15)联系

lcamtuf@google.com
afl-users+subscribe@googlegroups.com