本来不打算做AFL的源码分析,但是看到了一篇文章吸引到了我,当然这篇文章还是不会涉及到源码分析,在后续的FUZZ相关文章中会进行进一步分析。
fuzzer工作状态
首先可以查看在output
目录下的fuzzer_state
文件:
1 | ➜ afl-2.52b cat ./afl_test/output_dir/fuzzer_stats |
不过这里我在运行时cat几次内容都没有发生变化所以我猜测应该是运行开始时产生,运行结束时才修改内容。那么如果想要实时的查看运行情况的话可以用afl-whatsup
。
1 | ➜ afl-2.52b ./afl-whatsup ./afl_test |
虽然但是,我感觉我这里好像使用有问题,但是先不管那么多了。
再简单介绍一下afl-plot
,这个工具总结出来的内容更为直观,可以直接图形化显示。这里存在一定依赖问题
1 | apt-get install gnuplot |
下面就是输出出来的结果,但是不知道为什么这里的total paths
没有显示,我猜测可能是因为这里不是使用afl-gcc
进行编译或者就是我的电脑性能太拉了。这里的uniq crashes
开始在增加随后逐渐趋于平稳。最后一个就是执行速度,随着时间的推移也是越来越慢了,当然也可能是因为占用了太多的系统资源。
然后再说说pythia插件吧,这个插件可以看到发现新的crash和path的概率。他与原版也只是差了几个字段。
这里在process timing
里面框中出现了两个新的字段分别是correctiness
和fuzzability
,他们的含义分别是在没有发现crash时,发现一个导致crash输入的概率,表示在该程序中发现新路径的难度,该数值越高代表程序越容易Fuzz。在overall results
框中也多了两个,第一个是当前发现的路径,一个是路径覆盖率。
何时关闭fuzz
用过就能知道的是fuzz其实是无限执行下去的,这里可以用上面的几种方法来观察是否该结束,当然这里也可以直接在原始的AFL中看到何时该结束,注意这里cycles done
的颜色,在fuzz的过程中这个颜色是一直会变化的,可以看到上面的图中颜色为紫色,这里为蓝色。所以他的变化顺序为紫色->黄色->蓝色->绿色,当为绿色是就代表很难再找到新的crash了,而这个时候就可以结束了。(下图为蓝色主要是受我电脑性能影响的)
1 | ➜ output_dir tree |
queue:存放所有具有独特执行路径的测试用例。
crashes:导致目标接收致命signal而崩溃的独特测试用例。
crashes/README.txt:保存了目标执行这些crash文件的命令行参数。
hangs:导致目标超时的独特测试用例。
fuzzer_stats:afl-fuzz的运行状态。
plot_data:用于afl-plot绘图。
处理测试结果
这里只介绍两种方式,一是crashwalk
再就是afl-collect
crashwalk
安装过程这里就不再说了,网上很多,搜搜就有。
首先这个有两种模式,一是Manual Mode
其次就是AFL Mode
,他们的命令新式分别如下。
1 | ~/tools/go/bin/cwtriage -root syncdir/fuzzer1/crashes/ -match id -- ~/parse @@ |
这里使用的是第一种方式(用AFL时我这里会出现无法no crash detected
错误)。
可以看到这里的描述中写上了栈溢出漏洞。
afl-collect
这个工具使用命令如下:
1 | python3 ./afl-collect -j 8 -d crashes.db -e gdb_script ./afl_sync_dir ./collection_dir -- /path/to/target --target-opts |
结果比上面的更为直观。
代码覆盖率
代码覆盖率是模糊测试中一个极其重要的概念,使用代码覆盖率可以评估和改进测试过程,执行到的代码越多,找到bug的可能性就越大,毕竟,在覆盖的代码中并不能100%发现bug,在未覆盖的代码中却是100%找不到任何bug的,所以本节中就将详细介绍代码覆盖率的相关概念。
代码覆盖率是一种度量代码的覆盖程度的方式,也就是指源代码中的某行代码是否已执行;对二进制程序,还可将此概念理解为汇编代码中的某条指令是否已执行。其计量方式很多,但无论是GCC的GCOV还是LLVM的SanitizerCoverage,都提供函数(function)、基本块(basic-block)、边界(edge)三种级别的覆盖率检测,更具体的细节可以参考LLVM的官方文档。
基本块
只有一个入口点,BB中的指令不是任何跳转指令的目标。
只有一个退出点,只有最后一条指令使执行流程转移到另一个BB
如下图中的代码就可以被切割为4个基本块,平时我们在IDA图形模式中看到的就是一个一个的基本块
拿一个程序举例,在ida中每一块就代表一个基本块。
边
依旧是上面ida的图,每一条线,也就是每一个箭头就代表一个边。
元组
在AFL中,使用二元组(branch_src, branch_dst)来记录当前基本块 + 前一基本块的信息,从而获取目标的执行流程和代码覆盖情况,伪代码如下:
1 | cur_location = <COMPILE_TIME_RANDOM>; //用一个随机数标记当前基本块 |
实际插入的汇编代码,如下图所示,首先保存各种寄存器的值并设置rcx,然后调用__afl_maybe_log
,这个方法的内容相当复杂,这里就不展开讲了,但其主要功能就和上面的伪代码相似,用于记录覆盖率,放入一块共享内存中。
计算代码覆盖率
这里计算代码覆盖率主要是介绍两个工具,一是GCOV另一个则是LCOV它是GCOV的前端。
可以看到这里有覆盖率之类的东西,当然也可以在网页中打开
点开文件会有更为详细的数据,每行代码前的数字代表被执行的次数,其中红色的代表未执行过的。