经典 Fuzzer 工具 AFL 模糊测试指南

经典 Fuzzer 工具 AFL 模糊测试指南AFL(AmericanFuzzyLop)是由安全研究员MichałZalewski开发的一款基于覆盖引导(Coverage-guided)的模糊测试工具。通过记录输入样本的代码覆盖率,不断对输入进行变异,从而达到更高的代码覆盖率。AFL采用新型的编译时插桩和遗传算法自动发现新的测试用例,这些用例会触发目标二进制文件中的新内部状态。这大大改善了模糊测试的代码覆盖范围。从源码编译程序时…

大家好,欢迎来到IT知识分享网。

AFL(American Fuzzy Lop)是由安全研究员Michał Zalewski 开发的一款基于覆盖引导(Coverage-guided)的模糊测试工具。通过记录输入样本的代码覆盖率,不断对输入进行变异,从而达到更高的代码覆盖率。AFL 采用新型的编译时插桩和遗传算法自动发现新的测试用例,这些用例会触发目标二进制文件中的新内部状态。这大大改善了模糊测试的代码覆盖范围。1

  1. 从源码编译程序时进行插桩,以记录代码覆盖率(Code Coverage);
  2. 选择一些输入文件,作为初始测试集加入输入队列(queue);
  3. 将队列中的文件按一定的策略进行变异”;
  4. 如果经过变异文件更新了覆盖范围,则将其保留添加到队列中;
  5. 上述过程会一直循环进行,期间触发了crash的文件会被记录下来。

在这里插入图片描述

0x10 安装 afl

以下三个版本任选其一(推荐第二个,笔者三个都试过,在普通 afl 模式下,三个都没有问题。但是涉及到更深层次的用法,多多少少都有些 bug;第二个是相对较好的一个版本)

  • 原版 AFL
  • afl-unicorn
  • AFLplusplus

AFLplusplus,引进了更强的编译算法,如果是源码插桩的方式,更适合使用 AFLplusplus。进入到已经下载好的项目,安装 afl,命令如下

make	# 编译 afl
sudo make install

这是编译成功的截图
在这里插入图片描述

0x20 构建被测程序(白盒测试)

用 AFL 编译目标源码,其目的在于插桩,让编译得到的程序,反馈路径覆盖。AFL 自带定制版本的 gcc 和 clang 编译器,建议选择 LLVM 的 clang 编译器,可以加快 fuzz 的速度

0x21 使用 afl-gcc 进行源码插桩

对于单个文件,直接使用 afl-gcc 代替 gcc 即可

afl-gcc test.c -o test

对于完整的项目,需要将编译器指定为 afl-gcc,然后再进行编译

./configure CC="afl-gcc" CXX="afl-g++"	# 或者直接修改 Makefie 文件,将编译器改位 afl-gcc
make

如果需要 fuzz 共享库,可以通过设置 LD_LIBRARY_PATH 让程序加载经过 AFL 插桩的 .so 文件,不过最简单的方法是静态构建,通过以下方式实现

./configure --disable-shared CC="afl-gcc" CXX="afl-g++"

0x22 使用 LLVM 模式进行源码插桩

AFL 还支持使用 LLVM 模式,可以获得更快的Fuzzing速度,而且具有更多的选项,LLVM 的前端表现形式是 clang 编译器,因此需要自行安装 clang,之后就可以编译了

afl-clang test.c -o test

0x23 出现 error while loading shared libraries 错误 2

