AFL_qemu_mode


AFL版本:2.52b。

AFL -Q模式

在Fuzzing的对象是无源码的二进制程序时,我们无法通过编译时插桩来插入forkserver以及用于收集代码信息的插桩信息。那么,我们就可以使用QEMU模式来实现二进制程序的动态插桩,收集代码覆盖率。

  1. 在命令行启动AFL的时候加入-Q参数即可使用AFL的qemu模式。
 case 'Q': /* QEMU mode */

        if (qemu_mode) FATAL("Multiple -Q options not supported");
        qemu_mode = 1;

        if (!mem_limit_given) mem_limit = MEM_LIMIT_QEMU;

        break;
  1. 然后在afl-fuzz.c的main函数中判断是否设置了qemu模式。调用get_qemu_argv函数来生成qemu命令。
if (qemu_mode)
    use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind);
  else
    use_argv = argv + optind;

这里传递了三个参数:命令行传入AFL的参数argv[0](也就是afl-fuzz的路径),当前还未处理的参数的地址:argv + optind,还剩下的参数个数:argc - optind。
假设我们启动AFL的命令为:

/path/to/afl-fuzz -i ./in -o ./out -Q /path/to/objdump -d @@

那么get_qemu_argv的第一个参数就是“/path/to/afl-fuzz”,第二个参数就是“/path/to/objdump”的地址,第三个参数是3。此时未处理的三个参数分别是:

/path/to/objdump
-d
.cur_input的路径

static char** get_qemu_argv(u8* own_loc, char** argv, int argc) {

  char** new_argv = ck_alloc(sizeof(char*) * (argc + 4));
  u8 *tmp, *cp, *rsl, *own_copy;

  /* Workaround for a QEMU stability glitch. */

  setenv("QEMU_LOG", "nochain", 1);

  memcpy(new_argv + 3, argv + 1, sizeof(char*) * argc);

  new_argv[2] = target_path;
  new_argv[1] = "--";

  /* Now we need to actually find the QEMU binary to put in argv[0]. */

  tmp = getenv("AFL_PATH");

  if (tmp) {

    cp = alloc_printf("%s/afl-qemu-trace", tmp);

    if (access(cp, X_OK))
      FATAL("Unable to find '%s'", tmp);

    target_path = new_argv[0] = cp;
    return new_argv;

  }

  own_copy = ck_strdup(own_loc);
  rsl = strrchr(own_copy, '/');

  if (rsl) {

    *rsl = 0;

    cp = alloc_printf("%s/afl-qemu-trace", own_copy);
    ck_free(own_copy);

    if (!access(cp, X_OK)) {

      target_path = new_argv[0] = cp;
      return new_argv;

    }

  } else ck_free(own_copy);

  if (!access(BIN_PATH "/afl-qemu-trace", X_OK)) {

    target_path = new_argv[0] = ck_strdup(BIN_PATH "/afl-qemu-trace");
    return new_argv;

  }

  SAYF("\n" cLRD "[-] " cRST
       "Oops, unable to find the 'afl-qemu-trace' binary. The binary must be built\n"
       "    separately by following the instructions in qemu_mode/README.qemu. If you\n"
       "    already have the binary installed, you may need to specify AFL_PATH in the\n"
       "    environment.\n\n"

       "    Of course, even without QEMU, afl-fuzz can still work with binaries that are\n"
       "    instrumented at compile time with afl-gcc. It is also possible to use it as a\n"
       "    traditional \"dumb\" fuzzer by specifying '-n' in the command line.\n");

  FATAL("Failed to locate 'afl-qemu-trace'.");

}

这个函数首先设置环境变量“QEMU_LOG”为“nochain”模式,参数1表示覆盖原有环境变量。
new_argv[3]之后记录目标程序的参数以及@@。
new_argv[2]是目标程序地址
new_argv[1]是“–”
new_argv[0]放“afl-qemu-trace”的路径。
因此afl-qemu-trace存在的情况下,get_qemu_argv函数返回后返回的是

/path/to/afl-qemu-trace -- /path/to/target -目标程序的参数  

build_qemu_support.sh

  1. 首先设置qemu的版本号,生成这个版本号对应的qemu下载路径。
  2. 进行一系列的校验:是否是linux环境、afl-qemu-cpu-inl.h、afl-showmap是否存在以及一些必要的软件是否安装。
  3. 下载qemu,解压。
  4. 用Linux的patch命令把AFL的patch打进去。patch的语法为:
patch [-bceEflnNRstTuvZ][-B <备份字首字符串>][-d <工作目录>][-D <标示符号>]
[-F <监别列数>][-g <控制数值>][-i <修补文件>][-o <输出文件>][-p <剥离层级>]
[-r <拒绝文件>][-V <备份方式>][-Y <备份字首字符串>][-z <备份字尾字符串>]
[--backup-if -mismatch][--binary][--help][--nobackup-if-mismatch]
[--verbose][原始文件 <修补文件>] 或 path [-p <剥离层级>] < [修补文件]
patch -p1 <../patches/elfload.diff || exit 1

