AFL(american fuzzy lop)学习二

AFL(american fuzzy lop)学习二

@sizaif

@2022-04-10

设计思想

覆盖率计算

改进边缘覆盖:

向目标程序注入以下工具来捕获分支(边缘)覆盖率和分支命中计数

  • 一条边表示为当前基本块(分配了一个随机常数)与前一个基本块(即元组 (prev_location, cur_location))之间的 XOR
  • 覆盖信息存储在一个紧凑共享的64KB的哈希表中,称为跟踪位图(bitmap)
  • 当访问一条边时,通过增加与该特定边的哈希相对应的位图中的值来记录命中
cur_location = <COMPILE_TIME_RANDOM>; # cur_location 值是随机生成的,以简化链接复杂项目的过程并保持 XOR 输出均匀分布。
shared_mem[cur_location ^ prev_location]++; # 由调用者传递给检测的二进制文件。
prev_location = cur_location >> 1; # 保持元组的方向性 以区分 A^B 和 B^A ,并保持紧密循环的标识,区分A^A,B^B

image-20220409092606122

**简单块计算存在的问题:**对已执行的基本块进行技术会导致信息丢失

例如: 已执行 BB1->BB2->BB3->BB4, 新的路径: BB1->BB2->BB4, BB1->BB2->BB3 未被计入,路径丢失

image-20220410102945216

相比简单的块覆盖计算:可以区分一下路径

  BB1 -> BB2 -> BB3 -> BBB4  (tuples: BB1BB2, BB2BB3, BB3BB4)
  BB1 -> BB2 ->BBB4  (tuples: BB1BB2, BB2BB4)
  BB1 -> BB2 ->BBB3  (tuples: BB1BB2, BB2BB3)

检测新行为

采用以下方法:

  1. fuzzer 维护先前执行中元组的全局映射;当一个变异的输入产生一个包含新元组的执行跟踪时,相应输入文件被保留等后续处理。
  2. 不触发执行跟踪中新的局部规模状态转换(即不产生新元组)的输入将被丢弃,即使它们的整体控制流序列是唯一的。

优势:

  1. 避免路径爆炸
  2. 达到较高的细粒度探索
  3. 不必对复杂的执行轨迹进行任何计算密集型和脆弱的全局比较

example: #2 存在(CA,AE)被认为是新路径,

  #1: A -> B -> C -> D -> E
  #2: A -> B -> C -> A -> E

处理路径#2 时,由于存在(CD,CA), 会出现以下的路径,但不会被视为新的路径

  #3: A -> B -> C -> A -> B -> C -> A -> B -> C -> D -> E

除了检测新元组外,模糊器还考虑粗略的元组命中计数, 分为以下桶:

1、2、3、4-7、8-15、16-31、32-127、128+

桶允许将仪器生成的 8 位计数器就地映射到 fuzzer 可执行文件依赖的 8 位位图,以跟踪已经看到的每个元组的执行计数

模糊策略

确定性策略包括:

  • 具有不同长度和步距的连续位翻转,(Sequential bit flips with varying lengths and stepovers)

  • 小整数的顺序加减法,(Sequential addition and subtraction of small integers)

  • 已知有趣整数的顺序插入(0、1、INT_MAX 等),(Sequential insertion of known interesting integers (0, 1, INT_MAX, etc))

非确定性步骤包括:

  • 堆叠位翻转 (stacked bit flips)
  • 插入 (insertions)
  • 删除 (deletions)
  • 算术 (arithmetics)
  • 不同测试用例的拼接 (splicing of different test cases)

afl-as.c

设计思想:

当使用 afl-gcc / afl-clang 编译程序时,工具链会自动寻找afl-as的位置,并调用此文件编译的程序afl-as

此汇编包装器的目的是

  1. 预处理由GCC/clang生成的汇编文件

    1. 处理输入的参数命令,为调用真正的as汇编器准备。
  2. 并在分支处插桩 afl-as.h 中包含的检测位。

    1. 处理输入文件,生成 modified_file。 根据afl-as.h在合适的的地方插桩。

      插桩执行代码:

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

      其本质上想捕获如下位置:

               ^main:      - function entry point (always instrumented)
               ^.L0:       - GCC branch label
               ^.LBB0_0:   - clang branch label (but only in clang mode)
               ^\tjnz foo  - conditional branches
      
  3. 使用execvp调用as进行真正的汇编

    execvp(as_params[0], (char**)as_params)
    

afl-as.h 插桩部分

// 伪代码
cur_location = <COMPILE_TIME_RANDOM>; # cur_location 值是随机生成的,以简化链接复杂项目的过程并保持 XOR 输出均匀分布。
shared_mem[cur_location ^ prev_location]++; # 由调用者传递给检测的二进制文件。
prev_location = cur_location >> 1; # 保持元组的方向性 以区分 A^B 和 B^A ,并保持紧密循环的标识,区分A^A,B^B

说明:

存储 XOR异或数据对: 1) 当前执行分支的标识符; 2)之前执行的分支的标识符。即TL,DR 执行 shm_trace_map[cur_loc ^ prev_loc]++

插桩操作

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

fprintf()将格式化字符串添加到汇编文件的相应位置,trampoline_fmt_64的具体内容如下:

/**
 * 这一段汇编代码,主要的操作是:
 * 保存rdx等寄存器
 * 将rcx的值设置为fprintf()所要打印的变量内容
 * 调用方法__afl_maybe_log()
 * 恢复寄存器
 */