构建完成之后,可能出现如下错误。这是因为编译器默认只会使用 /lib 和 /usr/lib 这两个目录下的库文件,通过源码编译的方式安装程序,如果不指定--prefix,会将库安装在/usr/local/lib目录下;当运行程序需要链接动态库时,提示找不到相关的.so库,会报错。也就是说,/usr/local/lib目录不在系统默认的库搜索目录中,需要将目录加进去。
在这里插入图片描述
解决方法

  1. 打开/etc/ld.so.conf文件
  2. 加入动态库文件所在的目录:执行vi /etc/ld.so.conf,在”include ld.so.conf.d/*.conf"下方增加"/usr/local/lib"
  3. 保存后,在命令行终端执行:/sbin/ldconfig -v;其作用是将文件 /etc/ld.so.conf 列出的路径下的库文件缓存到 /etc/ld.so.cache 以供使用,因此当安装完一些库文件,或者修改 etc/ld.so.conf增 加了库的新搜索路径,需要运行一下 ldconfig,使所有的库文件都被缓存到文件 /etc/ld.so.cache 中,如果没做,可能会找不到刚安装的库。

0x30 选择语料库

其实就是给被测程序喂入合适的测试用例,afl 会根据这些原始种子,变异生成大量的用例。理想条件下,提供的语料库(即原始的测试用例)能够让程序执行不同的路径,这样才能达到代码覆盖最大化。

0x31 选择用例

这里,借用 Freebuf 提供的资料,给出一些开源的语料库

  • afl generated image test sets
  • fuzzer-test-suite
  • libav samples
  • ffmpeg samples
  • fuzzdata
  • moonshine

事实上很多程序也会自带一些案例,也可以作为测试用例。

0x32 精简语料库

找到语料库之后,最好能够进行修剪,合并重复用例,裁剪体积。afl 推荐的每个用例体积小于 1KB,不然会影响 fuzz 的效率。

去重

afl-cmin 是 afl 提供的一个十分有用的工具,可以精简语料库,去掉可能重复的测试用例,针对一些复杂的语料库十分有用,可大大减少无用的 fuzz 用例。

afl-cmin -i input_dir -o output_dir -- /path/to/tested/program [params]

更多的时候,我们是从文件中获取输入,因此,往往使用 @@ 替代 params(参数),即

afl-cmin -i input_dir -o output_dir -- /path/to/tested/program @@

缩小体积

afl-tmin 可以缩短文件体积,因为 afl 要求测试用例的大小最好小于 1KB,因此最好将精简后的用例进一步缩小体积。afl-tmin 有两种工作模式,instrumented modecrash mode。默认的工作方式是instrumented mode

afl-tmin -i input_file -o output_file -- /path/to/tested/program [params] @@

由于 afl-cmin 一次性只能精简单个文件,如果用例特别多,需要手动花费很长时间,其实一条简单的 shell 脚本即可完成

for i in *; do afl-tmin -i $i -o tmin-$i -- ~/path/to/tested/program [params] @@; done;

0x40 fuzzing

在对程序正式进行 fuzz 之前,可以使用 afl-map 跟踪单个用例的执行路径,它会打印出程序的输出和 tuples

afl-showmap -m none -o /dev/null

正式执行 fuzz 测试的命令如下

afl-fuzz -m none -i in -o out target_binary @@

0x50 黑盒 fuzz

以上 fuzz 过程,依赖于我们有程序的源码,并且在编译过程中进行了插桩,但很多时候,我们并没有源码,这时候就要靠 afl 提供的 qemu_mode 模式了。原版本的 afl qemu 模式由于版本过老,已不能正常运行,推荐使用 github 上的 AFLplusplus 或者 afl-unicorn。AFLplusplus 更容易安装,而 afl-unicorn 针对 qemu 模式更加友好。

无论是下载的哪个版本的 afl,根目录下都会有 qemu_mode 文件夹,进入此目录,运行以下脚本,如果没有出错,就代表 qemu_mode 成功了

cd qemu_mode
sudo ./build_qemu_support.sh

如果出错,请访问:深入分析 afl / qemu-mode(qemu模式) / afl-unicorn 编译及安装存在的问题以及相应的解决方案

如果要对不同架构的二进制文件进行黑盒 fuzz,需要在编译 qemu 脚本前,指定相应的架构,举个例子,要在 x86 架构下,fuzz arm 架构的程序,需要运行如下命令

CPU_TARGET=arm ./build_qemu_support.sh

编译成功后,即可进行黑盒 fuzz

afl-fuzz -Q -m none -i in -o out target_binary @@

0x60 实战分析

0x61 源码分析

编写个 demo,举例来说。这里为了体现 afl 的插桩功能,特意多写了几个 if 分支,在分支深处,会发生栈溢出

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>

int main(int argc, char const *argv[])
{ 
   
	if(argc != 2)
	{ 
   
		printf("null args!\n");
		return -1;
	}

	/* Get file state */
	struct stat fstat;
	if(stat(argv[1], &fstat))
	{ 
   
		printf("Failed ^_^\n");
		return -1;
	}

	/* Open file */
	FILE * fd = NULL;
	fd = open(argv[1], O_RDONLY);
	if(fd == -1)
	{ 
   
		printf("open file failed!\n");
		return -1;
	}

	/* Select */
	char buf[15];
	if(read(fd, buf, 2) == -1)
	{ 
   
		printf("read failed!");
		return -1;
	}

	if(buf[0] == 'a' && buf[1] == 'b')
	{ 
   
		if(read(fd, buf, 4) != -1)
		{ 
   
			if(buf[2] == 's')
			{ 
   
				read(fd, buf, fstat.st_size - 6);
				printf("%s\n", buf);
			}
		}
	}

	return 0;
}