-p1: 设置欲剥离1层路径名称
< 后面跟修补文件

  1. 然后就是编译qemu,把对应架构的qemu文件拷贝出来,生成afl-qemu-trace文件。
  2. 然后测试是否能用。

diff文件

首先要看懂diff文件,以elfload.diff为例:

“- - -”表示变动前的文件,“+++”表示变动后的文件。

--- qemu-2.10.0-rc3-clean/linux-user/elfload.c	2017-08-15 11:39:41.000000000 -0700
+++ qemu-2.10.0-rc3/linux-user/elfload.c	2017-08-22 14:33:57.397127516 -0700

@@之间是变动的位置,“-20,6”表示第一个文件的第20行之后连续的6行,“+20,8”表示变动后成为第二个文件的从第20行开始连续的8行。

@@ -20,6 +20,8 @@

后面是变化的内容,+表示第二个文件新增的行。

 #define ELF_OSABI   ELFOSABI_SYSV
 
+extern abi_ulong afl_entry_point, afl_start_code, afl_end_code;
+
 /* from personality.h */
 
 /*

我们可以看一下打patch之前的elfload.c文件:

/* This is the Linux kernel elf-loading code, ported into user space */
#include "qemu/osdep.h"
#include <sys/param.h>

#include <sys/resource.h>

#include "qemu.h"
#include "disas/disas.h"
#include "qemu/path.h"

#ifdef _ARCH_PPC64
#undef ARCH_DLINFO
#undef ELF_PLATFORM
#undef ELF_HWCAP
#undef ELF_HWCAP2
#undef ELF_CLASS
#undef ELF_DATA
#undef ELF_ARCH
#endif

#define ELF_OSABI   ELFOSABI_SYSV

/* from personality.h */

/*
 * Flags for bug emulation.
 *
 * These occupy the top three bytes.
 */

打完patch后:

/* This is the Linux kernel elf-loading code, ported into user space */
#include "qemu/osdep.h"
#include <sys/param.h>

#include <sys/resource.h>

#include "qemu.h"
#include "disas/disas.h"
#include "qemu/path.h"

#ifdef _ARCH_PPC64
#undef ARCH_DLINFO
#undef ELF_PLATFORM
#undef ELF_HWCAP
#undef ELF_HWCAP2
#undef ELF_CLASS
#undef ELF_DATA
#undef ELF_ARCH
#endif

#define ELF_OSABI   ELFOSABI_SYSV

extern abi_ulong afl_entry_point, afl_start_code, afl_end_code;

/* from personality.h */

/*
 * Flags for bug emulation.
 *
 * These occupy the top three bytes.
 */

可以看出来增加了一行

extern abi_ulong afl_entry_point, afl_start_code, afl_end_code;

elfload.diff

  1. 首先extern对外部变量进行生命,告诉程序这些变量是已经定义的外部变量,可以合法使用。
  2. 修改了load_elf_image这个函数,这个函数的作用是把可执行程序加载到内存地址空间。AFL的修改就是在elfload.c函数运行的时候把相应的值记录到afl_entry_point, afl_start_code, afl_end_code里面,分别代表目标程序的entry_point,代码段地址以及代码段结束地址,其他没有做改动。

cpu-exec.diff

  1. 首先添加了头文件,主要是头文件:“afl-qemu-cpu-inl.h”。
  2. 在代码中添加了两行,分别是:
    AFL_QEMU_CPU_SNIPPET2;和AFL_QEMU_CPU_SNIPPET1;

AFL_QEMU_CPU_SNIPPET1的作用是当qemu发现一个未翻译过的基本块的时候,AFL会保存这个基本块的翻译没下一次fork的时候就不用重新翻译了。

AFL_QEMU_CPU_SNIPPET2,就是每次qemu执行一个基本块的时候,afl去check一下这个基本块的地址是不是目标程序的entry_point。如果是,就启动forkerver。如果不是entry_point的话就看一下是不是覆盖了新的分支。如果是的话就记录。

syscall.diff

  1. 添加变量:
    extern unsigned int afl_forksrv_pid;
  2. 应该是当pid等于我们forkserver生成的子程序pid,并且子程序AIGABRT退出时,把子程序进行处理?

configure.diff

换了一下头文件。

afl-qemu-cpu-inl.h

每当AFL_QEMU_CPU_SNIPPET2在每次qemu执行一个基本块的时候就执行以下代码:

#define AFL_QEMU_CPU_SNIPPET2 do { \
    if(itb->pc == afl_entry_point) { \
      afl_setup(); \
      afl_forkserver(cpu); \
    } \
    afl_maybe_log(itb->pc); \
  } while (0)

首先check一下基本块的地址是不是目标程序的入口地址,如果是的话就执行afl_setup()和afl_forkserver(cpu);
afl_setup函数用于设置共享内存区域,让afl_area_ptr指向共享内存区域的地址。
然后执行afl_maybe_log(itb->pc);将执行的trace记录在共享内存中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值