wzs2001

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

         afl-fuzz.c详解

main函数中fuzz主循环

第一次循环

while(1){
u8 skipped_fuzz;
cull_queue();         //精简队列
skipped_fuzz=fuzz_one(use_argv);   //fuzz_one不执行为1,否则返回0
if(!stop_soon&&sync_id&&!skipped_fuzz){        //fuzz_one未执行
if(!(sync_interval_cnt++ % SYNC_INTERVAL))
     sync_fuzzers(use_argv);       
}
queue_cur=queue_cur->next;     /*开始测试下一个queue*/
current_entry++;
}

 

关键函数

fuzz_one

  if(pending_favored),是否存在受青睐的 

  •如果存在受青睐的,对于这一项被fuzz过或者这一项不是favored,有99%的概率被跳过

  •如果不存在受青睐的,对于不是dumb模式,这一项不是favored,并且测试用例满足一定数量             (queue_path>10,这里阈值为10个),继续进行分支

    •如果当前的循环次数大于1且这一项没被fuzz过,有75%的概率被跳过,不满足条件会有更大的概率被跳           过(95%)

  映射文件内容到内存

  •mmap函数用于将文件按文件内容映射到内存中,接着给out_buf用ck_alloc_nozero分配len大小的全零内存,      最后用cur_depth记录当前测试用例的深度。

  CALIBRATION校准阶段:如果出现校准错误且少于3次,用calibrate再次校准

  TRIMMING修剪阶段:如果当前不是dumb模式且这个测试用例没有被修剪过,调用trim_case函数对in_buf进行                            修剪并复制到out_buf中

  PERFORMANCE SCORE:在确定性模糊阶段前计算性能评分,并根据一些条件决定是否跳过确定性模糊阶段(对                         于相同的输入和相同的程序执行环境,每次运行都会产生相同的输出)

  •首先调用calculate_score计算性能评分,接着如果满足命令行参数-d被指定,当前测试用例被fuzz过,当       前测试用例之前通过确定性测试三者之一可以直接跳转到havoc_stage阶段(同时也会考虑到多个主实例的协       同关系,这里不展开),最后令doing_det=1表示正在进行确定性模糊测试。

SIMPLE BITFLIP (+dictionary construction)

#define FLIP_BIT(_ar, _b) do { \
    u8* _arf = (u8*)(_ar); \
    u32 _bf = (_b); \
    _arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7)); \
  } while (0)

  这里定义了一个比较重要的函数:((_bf)>>3)^表示右移3位做异或,(_bf) & 7中后面一般是len长度,相当于模7,产生了(0,1,2,3,4,5,6,7),(128 >> ((_bf) & 7))就表示将128依次右移0-7个单位产生了二进制 (10000000-1)。

  连起来理解,意思就是每8次为一个循环,128分别右移0、1、2、3、4、5、6、7位,将右移后的数字与 _arf[i]进行异或翻转,相当于每一位都进行一次翻转,如下图

 

  Single walking bit

stage_short = "flip1";    //给当前阶段一个简短的标识符,即'flip1'
stage_max   = len << 3;   //计算当前阶段的最大迭代次数,len是当前测试用例的长度
stage_name  = "bitflip 1/1";