0x62 源码插桩

使用 afl 的 LLVM 模式编译,即对源代码进行插桩

afl-clang afl_demo.c -o afl_demo

每一个 if 语句代表每一个条件分支,在 IDA 的反汇编窗口现实的就是一个基本块。一个 if 语句就是一个新分支,如下所示,我们用 afl-clang 编译后的反汇编代码,可以看到,在每个 if 语句中,都已经自动插桩,_afl_maybe_log 是插桩函数,用来反馈路径
在这里插入图片描述

0x63 生成语料库

新建 in 目录,在目录下新建文件,写入种子,变异算法会根据此种子变异生成各种测试用例,喂给程序,比如写入以下信息(根据代码,当第一个字符为 a,第二个字符为 b,第五个字符 为 s,可能会发生溢出)

abttdcccccccccccccccccccccccccaaaaaaaaaaaaaaaaaaaaaaadddddddddddddddddddddddddddd

0x64 开始 fuzzing

root@lys-virtual-machine:~/Documents/test# afl-fuzz -m none -i in -o out ./afl_demo @@
afl-fuzz++2.60d based on afl by Michal Zalewski and a big online community
[+] afl++ is maintained by Marc "van Hauser" Heuse, Heiko "hexcoder" Eißfeldt and Andrea Fioraldi
[+] afl++ is open source, get it at https://github.com/vanhauser-thc/AFLplusplus
[+] Power schedules from github.com/mboehme/aflfast
[+] Python Mutator and llvm_mode whitelisting from github.com/choller/afl
[+] afl-tmin fork server patch from github.com/nccgroup/TriforceAFL
[+] MOpt Mutator from github.com/puppet-meteor/MOpt-AFL
[*] Getting to work...
[+] Using exploration-based constant power schedule (EXPLORE)
[+] You have 8 CPU cores and 2 runnable tasks (utilization: 25%).
[+] Try parallel jobs - see /usr/local/share/doc/afl/parallel_fuzzing.md.
[*] Checking CPU core loadout...
[+] Found a free CPU core, try binding to #0.
[*] Checking core_pattern...
[!] WARNING: Could not check CPU scaling governor
[*] Setting up output directories...
[+] Output directory exists but deemed OK to reuse.
[*] Deleting old session data...
[+] Output dir cleanup successful.
[*] Scanning 'in'...
[+] No auto-generated dictionary tokens to reuse.
[*] Creating hard links for all input files...
[*] Validating target binary...
[*] Attempting dry run with 'id:000000,time:0,orig:testcases.txt'...
[*] Spinning up the fork server...
[+] All right - fork server is up.
    len = 81, map size = 9, exec speed = 199 us