static const u8* trampoline_fmt_64 =
  "\n"
  "/* --- AFL TRAMPOLINE (64-BIT) --- */\n"
  "\n"
  ".align 4\n"
  "\n"
  "leaq -(128+24)(%%rsp), %%rsp\n"
  "movq %%rdx,  0(%%rsp)\n"
  "movq %%rcx,  8(%%rsp)\n"
  "movq %%rax, 16(%%rsp)\n"
  "movq $0x%08x, %%rcx\n"
  "call __afl_maybe_log\n"
  "movq 16(%%rsp), %%rax\n"
  "movq  8(%%rsp), %%rcx\n"
  "movq  0(%%rsp), %%rdx\n"
  "leaq (128+24)(%%rsp), %%rsp\n"
  "\n"
  "/* --- END --- */\n"
  "\n";

__afl_maybe_log作为插桩代码所执行的实际内容,分析"movl $0x%08x, %%ecx\n"这条指令

R(MAP_SIZE)即为上述指令将rcx设置的值,即为。根据定义,宏MAP_SIZE为64K,;R(x)的定义是(random() % (x)),所以R(MAP_SIZE)即为0到MAP_SIZE之间的一个随机数。

因此,在处理到某个分支,需要插入桩代码时,afl-as会生成一个随机数,作为运行时保存在ecx中的值。而这个随机数,便是用于标识这个代码块的key。

检查 SHM 区域是否已映射

static const u8* main_payload_64 = 
·····
  "  /* Check if SHM region is already mapped. */\n"
  "\n"
  "  movq  __afl_area_ptr(%rip), %rdx\n"
  "  testq %rdx, %rdx\n"
  "  je    __afl_setup\n"
  "\n"
·····

分支记录

static const u8* main_payload_64 = 
······
"__afl_store:\n"
  "\n"
  "  /* Calculate and store hit for the code location specified in rcx. */\n"
  "\n"
#ifndef COVERAGE_ONLY
  "  xorq __afl_prev_loc(%rip), %rcx\n"
  "  xorq %rcx, __afl_prev_loc(%rip)\n"
  "  shrq $1, __afl_prev_loc(%rip)\n"
#endif /* ^!COVERAGE_ONLY */
  "\n"
#ifdef SKIP_COUNTS
  "  orb  $1, (%rdx, %rcx, 1)\n"
#else
  "  incb (%rdx, %rcx, 1)\n"
#endif /* ^SKIP_COUNTS */
·····

对应伪代码 shared_mem[cur_location ^ prev_location]++;

具体地,变量__afl_prev_loc保存的是前一次跳转的”位置”,其值与rcx做异或后,保存在rip中,并以rdx(共享内存)为基址,对rcx下标处进行加一操作。而rip的值右移1位后,保存在了变量__afl_prev_loc中。

则这里的rcx,保存的为伪代码中的cur_location。回忆之前介绍代码插桩的部分:

static const u8* trampoline_fmt_64 =
······
  "movq $0x%08x, %%rcx\n"
  "call __afl_maybe_log\n"
······

在每个插桩处,afl-as会添加相应指令,将rcx的值设为0到MAP_SIZE之间的某个随机数,从而实现了伪代码中的cur_location = <COMPILE_TIME_RANDOM>;

因此,AFL为每个代码块生成一个随机数,作为其“位置”的记录;随后,对分支处的”源位置“和”目标位置“进行异或,并将异或的结果作为该分支的key,保存每个分支的执行次数。用于保存执行次数的实际上是一个哈希表,大小为MAP_SIZE=64K

afl-gcc.c

**设计思想: **

  1. 当前系统路径中找到afl-as汇编器所在位置,调用afls-as, afl-as以as的别名形式存在

    插桩检测位。编译器默认优先使用该路径中的汇编器和链接器,即 afl-as,实际的插桩工作发生在汇编的时候

  2. 读取输入的参数命令,根据输入的参数命令

    CC=%s/afl-gcc ./configure
    CXX=%s/afl-g++ ./configure
    

    1). 判断使用的afl编译器对应真实的编译器 gcc/g++clang/clang++

    将真实编译器填入cc_params[0]

    afl-gcc -> gcc
    afl-g++ -> g++
    afl-clang++ ->clang++
    afl-clang -> clang
    

    image-20220410202752730

    2). 读取其他相应的参数,并填充到cc_params数组中

  3. 调用系统命令execvp 执行真实的编译器

    execvp(cc_params[0], (char**)cc_params);
    

    execvp()的基本语法 (Basic Syntax of execvp())

    This function takes in the name of the UNIX command to run, as the first argument.

    该函数将要运行的UNIX命令的名称作为第一个参数。

    This is there in the <unistd.h> header file, so we must include it in our program.

    该文件位于<unistd.h>头文件中,因此我们必须将其包含在程序中。

    #include <unistd.h>
    int execvp(const char* command, char* argv[]);
    

    Here, we refer to a “command” as any binary executable file that is a part of the PATH environment variable. So, if you want to run custom programs, make sure that you add it to your PATH variable!

    在这里,我们将“命令”称为PATH环境变量一部分的任何二进制可执行文件。 因此,如果要运行自定义程序,请确保将其添加到PATH变量中!

    The second argument (argv) represents the list of arguments to command. This is an array of char* strings.

    第二个参数( argv )表示command的参数列表。 这是一个char*字符串数组。

    Here, argv contains the complete command, along with it’s arguments.

    在这里, argv包含完整的命令及其参数。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值