前言
??在学习了一段时间的pwn后,我个人对漏洞挖掘也是充满了极大的兴趣,但是真实环境中的漏洞挖掘和CTF中的pwn题还是有很大区别的。原因在于,CTF中的pwn题代码量少,实现逻辑并不复杂,存在的漏洞也是比较明显的,一般都是通过代码审计就能发现;而在真实环境中,代码量大,实现逻辑复杂,虽然造成漏洞的代码可能和pwn题相差不大,但是在庞大的代码量下,通过代码审计的方式来发现漏洞并不是一个好的方式。因此,在工业界出现了fuzzing即模糊测试,其原理是变异输入来对程序进行测试,记录可以造成crash的输入,之后再单独分析这些输入。另外,对程序进行fuzzing来发现漏洞的软件叫fuzzer。 ??在本篇博客中要介绍的AFL就是fuzzer中的一个里程碑标志,其出现在2013年,并对之后出现的fuzzer产生了重大影响。AFL是基于代码路径覆盖率的,采用二进制插桩实现,可以分为有源码插桩和无源码插桩,下面介绍AFL的使用也是从这两个方向出发。
AFL的安装
??AFL ??安装的话从上面的官方链接点进去,如下截图所示,点击最新源代码打包工具的链接即可获得最新的源代码安装包。下载并解压成功后,进入到该文件夹中,使用make 命令进行安装,如果安装过程中出现问题,可以查阅docs/INSTALL 获得一些提示,看看是否有帮助,要不就百度一下。
AFL运行界面介绍
??接下来看下AFL工作时的界面情况,如下截图所示,展示了AFL进行fuzzing时的工作界面状态。界面状态中的大部分内容都可以直接根据其中文意思知晓其含义,在本篇博客中将会简单介绍下大部分的状态指示。
- process timing – 指示了fuzzing测试的时间消耗。一般说来一个中等程序的测试会需要数天或者数周的时间。其下面的四个状态栏信息按照中文翻译理解就行,值得说明的是,第二个状态指示
last new path 如果在程序开始测试的几分钟内没有变化,说明要么是程序引入不正确,或者是测试用例输入不正确,亦或者设置的内存太小,总之fuzzing没有正确启动。当然AFL会在last new path 长时间没有出现时给出警告。 - overall results – 汇总了fuzzing测试的结果。第一个状态栏
cycles done 表明fuzzing的轮数,其颜色值得说明一下,最开始是用品红色表示其处于the first pass ,正如截图中所示。如果在the first pass 后有新的发现,进入子过程,颜色会变成黄色,所有子过程完成后将会变成蓝色,最后变成绿色的话表明已经长时间没有新的动作了,此时也提示我们应该手动ctrl-c去关闭fuzzing。 - cycle progress – 展示了当前队列中fuzzer的位置,以及放弃了的超时输入。
- map coverage – 展示了程序的覆盖率。
- stage progress – 进一步展示了fuzzer的执行过程。
now trying 指明当前所用的变异输入的方法,exec speed 指明测试用例的执行速度,正常情况下是超过500 exec/sec,长时间低于100的话,建议最好查看perf_tips.txt 来寻求优化。 - findings in depth – 展示了一些路径、crash的信息。
- fuzzing strategy yields – 进一步展示了AFL所做的工作,采用的策略情况。
- path geometry – 汇总了路径测试的相关信息。
levels 表示测试等级,pending 表示还没有经过fuzzing的输入数量,pend fav 表明fuzzer感兴趣的输入数量,own finds 表示在fuzzing过程中新找到的,或者是并行测试从另一个实例导入的,imported 中的n/a 表明不可用,即没有导入。最后说明一下stability ,表明相同输入是否产生了相同的行为,一般结果都是100%,如果低于100%并且变红,则需要查阅官方文档寻找解决步骤。
fuzzing – 有源码的程序
??接下来介绍AFL如何fuzzing有源码的程序。对于有源码的程序,先使用afl的编译器对源码进行插桩编译,编译c程序使用afl-gcc ,编译c++程序使用afl-g++ 。
$ afl-gcc test.c -o test
$ afl-g++ test.cpp -o test
??然后使用afl-fuzz 进行测试,命令行如下,-i 指明测试用例的目录,-o 指明测试结果的存放目录。对于直接从终端获取输入的程序来说,我们需要在testcase_dir 目录下新建一个文件,文件的内容就是程序的输入,文件命名不唯一,后面会给一个示例。
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program [...params...]
??对于以文件作为输入的程序,可以使用下面的命令,直接在程序参数位置处使用@@ ,AFL将会自动将其替换为测试用例中的文件名。
$ /afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@
??以afl-2.52b 版本为例,用下面的代码作为示例,可以看到代码中存在一个明显的栈溢出漏洞,另外之所以要写个if判断,主要是防止程序太过于简单,没有多条路径分支,last new path 状态栏不会更新,提示我们进行检查。
#include <stdio.h>
int main() {
char buf[100] = {0};
gets(buf);
if (buf[0] == 'A')
printf("hello\n");
else
printf("NO A\n");
return 0;
}
??按照下面的命令操作进行,fuzz_out文件夹会在fuzzing时自动生成。
$ mkdir fuzz_in
$ echo "hello" > fuzz_in/testcase
$ afl-gcc test.c -o test
$ afl-fuzz -i fuzz_in -o fuzz_out ./test
??如下截图所示,展示了fuzzing测试的结果,由于程序比较简单,在fuzzing几分钟后就已经发现了2个unique crashes,之所以要等半个小时,主要看看啥时候跑到蓝色指示轮数。
??之后我们手动ctrl-c终止fuzzing,看看fuzz_out文件下包含哪些内容,如下截图所示:
- queue – 存放fuzzer生成的所有不同执行路径的测试用例+我们自己一开始构造的测试用例;
- crashes – 存放造成程序崩溃的测试用例,根据产生的信号不同进行分类;
- hangs – 存放造成程序超时的测试用例;
- 剩下的文件记录了fuzzer工作时的一些信息。
??最后我们重点关注的就是crashes下的测试用例,结合我们前面对代码的审计,只有一个栈溢出漏洞,那么这里产生的两个crash是怎么回事?如下截图所示,是两个测试用例的十六进制表示,其实都是栈溢出,只不过第一个用例首字母不是A ,所以和第二个用例最终执行的路径不同,因此AFL认定存在两个不同的crash。
fuzzing – 无源码的程序
??待补充
总结
不忘初心,砥砺前行!
|