AFL模糊测试学习(一)源码插桩


先简单介绍一下

    刚接触模糊测试肯定是要从现在最为流行的AFL开始,现在很多关于AFL的改进都是针对AFL的,而且AFL本身因为其遗传算法、高吞吐量而得到很好的应用,在一些AFL变异版本(如AFLGO、PFuzz、CollAFL、ENFuzz等)、混合模糊测试(结合模糊测试和符号化执行)在实验对比中都拿AFL当作baseline。我们就一步一步从AFL本身学起,对模糊测试进行深入学习吧。

    对一个程序进行模糊测试我们需要做到以下几点:

1、能够变异很多的测试样例,把种子(测试样例)进行自生产

2、在程序以一个种子作为输入后,能够观察程序是否会走新的路径。

    这样一来就可以把能触发不同执行路径的种子进行收集,当有种子是程序运行产生错误或者延迟的时候进行记录,就可以利用工具看程序在哪存在问题。

    拿到AFL源码(github有)后进行make,make install编译安装后就可以用了。Make过程中会出现缺少什么依赖之类的,该安装就安装,就可以编译成功了。为了获得对一个程序执行路径的掌握,需要插桩,有程序源码的话用内置的afl-gcc进行源码插桩,插桩后就可以进行模糊测试了,命令行为./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@(@@表示程序执行的时候会把indir里面的测试样例当作参数放在命令行中,-i后面是存放测试样例的文件夹,-o后面是存放输出结果的文件夹,./后面是插桩过的二进制程序)。

    但是如果只有二进制程序的话,使用QEMU模式进行插桩(速度会慢二到五倍)。在之后会进行讲解。



使用afl-gcc插桩

afl-as作用

    我们先看一下afl-gcc的源码,其实afl-gcc就是对gcc的一个封装,会在gcc的基础上调用afl-as。

gcc编译流图

    afl-as是对.s文件进行插桩操作,在其中有跳转的的位置插入汇编码,实现在程序跳转时能够通过在跳转处的插桩掌握程序的执行路径。在函数add_instrumentation()中对输入的汇编代码.s文件进行操作,下列代码将识别跳转(je,jnz之类)。

if (line[0] == '\t') {

      if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) {

        fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, R(MAP_SIZE));

        ins_lines++;

      }

      continue;

    }

    其中R(MAP_SIZE)为随机值,修改了rcx中的值,调用__afl_maybe_log。我们可以在使用afl-gcc编译后的二进制程序反汇编,看到跳转处有插桩。拿一个简单的例子为例

int main(int argc, char *argv[])

{

    if(argc>1)

        printf("yes");

    else

        printf("no");

    return 0;

}

    把编译好的文件反汇编可以看到插入的汇编代码:


插桩后的汇编代码

    之前提到过,在跳转处插入了调用random封装而成的R(MAP_SIZE)函数生成一个随机值存入rcx寄存器,再进入堆栈成为_afl_maybe_log的参数。那么当AFL在选择输入种子之后就会运行程序,没到一个基本块就会执行一次_afl_maybe_log,这样一来,每个基本块相当于有了自己的id,afl执行了哪些基本块就会记录id,从而得到执行路径。关于AFL怎么记录基本块ID,怎么比对不同种子下的执行路径,我们会在第三节AFL的运行中详细讲。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容