[+] All test cases processed.

[+] Here are some useful stats:

    Test case count : 1 favored, 0 variable, 1 total
       Bitmap range : 9 to 9 bits (average: 9.00 bits)
        Exec timing : 199 to 199 us (average: 199 us)

[*] No -t option specified, so I'll use exec timeout of 20 ms.
[+] All set and ready to roll!

fuzz 窗口
在这里插入图片描述
可以看到,已经有 crash 出现,当 cycles done 为绿色时,表示可以停止 fuzz 了。

0x65 结果分析

我们分析一下结果,根据 fuzz 时输入的命令,我们的结果是输出在 out 目录下

root@lys-virtual-machine:~/Documents/test/out# tree
.
├── cmdline
├── crashes
│   ├── id:000000,sig:11,src:000001,time:600493+000003,op:splice,rep:64
│   ├── id:000001,sig:11,src:000003,time:600898,op:havoc,rep:64
│   └── README.txt
├── fuzz_bitmap
├── fuzzer_stats
├── hangs
├── plot_data
└── queue
    ├── id:000000,time:0,orig:testcases.txt
    ├── id:000001,src:000000,time:3,op:flip1,pos:0,+cov
    ├── id:000002,src:000000,time:6,op:flip1,pos:1,+cov
    └── id:000003,src:000000,time:3406,op:havoc,rep:2,+cov

3 directories, 11 files
  • crashes:导致目标接收致命 signal 而崩溃的独特测试用例
  • fuzzer_stats:afl-fuzz 的运行状态
  • hangs:导致目标超时的独特测试用例
  • plot_data:用于 afl-plot 绘图
  • queue:存放所有具有独特执行路径的测试用例

afl-plot 可以绘制更加直观的结果,利用的就是 fuzzer 生成的 plot_data 文件。当然,要使用 afl-plot ,需要先安装 apt-get install gnuplot

root@lys-virtual-machine:~/Documents/test# afl-plot out result/
progress plotting utility for afl-fuzz by Michal Zalewski

[*] Generating plots...
[*] Generating index.html...
[+] All done - enjoy your charts!

这样就可以在 result 目录下,生成 html 和图片文件,如下所示
在这里插入图片描述

  • 第一幅图:路径覆盖变化,pending fav 数量变为零并且 total paths 数量基本上没有再增长时,说明 fuzzer 有新发现的可能性就很小了
  • 崩溃和超时的变化
  • 执行速度的变化

要想重新找到 bug,直接输入 crash 目录下的测试用例即可。

0x70 总结

AFL 作为一款优秀的 fuzz 工具,通过源码插桩的方式,计算代码覆盖率,再以此为基础,对语料库(种子文件)不断进行变异,从而达到增大代码覆盖率的效果。本文主要讲解其一般用法,其效率最高的也是 LLVM 模式下的源码插桩,结合具体实例,进行 fuzz 测试。对 C/C++ 代码编译成的二进制文件,虽然 AFL 也提供基于 qemu 的无源码黑盒 fuzz,但是效率低,发现漏洞的可能性小。所以最好采用 AFL 的一般模式,即基于源码插桩的 fuzz 测试。

在没有源码的情况下,如果想直接使用二进制插桩,需要安装 qemu-mode 或者 unicorn 模式,安装可能会出现很多问题,欢迎访问另外一篇博客:深入分析 afl / qemu-mode(qemu模式) / afl-unicorn 编译及安装存在的问题以及相应的解决方案。

AFL 作为一个具有跨时代意义的 fuzz 工具,后续其他的 fuzzer 如雨后春笋,不断涌现,如果你想了解更多的 AFL 改进型工具,或者你想了解更多的 fuzzing 技术,欢迎访问:https://github.com/liyansong2018/fuzzing-tutorial


  1. https://www.freebuf.com/articles/system/191543.html ↩︎

  2. https://www.cnblogs.com/codingmengmeng/p/7456539.html ↩︎

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/10637.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信