前言
开始第一次Fuzzing啦。
一、AFL环境的配置
本次实践中,我们是采用AFL进行的。AFL全称为American Fuzzy Lop,它已经成为现在的主流模糊测试工具之一,它是利用插桩技术来进行路径覆盖的,下次代码再执行到此处时,会识别出上次插的桩,从而判断来过。(因为是第一次接触整个模糊测试技术,个人对AFL的原理理解也很浅薄,原理部分以后再进行分享。)
1.准备Linux系统
我采用的时VMware12安装的虚拟机,在虚拟机上安装Ubuntu16的Linux系统,对Linux系统的核心和内存分配都采用默认值。
2.安装编译环境
检查LLVM和Clang是否安装。
sudo apt-get install llvm
sudo apt-get install clang
sudo 是Linux系统管理指令,是允许系统管理员让普通用户执行一些或者全部的root命令的一个工具,如halt,reboot,su等等。 这样不仅减少了root用户的登录和管理时间,同样也提高了安全性。
apt-get 命令能自动从互联网中搜索目标软件,并进行下载、安装、更新等。
LLVM 是一个自由软件项目,它是一种编译器基础设施,以C++写成,包含一系列模块化的编译器组件和工具链,用来开发编译器前端和后端。
Clang 是一个C、C++、Objective-C和Objective-C++编程语言的编译器前端。 它采用了LLVM作为其后端,而且由LLVM2.6开始,一起发布新版本。
3.安装AFL
从官网上下载AFL安装包
wget http://lcamtuf.coredump.cx/afl/releases/afl-2.52b.tgz
使用tar指令对压缩包进行解压
tar -zxvf afl-2.52b.tgz
解压完成后,cd到解压出的afl文件夹中,执行以下指令进行安装
make
sudo make install
安装完成后尝试输入afl-fuzz,已经能提示出各个指令时,说明安装正确。
二、开始Fuzzing
1.准备待测试的c程序
将待测试的c代码保存为文件afl_test.c
#include <stdio.h>
int main(int argc, char *argv[])
{
char buf[100]={0};
gets(buf);//存在栈溢出漏洞
printf(buf);//存在格式化字符串漏洞
return 0;
}
2.用gcc编译并准备
使用以下指令对afl_test.c进行编译。(以下指令的意思还不太懂,后续再更新)
afl-gcc -g -o afl_test afl_test.c
编译后会生成一个新文件afl_test,该afl_test相当于“启动器”(在其他地方的参考资料看到,以afl_test这个文件开始fuzz之后它会保持不变?不太理解,找机会问问学长)
有了afl_test这个Fuzzing启动器后,创建两个文件夹,分别设为fuzz_in和fuzz_out,一个用来存储输入的测试用例,一个用来存储输出的crash信息。
在fuzz_in文件中新建一个文本文档,命名为testcase,在里面随便写个字符串作为启动Fuzzing的第一个测试用例,之后的测试用例都是从这个用例突变而来。(个人暂时是这样理解的,还不知道对不对,需要求证一下学长)
编译完成后还需临时修改一下系统配置,使系统将coredump输出到文件,而不是上报给系统的处理程序
echo core > /proc/sys/kernel/core_pattern
3.开始Fuzzing
执行以下指令开始Fuzzing。
afl-fuzz -i fuzz_in -o fuzz_out ./afl_test
(如果是继续上一次的Fuzzing操作,则用-来代替fuzz_in)
afl-fuzz -i - -o fuzz_out ./afl_test
我对这个指令的理解为:执行afl的fuzz功能,-i表示指定input为fuzz_in文件夹,Fuzzing时会自动先从这个文件夹里的testcase中抽取测试用例,-o表示指定整个Fuzzing的output为fuzz_out这个文件夹,随后定位到Fuzzing的“启动器”afl_test的位置,开始Fuzzing。
整个Fuzzing的界面如上图,unique crashes就是测试出的漏洞。(整个界面的其他信息还不太懂是什么意思,待后续学习时分享)
4.Fuzzing结果查看
Fuzzing是不会主动停止的,当感觉测试的差不多了就可以操作快捷键Ctrl+C进行停止(暂时的理解,但感觉应该是有表示差不多可以停止了
这类的标志性信息,还需后续学习)
观察fuzz_out中的内容,盲猜crashes里面存放的就是出错的测试用例(显然不用猜),上网查了下,hangs表示超时的测试用例,queue里面是每个不同执行路径的测试用例。
想看具体的crash是什么样的嘛??按下面操作就可以啦。
首先cd到crashes文件夹里
然后使用ls -al命令就能查看到所有crash的“标志信息”(不知道说的准不准确)
下面用xxd指令来查看这两个crash的具体情况
左边是16进制的表示形式(右边好像就是造成crash的具体用例的吧?问问学长再来)
三、目前的小问题
问明白了再来更新
1、编译c文件使用的指令afl-gcc -g -o afl_test afl_test.c
每一步是啥意思讷?
2、通过上面的gcc编译生成的afl_test不会改变吗?插桩插上面了为啥还不变呢?
3、fuzz_in文件夹里边的testcase里的内容是第一个测试用例嘛?以后的用例都是基于它突变而来嘛?具体怎么突变的呢?第一个测试用例的设置有没有什么“讲究”?
4、整个Fuzzing界面各个部位的含义是什么?
5、Fuzzing过程中怎么确定是否可以停下来了?
6、Fuzzing输出的结果查看到了,但结果具体是啥意思捏?
四、问题解答
1、编译c文件使用的指令afl-gcc -g -o afl_test afl_test.c
每一步是啥意思?
afl-gcc是afl所特有的编译器,不过在网上查了一下之后发现,它可以理解为是在gcc表面上套了层壳,实现了gcc的编译功能和afl特有的插桩,实际调用afl-gcc进行编译时,还是会转向调用gcc。-g
就是gcc中的“生成调试信息”的指令,个人感觉在实现编译功能时并不是特别需要吧?-o
可以理解为“-output”
,output所接即为输出文件,剩下一个.c的文件名自然就是需要afl-gcc编译的C文件了。换一种写法其实会更明了一些,afl-gcc afl_test.c -o afl_test
,直接由afl-gcc指向待编译的afl_test.c文件,输出的编译完成的文件命名为afl_test。
2、通过上面的gcc编译生成的afl_test不会改变吗?插桩插上面了为啥还不变呢?
程序的执行过程有4步:先进行预处理,随后进行编译,再对编译生成的文件进行汇编,最后进行链接。预处理时将代码中的头文件换成原始的C程序,并插入到程序中,生成.i
文件,而在编译阶段就将预处理生成的文件编译为.s
文件,该文件再经过汇编生成可直接执行的机器指令,保存为.o
文件,链接时就将C语言标准库中的函数包链接到C程序中。AFL这个工具其实就是在编译时进行插桩的,插桩插的是一段统计代码
(个人感觉是遇到一个统计代码的标记,就在AFL计数,所以afl_test这个文件是不会变的),我之前还以为是先输入测试用例,测试用例在运行过程中每找到一个分支路径就插个桩。
3、待续
应该是这样的,不过具体的突变方式也需要读源码才知道。测试用的设置肯定是有讲究的,afl提供了基本语料库和语料库蒸馏的方法,学了再来分享。
4、整个Fuzzing界面各个部位的含义是什么?
process timing:部分表示Fuzzer运行时长(run time)、以及距离最近发现的路径(last new path)、崩溃(last unique crash)和挂起(last unique hang)经过了多长时间。
overall results:部分表示Fuzzer当前状态的概述,完成的测试轮数、代码路径数量、特定种类的崩溃次数、特定种类的挂起次数。
cycle progress:输入队列的距离。
map coverage:目标二进制文件中的插桩代码所观察到覆盖范围的细节。
Stage progress:Fuzzer现在正在执行的文件变异策略、执行次数和执行速度。
Findings in depth:有关我们找到的执行路径,异常和挂起数量的信息。
Fuzzing strategy yields:关于突变策略产生的最新行为和结果的详细信息。
Path geometry:有关Fuzzer找到的执行路径的信息。
5、Fuzzing过程中怎么确定是否可以停下来了?
主要通过观察afl的状态栏来完成。
(1)”cycles done”字段颜色变为绿色
状态窗口中该字段的颜色可以作为何时停止测试的参考,随着周期数不断增大,其颜色也会由洋红色,逐步变为黄色、蓝色、绿色。当其变为绿色时,继续Fuzzing下去也很难有新的发现了,这时便可以通过Ctrl-C停止afl-fuzz。
(2)距上一次发现新路径(或者崩溃)已经过去很长时间了
这里显示的已经53分钟没有找到新的路径或crash了,估计也差不多了。
(3) 目标程序的代码几乎被测试用例完全覆盖,这种情况比较少见,但是对于某些小型程序还是有可能的。
6、Fuzzing输出的结果查看到了,但结果具体是啥意思?
暂时只知道xxd出来结果一大片的是栈溢出,长度较短的是造成崩溃的字符串用例。。