前言
本篇博客以此文的内容为基础进行扩展延申,并记录了用Qemu模式Fuzz时的踩坑过程及解决方案。
AFL简介
概述:AFL(American Fuzzy Lop)是一款开源的Fuzzing测试工具,由Google安全工程师MIchal Zalewski开发
Fuzz模式:
- 有源码模式:通过对源码重新编译时进行插桩(instrumentation)的方式,自动产生测试用例来探索二进制程序内部新的执行路径
- 无源码模式:配合QEMU等工具,对闭源二进制代码进行fuzzing,但执行效率会受到影响
工作原理(有源码模式)
- 使用afl-clang/clang++ 或 afl-gcc/g++ 来编译工程代码
- 将testcase写入文件(文件大小尽量<1K)作为输入
- 启动afl-fuzz,读取testcase(seed),作为输入执行程序
- 如果发现新的路径则保存此testcase到一个queue中,afl-fuzz依据最初的testcase进行突变,以产生不同的样例输入
- 如果程序崩溃,则记录crash
特点
与其他基于插装技术的fuzzers相比,afl-fuzz具有较低的性能消耗,有各种高效的fuzzing策略和tricks最小化技巧,不需要先行复杂的配置,能无缝处理复杂的现实中的程序
安装
在github下载压缩包,解压后在目录中打开终端输入:
1 | make |
输入以上命令后基本就能安装成功了,在终端输入afl-后tab
,就能出现以下这些命令了
说明安装成功
有源码Fuzz
编写测试程序
1 | // 编写测试程序test.c,这里头文件都用"",主要是防止博客识别markdown时将其清空。实际编写时请替换回<> |
插桩编译
- 普通程序编译:
- 执行
afl-gcc test.c -o afl_test
对测试程序进行编译,如果是一个c++的源码,那就需要用afl-g++。afl-clang和afl-clang++的使用方法类似 - 建立两个文件夹:
fuzz_in
和fuzz_out
,用来存放程序的输入和fuzz的输出结果 - 在fuzz in中需要创建一个
testcase
文件,AFL在fuzz时会从该文件中读取测试样例。在本例中,在testcase
中写入abc就可以了
- 执行
- 编译项目:
- 在编译项目时,通常有Makefile,这时就需要在Makefile中添加内容
- gcc/g++重新编译目标程序的方法如下:
- C程序:设置
CC=/path/to/afl/afl-gcc ./configure
- C++程序:设置
CXX=/path/to/afl/afl-g++ .
- 执行
make clean all
- C程序:设置
- afl-clang和afl-clang++的使用方法类似
开始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 @@ |
输入命令:afl-fuzz -i fuzz_in -o fuzz_out ./afl_test
表示,从fuzz_in中读取输入,输出放入fuzz_out中,afl_test是我们要进行fuzz的程序,-f参数表示:testcase的内容会作为afl_test的stdin
如果出现如下错误(我没遇到,直接执行成功了):
需要根据提示设置core_pattern:
- 切换到root用户
echo core > /proc/sys/kernel/core_pattern
再次执行afl-fuzz -i fuzz_in -o fuzz_out ./afl_test
AFL界面
执行成功后,便会开始fuzz,出现下图所示界面:
界面上的数据含义都比较清晰,有不明白的地方可以直接参考官方文档
Crash分析
由上图,经过一段时间的fuzz后,发现了6个crash,进入fuzz_out目录下查看情况,其中自动生成了一些文件夹,含义如下:
- crashes:产生crash的测试用例
- hangs:产生超时的测试用例
- queue:每个不同执行路径的测试用例
本次实验我们对crashes文件夹更感兴趣,分别查看导致不同crash的测试样例:
由上图可知,AFL成功检测出我们预先设置的导致crash的情况以及潜在的漏洞。
无源码Fuzz
有源码的Fuzz测试主要针对开源软件进行,对于一些不开源的产品,AFL使用了QEMU模式进行测试,只需要在之前命令的基础上加上参数-Q即可。
准备工作
执行如下指令,完成对QEMU的配置
1 | cd qemu_mode |
执行上述指令时,必定会报错,下面来看可能的错误类型与修复方案。
报错修复
我在执行指令./build_qemu_support.sh
时,就遇到报错了,
在看雪找到一个解决的方案是安装qemu
对于16.0.4之后版本的Ubuntu,Qemu的安装方式为
apt-get install qemu-user
。执行完该指令后,在终端输入qemu-后tab
,就能出现以下这些命令了但实际上仍未解决这个问题,在看雪的评论区找到另一个方案:通过打补丁来解决。上述报错中,其中一个问题是“static declaration of ‘gettid’ follows non-static declaration”。这里层主参考qemu的一个补丁,编写了afl的补丁。这里我们直接拿过来用就可以了。完整补丁如下所示:
diff1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42--- qemu-2.10.0-rc3-clean/linux-user/syscall.c 2020-11-06 22:14:34.218924847 -0500
+++ qemu-2.10.0-rc3/linux-user/syscall.c 2020-11-06 22:17:09.722926317 -0500
#endif
#ifdef __NR_gettid
-_syscall0(int, gettid)
+#define __NR_sys_gettid __NR_gettid
+_syscall0(int, sys_gettid)
#else
/* This is a replacement for the host gettid() and must return a host
errno. */
cpu = ENV_GET_CPU(env);
thread_cpu = cpu;
ts = (TaskState *)cpu->opaque;
- info->tid = gettid();
+ info->tid = sys_gettid();
task_settid(ts);
if (info->child_tidptr)
put_user_u32(info->tid, info->child_tidptr);
mapping. We can't repeat the spinlock hack used above because
the child process gets its own copy of the lock. */
if (flags & CLONE_CHILD_SETTID)
- put_user_u32(gettid(), child_tidptr);
+ put_user_u32(sys_gettid(), child_tidptr);
if (flags & CLONE_PARENT_SETTID)
- put_user_u32(gettid(), parent_tidptr);
+ put_user_u32(sys_gettid(), parent_tidptr);
ts = (TaskState *)cpu->opaque;
if (flags & CLONE_SETTLS)
cpu_set_tls (env, newtls);
break;
#endif
case TARGET_NR_gettid:
- ret = get_errno(gettid());
+ ret = get_errno(sys_gettid());
break;
#ifdef TARGET_NR_readahead
case TARGET_NR_readahead:将上述补丁命名为gettid.diff,并保存在patches目录下。然后在build_qemu_support.sh中patch那一段的最后加上
patch -p1 <../patches/gettid.diff || exit 1
。具体如下图所示:过上述操作后,再次执行build_qemu_support.sh时,便解决了gettid的问题。但另一个报错“ ‘SIOCGSTAMPNS’ undeclared here”仍未解决,此时可以参考解决gettid的方式,同样通过补丁的方式来解决。最终,我们找到了一个解决同样问题的补丁。由于需要打补丁的函数syscall本身已经有了相应的补丁文件syscall.diff。因此我们只需要将新的补丁添加至该补丁的开头即可。经修改后的syscall.diff如下所示(红框内容为新添加的部分):
此时再次执行build_qemu_support.sh,即可成功
然后回到AFL目录,执行
make install
。接下来,开始qemu模式下的无源码的fuzz。
开始Fuzz
无源码的fuzz和有源码的fuzz相比,只是多了一个参数Q。这里我们还是用刚刚的测试程序,只不过这里不使用afl-gcc插桩编译,而是用普通的gcc进行编译。然后执行如下语句进行无源码fuzz:
1 | afl-fuzz -i fuzz_in/ -o fuzz_out/ -Q ./normal_test |
然后,非常不幸,又报错了。。。
说是没法定位afl-qemu-trace。这个问题很简单,简单来说就是在终端输入afl-后tab
键,不会出现afl-qemu-trace。所以只需将其复制到/usr/local/bin
目录下。然后就可以fuzz了。
可以看到,从fuzz的速度和效率上来看,比源码fuzz还是差上不少的。