文章目录
AFL版本:2.52b。
AFL -Q模式
在Fuzzing的对象是无源码的二进制程序时,我们无法通过编译时插桩来插入forkserver以及用于收集代码信息的插桩信息。那么,我们就可以使用QEMU模式来实现二进制程序的动态插桩,收集代码覆盖率。
- 在命令行启动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;
- 然后在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
- 首先设置qemu的版本号,生成这个版本号对应的qemu下载路径。
- 进行一系列的校验:是否是linux环境、afl-qemu-cpu-inl.h、afl-showmap是否存在以及一些必要的软件是否安装。
- 下载qemu,解压。
- 用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层路径名称
< 后面跟修补文件
- 然后就是编译qemu,把对应架构的qemu文件拷贝出来,生成afl-qemu-trace文件。
- 然后测试是否能用。
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
- 首先extern对外部变量进行生命,告诉程序这些变量是已经定义的外部变量,可以合法使用。
- 修改了load_elf_image这个函数,这个函数的作用是把可执行程序加载到内存地址空间。AFL的修改就是在elfload.c函数运行的时候把相应的值记录到afl_entry_point, afl_start_code, afl_end_code里面,分别代表目标程序的entry_point,代码段地址以及代码段结束地址,其他没有做改动。
cpu-exec.diff
- 首先添加了头文件,主要是头文件:“afl-qemu-cpu-inl.h”。
- 在代码中添加了两行,分别是:
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
- 添加变量:
extern unsigned int afl_forksrv_pid; - 应该是当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记录在共享内存中。