for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {   //每运行8次循环_arf(大小为一个字节)的下标+1

    stage_cur_byte = stage_cur >> 3;

    FLIP_BIT(out_buf, stage_cur);

    if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;

    FLIP_BIT(out_buf, stage_cur);

  把这个阶段命名为"bitflip 1/1",for循环中把out_buf每8位分为一组且每一位都进行一次异或翻转,调用    common_fuzz_stuff进行fuzz,fuzz完毕后再调用FLIP_BIT翻转回来。

  同时bit flip 1/1中对每个比特的最低位做特殊处理,收集符合条件的字符串添加到自动队列中。

  •如果在处理两个相邻比特的最低位时发现执行路径发生变化,代表发现新的token,token可以理解成输入用       例的敏感部分,同时保证只有实际发生位翻转导致执行路径发生变化时,才将最低位添加到字符串中。

  •当目前处于文件末尾且哈希值不变时,在满足一些特定条件时将其添加到自动队列中。

  •a_collect是一个数组,用于保存可能成为token的字符串,a_len的作用记录正在收集的字符串的长度

stage_finds[STAGE_FLIP1]  += new_hit_cnt - orig_hit_cnt;  //flip1新发现的路径和crashes总和
stage_cycles[STAGE_FLIP1] += stage_max;                   //common_fuzz——stuff执行的次数

  Two walking bits

stage_name  = "bitflip 2/1";
stage_short = "flip2";
stage_max   = (len << 3) - 1;     //每次连续翻转这一位和下一位,所以要减一

orig_hit_cnt = new_hit_cnt;   

for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {

  stage_cur_byte = stage_cur >> 3;

  FLIP_BIT(out_buf, stage_cur);
  FLIP_BIT(out_buf, stage_cur + 1);

  if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;

  FLIP_BIT(out_buf, stage_cur);
  FLIP_BIT(out_buf, stage_cur + 1);

}

new_hit_cnt = queued_paths + unique_crashes;

stage_finds[STAGE_FLIP2]  += new_hit_cnt - orig_hit_cnt;
stage_cycles[STAGE_FLIP2] += stage_max;

  之前是每次翻转1bit就进行执行一次common_fuzz_stuff,现在变成每次连续翻转相邻的2bit执行一次      common_fuzz_stuff

  Four walking bits:每次连续翻转4bit

  Walk byte:这里开始以整数个byte为单位进行翻转,与上面按bit有些不同

  •首先不采用flip_bit进行翻转,而是使用0xFF

  •会定义一个effector map:在对一整个byte进行翻转时,如果执行路径和原始路径不一致,,就将该byte在       effector map中标记为1,可以理解为有效的,否则为0。可以理解为如果一整个byte被翻转后,依然无法造       成执行路径的改变,那么这个比特很大概率是不是metadata而是普通的data,对整个的fuzzing过程意义不       大,在后续的变异可以跳过,这里可以理解为是一次对文件格式的启发式判断。

  •如果是dumb模式或者len太短,直接对执行路径整体取反,这样会使执行路径改变,从而令effector map为1

  •当effector map密度过大时,会直接将整个eff_map都标记为1,即使不这样做,也不会省很多时间

  Two walking bytes

if (len < 2) goto skip_bitflip;    //长度小于2.跳转到skip_bitflip
if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) {   //若相邻的两个bytes没有一个被标记为1,那么直接跳过这两个bytes继续遍历后面的
      stage_max--;
      continue;              //跳过当前循环的剩余代码,进入下一次循环
    }
skip_bitflip:
  if (no_arith) goto skip_arith;   //若设置了no_arith,可以跳过算数运算部分(ARITHMETIC INC/DEC),直接进入INTERESTING VALUES

    Four walking bytes:和Two walking bytes类似,没有什么特殊。

