1. guided fuzzing
的挑战
Fuzzing
是漏洞挖掘领域最有效的方法之一,可以用来发现大量的远程代码执行和提权的漏洞。然而,
fuzzing
优势相对肤浅和盲目的。随机变异使得我们很难实现达到测试程序特定的代码路径。这就使得测试的代码覆盖率很低。
有很多人试图去解决这个问题,
Tavis Ormandy
曾经提出一种:语料精馏法。这个方法根据代码覆盖率,从大量高质量的输入文件语料中选取一个子集,然后按照传统方法去
fuzz
。这种方法很有效,但前提是需要一个这样的语料。另一方面,代码覆盖率只提供了一个很简单的对程序状态的描述,当
Fuzzing
测试到了一定的程度,代码覆盖率就没什么作用了。
所以,大家都在探索更复杂的技术,比如:程序控制流分析,符号执行或静态分析。这些技术在实验中是很有前途的,但是在实际应用中显得效率低下、缺乏可靠性。
2. AFL
的方案
AFL
是一个暴力方法的
fuzzer
,采用了一个极其简单但是绝对可靠的,插桩代码导向的遗传算法。
它使用
一种自定义的边缘覆盖率
来识别程序控制流的局部变化。
简单来说,整个算法的逻辑如下:
1) Load user-supplied initial test cases into the queue,
2) Take next input file from the queue,
3) Attempt to trim the test case to the smallest size that doesn't alter
the measured behavior of the program,
4) Repeatedly mutate the file using a balanced and well-researched variety
of traditional fuzzing strategies,
5) If any of the generated mutations resulted in a new state transition
recorded by the instrumentation, add mutated output as a new entry in the
queue.
6) Go to 2.
|
被选择出来的测试用例会进行周期性的删减,消除那些已经被更新的、更高覆盖率的废弃的测试用例。
整个
fuzzing
过程,会产生一个很有用的
测试
集合的语料库,可以用来对一些浏览器、
office
应用、闭源的软件等进行压力测试。
经测试,
AFL
的性能,比那些
blind fuzzing
或者
coverage-only
工具高多了。
3.
使用
AFL
插桩程序(有源码的情况下
instrument
)
当有源代码时,我们可以在编译期间进行instrument,这种instrument对性能影响很小。
gcc/g++重新编译目标程序的方法是:
CC=/path/to/afl/afl-gcc ./configure
make clean all
对于C++程序,还要设置:
CXX=/path/to/afl/afl-g++.
afl-clang和afl-clang++的使用方法类似。clang用户还可以选择更高效的插桩模式,参考llvm_mode/README.llvm。
当测试libraries的时候,我们需要写一个小程序,从stdin中或者一个文件中读取数据,然后传到被测试的libraries中。这种情况下,我们必须把插桩好的库的静态版本与可执行文件链接起来,或者确保运行时加载正确的.so文件。最简单的方法是静态构建,方法如下:
$ CC=/path/to/afl/afl-gcc ./configure –disable-shared
如果设置AFL_HARDEN=1,当调用‘make’时会导致CC自动启动代码hardening选项,使得检测内存bug更简单。Libdislocator,是AFL的一个辅助库(在Libdislocator/README.dislocator中),可以帮助发现heap corruption堆溢出问题。
4.
对无源码二进制
instrument
(
所以最好还是对有源码,开源的程序进行
fuzz
)
AFL对没有源代码的程序提供的黑盒二进制instrument支持是实验性的。通过qemu的
用户级(userland)
仿真实现。
QEMU是独立于AFL的工程,但是在AFL中也提供了便于实现该功能的方式:
$ cd qemu_mode
$ ./build_qemu_support.sh
更多的说明和注意事项,在qemu_mode/README.qemu中。
这种模式比编译时instrument慢2到5倍,而且不利于并行处理。
5.
选取初始测试样例
为了正确操作,AFL一开始需要有一个可以被目标程序正确读取的输入样例。选择输入样例的两条基本准则:
-文件尽可能小。低于1kb是理想的,虽然不是必要的。
-选择的样例尽可能在功能上不相同,比如:fuzz照片程序时,没有必要用50个不同的风景照
PS:
如果有大量数据可用于筛选,可以使用afl-cmin功能来识别在目标程序中执行不同代码路径的功能不同的文件的子集。
afl-cmin用来从大量数据中筛选出合适的子集作为初始test case。
注意:afl-cmin是对一个文件夹中的所有testcase进行删减达到最小化。
6.
开始
Fuzzing
afl-fuzz负责进行fuzzing的过程,需要指定一个初始test cases的输入目录、一个存放findings结构的输出目录和要测试的目标程序的路径。
对那些可以直接从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选项,把变异数据写到一个指定的文件。
对于没有被instrument的二进制目标程序
,可以用qemu模式(-Q)进行fuzz,或者直接用传统blind-fuzzer模式(-n)。
使用-t和-m选项,修改fuzz过程的默认timeout和memory limit。
一般afl-fuzz在开始时会执行一系列的确定性fuzzing步骤,这会花费几天的时间,但是会生成更neat的testcase。如果你想要快速运行、马上生成dirty的结果----类似于zzuf和其他传统fuzzer----可以使用-d选项,跳过确定性fuzzing步骤。
7. fuzzing
的输出文件
有关如何解释显示的统计信息和监视进程的运行状况的信息,请参阅status_screen.txt文件。
Fuzzing过程中,会在输出目录中创建3个子目录并实时更新:
- queue/ - 每个独特执行路径的test case,加上用户给定的初始文件。这就是the synthesized corpus合成语料库。可以使用afl-cmin缩小size。
- crashes/ - 导致程序接收fatal信号(e.g., SIGSEGV, SIGILL, SIGABRT)的unique test case。会根据信号的不同进行分组。
- hangs/ - unique test cases that cause the tested program to time out.
如果相关联的执行路径包含在先前记录的故障中未见到的任何状态转换,则崩溃和挂起被认为是
“
unique”。
Crashes and hangs are considered "unique" if the associated execution paths involve any state transitions not seen in previously-recorded faults.
The file names for crashes and hangs are correlated with parent, non-faulting queue entries. This should help with debugging.
crash和hang的文件名与parent和非故障队列条目有关
。这应该有助于调试。
当您无法重现由afl-fuzz发现的崩溃时,最可能的原因是您没有设置与该工具使用的内存限制相同。尝试:
$ LIMIT_MB=50
$ ( ulimit -Sv $[LIMIT_MB << 10]; /path/to/tested_binary ... )
更改LIMIT_MB以匹配传递给afl-fuzz的-m参数。
任何现有输出目录也可用于恢复已中止的jobs;try:
$ ./afl-fuzz -i- -o existing_output_dir [...etc...] //-i之后并没有输入目录,而是一个连接符,
如果您安装了gnuplot,您还可以使用afl-plot为任何活动的模糊任务生成一些漂亮的图形。
8
、并行
Fuzzing
afl-fuzz的每个实例都占用大约一个核心。 这意味着在多核系统上,并行化是必须的以充分利用硬件。 有关如何在多个内核或多个网络机器上fuzz一个共同目标的提示,请参阅parallel_fuzzing.txt。
并行Fuzzing模式还提供了一种用于将AFL与其他模糊器,符号执行或者concolic混合符号执行引擎等连接的简单方法; 请参阅parallel_fuzzing.txt的最后一节提示。
9
、
Fuzzer dictionaries
字典
afl-fuzz变异引擎针对紧凑的数据格式(即,图像,多媒体,压缩数据,正则表达式语法或shell脚本)进行了优化。它有点不太适合特别冗长和冗余的语言 - 尤其包括HTML,SQL或JavaScript。
为了避免构建语法感知工具的麻烦,afl-fuzz提供了一种使用语言关键字,magic header或其他与目标数据类型相关联的特殊表示的可选字典为模糊过程提供种子的方法 - 并且使用它来重建底层语法,参考:
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将通过在确定性字节翻转期间非常仔细地观察插桩来尝试提取输入语料库中的现有语法表示(token-表示,记号)。 这适用于一些类型的解析器和语法,但不像
-x模式
那么好。
如果字典真的很难得到,另一个选择是让AFL运行一段时间,然后使用AFL自带的表示捕获库。 有关详细信息,请参阅libtokencap/README.tokencap。
在
http://volatileminds.net/2015/08/20/advanced-afl-usage-preeny.html
中有关于字典的使用和简单介绍。
10
、
crash
分类
Crash triage
基于覆盖的崩溃分组通常生成一个小数据集,可以手动或使用非常简单的GDB或Valgrind脚本快速分类。每个崩溃也可追溯到其在queue中的非崩溃的parent测试用例,从而更容易诊断故障。
afl-fuzz的支持一个崩溃探索模式,使用-C标志。
https://lcamtuf.blogspot.com/2014/11/afl-fuzz-crash-exploration-mode.html
在这种模式下,模糊器需要一个或多个崩溃的测试用例作为输入,并使用其反馈驱动的fuzzing策略来快速枚举程序中可以到达的所有代码路径,同时保持其处于崩溃状态。
不导致崩溃的变异被拒绝; 任何不影响执行路径的更改也一样。
输出是一个小文件库,可以快速检查攻击者对故障地址的控制程度,或者是否有可能通过一个初始的越界读取,以及查看下面的内容 。
afl-tmin工具
适用于crash和non-crash的test case。用于test case 最小化!注意:
afl-tmin是对一个文件的无用字节、bit的删减以达到最小化。
$ ./afl-tmin -i test_case -o minimized_result -- /path/to/program [...]
afl-analyze工具。它需要一个输入文件,尝试顺序翻转字节,并观察被测试程序的行为。然后对输入进行颜色编码,基于哪些部分看起来是关键的,哪些部分不是;虽然不是防弹,它通常可以提供对复杂文件格式的快速洞察。
11
、
Going beyond crashes
Fuzzing is a wonderful and underutilized technique for discovering non-crashing design and implementation errors, too. Quite a few interesting bugs have been found by modifying the target programs to call abort() when,say:
-Two bignum libraries produce different outputs when given the same fuzzer-generated input,
- An image library produces different outputs when asked to decode the same input image several times in a row,
- A serialization / deserialization library fails to produce stable outputs when iteratively serializing and deserializing fuzzer-supplied data,
- A compression library produces an output inconsistent with the input file
when asked to compress and then decompress a particular blob.
Implementing these or similar sanity checks usually takes very little time;
if you are the maintainer of a particular package, you can make this code
conditional with #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION (a flag also
shared with libfuzzer) or #ifdef __AFL_COMPILER (this one is just for AFL).
12
、常识性的风险
Common-sense risks
与其他计算密集型任务类似,fuzzing可能会给你的硬件或系统带来压力, A good way to monitor disk I/O on Linux is the 'iostat' command:
$ iostat -d 3 -x -k [...optional disk ID...]
13
、已知的限制和改进的领域
Known limitations & areas for improvement
-AFL
通过检查由于信号(
SIGSEGV
,
SIGABRT
等)导致的第一个引起的进程死亡来检测故障。为这些信号安装自定义处理程序的程序可能需要注释掉相关代码。同样,由目标产生的子处理中的错误可能会逃避检测,除非您手动添加一些代码来捕获它。
-与任何其他强力工具一样,如果使用加密,校验和,加密签名或压缩来完全包装要测试的实际数据格式,则模糊器提供有限的覆盖率。
要解决这个问题,你可以注释掉相关的检查;如果这是不可能的,你也可以写一个后处理器postprocessor,参考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不输出人类可读的覆盖数据。如果你想监视覆盖,使用afl-cov从Michael Rash:
https://github.com/mrash/afl-cov
-偶尔,有意识的机器反对他们的创造者。如果发生这种情况,请参阅http://lcamtuf.coredump.cx/prep/。