AFL模糊测试学习(三)AFL运行

以使用afl-gcc进行插桩后的AFL运行为例。我们来学习AFL的运行。main函数中首先识别参数opt = getopt(argc, argv,"+i: o: f: m: t: T: d: n: C: B: S:M: x: Q")。然后在setup_shm()这个函数中初始化MAP_SIZE(2^16)大小的map用来父进程子进程间的信息共享。然后perform_dry_run()这个函数把初始的种子跑一遍,观察是否有问题。

先补充一些知识:

fd文件(File descriptor)
所有都可以抽象成文件,比如普通的文件、目录、块设备、字符设备、socket、管道等等。当通过一些系统调用(如open/socket等),会返回一个fd(就是一个数字)给你,然后根据这个fd对应的文件进行操作,比如读、写。linux默认对每个进程最大能打开的fd的个数是1024(软限制是1024,硬限制4096)。

管道PIPE
管道初始化函数 int pipe(int pipefd[2]); 成功:0;失败:-1

fd[0] → r; fd[1] → w,就像0对应标准输入,1对应标准输出一样。向管道文件读写数据其实是在读写内核缓冲区。在后面AFL的forkserer中父进程和子进程交互就使用了st_pipe[2],ctl_pipe[2]。

这里介绍几个很重要的数据结构和函数:

cull_queue()
种子的数据结构为:

struct queue_entry {

    u8* fname;                          /* File name for the test case      */

    u32 len;                            /* Input length                     */

    u8  cal_failed,                     /* Calibration failed?              */

    trim_done,                      /* Trimmed?                         */

    was_fuzzed,                     /* Had any fuzzing done yet?        */

    passed_det,                     /* Deterministic stages passed?     */

    has_new_cov,                    /* Triggers new coverage?           */

    var_behavior,                   /* Variable behavior?               */

    favored,                        /* Currently favored?               */

    fs_redundant;                   /* Marked as redundant in the fs?   */

    u32 bitmap_size,                    /* Number of bits set in bitmap     */

    exec_cksum;                     /* Checksum of the execution trace  */

    u64 exec_us,                        /* Execution time (us)              */

    handicap,                       /* Number of queue cycles behind    */

    depth;                          /* Path depth                       */

    u8* trace_mini;                     /* Trace bytes, if kept             */

    u32 tc_ref;                         /* Trace bytes ref count            */

    struct queue_entry *next,           /* Next element, if any             */

                     *next_100;       /* 100 elements ahead               */

};

我们知道种子是一系列的,每次通过程序运行该种子的时间、是否产生新路径等信息对种子进行打分排序,对一些没有贡献的种子,pass_det这一项就置为表示循环fuzz的时候将跳过该种子。

run__target()
不管是在一开始的perform_dry_run()还是后面测试过程中一直循环的fuzz_one(),这个是运行插桩程序的函数。在里面先是查询有没有forkserver这么一个子进程,如果没有就建立一个forkserver,同时建立好管道。

在init_forkserver这个环节,会生成两个管道ctl_pipe, st_pipe,分别绑定fd 198,fd 199.然后开始运行程序并且进程间通信。一旦init了child进程就不会退出,每次有新的测试样例,就直接运行,运行的时候child进程fork一个grandchild进程,这个进程一直运行_afl_store()这个函数对shm的bitmap进行修改。

如果是第一次运行(一般在perform_dry_run()这个函数中),就会创建一个子进程。子进程会一直用testcase这个缓冲区中的种子运行程序,通过管道把执行路径交互给父进程。交互过程如下:

AFL模糊测试学习(三)AFL运行_第1张图片

每次运行run_target()函数都会在ctl_pipe写入值,唤醒卡死在read等待的子进程,子进程就可以fork一个孙进程,孙进程执行程序,把所有程序块中插桩的部分执行一遍,把结果写入共享的内存区,就实现了执行路径的记录。


cur_location = ;

shared_mem[cur_location ^ prev_location]++; 

prev_location = cur_location >> 1;

这里的cur_location和prev_location都是程序块的id,这里使用[cur_location ^ prev_location]意为记录边,因为如果一个程序有a、b、c三个基本块,a->b->c 和a->c->b是不同的执行路径,但是只用基本块记录的话都是a、b、c,用边记录的话可以跟好比对运行路径。

关于种子变异,这个是AFL的确定性变异(位翻转、拼接等)和非确定性变异组合而成,在afl-fuzz.c中可以更好学习,就不在这里赘述。

就先写到这里,大家对AFL还有什么疑问,可以留言。

你可能感兴趣的:(AFL模糊测试学习(三)AFL运行)