ARITHMETIC INC/DEC

  8-bit arithmetics

  这部分是进行加减变异的,只对被eff_map标记为1的进行变异

 for (j = 1; j <= ARITH_MAX; j++) {

      u8 r = orig ^ (orig + j);

      /* Do arithmetic operations only if the result couldn't be a product
         of a bitflip. */

      if (!could_be_bitflip(r)) {

        stage_cur_val = j;
        out_buf[i] = orig + j;

        if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;
        stage_cur++;

      } else stage_max--;

  ARITH_MAX最大为35,就是对目标整数进行+1,+2,...,+35,-1,-2,...,-35的加减变异。同时有两种情况会  选择跳过这次变异。第一个是在eff_map中没有被标记为1的,第二个是变异后的结果与之前bitflip翻转后的结果一致时,就不会再执行fuzz。

 

  16-bit arithmetics,both endians

for (j = 1; j <= ARITH_MAX; j++) { 
      u16 r1 = orig ^ (orig + j),       //j为1时异或后r1为1
          r2 = orig ^ (orig - j),
          r3 = orig ^ SWAP16(SWAP16(orig) + j),   //经过字节顺序翻转(SWAP16)后的异或运算结果
          r4 = orig ^ SWAP16(SWAP16(orig) - j);

同时考虑了大端序和小端序

大端字节序:数据的高位字节存储在低地址,低位字节存储在高地址,考虑十六进制数'0xABCD',存储为'0xABCD'

小端字节序中,存储为'0xDCBA'

 if ((orig & 0xff) + j > 0xff && !could_be_bitflip(r1)) {   //检查是否发生了1byte以上的溢出
                                     并且不是bitflip的产物
     stage_cur_val = j; *(u16*)(out_buf + i) = orig + j; if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; stage_cur++; } else stage_max--;

  orig & 0xff 表示取 orig 的最低字节(低8位),这是为了忽略除最低字节外的其他字节的影响这里为什么一定要大于1比特以上的溢出,猜测是因为对于位翻转来说,它只会影响单个位,而不会导致整个字节的变化,因此,为了确保发生的是更大范围的变化,代码限定了溢出必须至少是1字节,以增加测试的多样性和复杂性。

  32-bit arthmetics,both endians:同上16 bits,同样对大端序和小端序都进行变异。

INTERESTING VALUES 替换变异

  会实现存储一些特殊的数interesting_8,16,32这种用于接下来的替换

  8/8 以字节为单位进行替换变异,考虑eff_map和冗余变换,之后进行fuzz

  16/8 以两个字节为单位,去除之前的冗余,同时考虑大小端序(只有大于一个字节的单位时才需要考虑两种排序方法)

  32/8 以四个字节为单位,原理同16/8

DICTIONARY STUFF

 变异过程分为三部分:user extras(over),user extras(insert),auto extras(over)

  user extras(over)覆盖阶段:将用户提供的extras[]数组以memcpy的方式替换到原文件中,会跳过一些特殊情况,比如extras_cnt>MAX_DET_EXTRAS,out_buf数组与extras相等等等,然后进行fuzz

  user extras(insert)插入阶段:先分配内存空间,把extras.data中的数据复制到ex_tmp中,对新产生的ex_tmp进行fuzz。

  auto extras(over):将另一个数组a_extras的data数据内容依次替换到原文件中,进行fuzz

  截止到目前,已经完成了全部的deterministic fuzz

接下来的变异会充满随机性,对于dumb mode或者从fuzzer则会直接从这里开始

RANDOM HAVOC 随机毁灭阶段

  充满了各种随机生成的变异,是对原文件的“大破坏”,包含很多轮。

    case 0:随机选取某个bit进行翻转

  case 1:随机选取某个byte,将其用随机的interesting value替换

  case 2:随机选取某个word,将其设置为随机的interesting_16,大小端随机选择

  case 3:随机选取某个dword,将其设置为随机的interesting_32,大小端随机选择

  case 4:随机选取byte进行减变异

  case 5:随机选取byte进行加变异

  case 6:随机选取word进行减变异,大小端随机选择

  case 7:随机选取word进行加变异,大小端随机选择

  case 8:随机选取dword进行减变异,大小端随机选择

  case 9:随机选取dword进行加变异,大小端随机选择

  case 10:随机选择byte进行异或翻转

  case 11-12:删除字节,希望使保留文件尽可能小

  case 13:75%克隆+25%插入恒定字节插入

  case 14:随机选取某个位置替换一段随机内容,这段内容75%是原来out_buf[]中的内容,25%是随机生成的一              段相同的数字(随机生成的数字中,50%是原来out_buf中的内容,50%是随机生成)

  case 15:随机选取一个位置,用随机选取的extra token替换

  case 16:随机选取一个位置,用随机选取的extra token插入

SPLICING拼接阶段

  如果上述策略仍然没有发现新的发现,那么就会把当前的输入,随机选取另一个输入进行拼接,回到上一个havoc阶段

posted on 2024-01-06 14:52  学不完根本学不完  阅读(344)  评论(0)    收藏  举报