大家好,欢迎来到IT知识分享网。
AFL(American Fuzzy Lop)是由安全研究员Michał Zalewski 开发的一款基于覆盖引导(Coverage-guided)的模糊测试工具。通过记录输入样本的代码覆盖率,不断对输入进行变异,从而达到更高的代码覆盖率。AFL 采用新型的编译时插桩和遗传算法自动发现新的测试用例,这些用例会触发目标二进制文件中的新内部状态。这大大改善了模糊测试的代码覆盖范围。1
- 从源码编译程序时进行插桩,以记录代码覆盖率(Code Coverage);
- 选择一些输入文件,作为初始测试集加入输入队列(queue);
- 将队列中的文件按一定的策略进行变异”;
- 如果经过变异文件更新了覆盖范围,则将其保留添加到队列中;
- 上述过程会一直循环进行,期间触发了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目录不在系统默认的库搜索目录中,需要将目录加进去。
解决方法
- 打开
/etc/ld.so.conf
文件 - 加入动态库文件所在的目录:执行
vi /etc/ld.so.conf
,在”include ld.so.conf.d/*.conf"
下方增加"/usr/local/lib"
- 保存后,在命令行终端执行:
/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 mode
和crash 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
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/10637.html