使用 afl 对无源码的程序进行 fuzz
有源码的情况下,可以使用 afl 相关的编译器进行编译插桩,然后进行 fuzz;如果是无源码的情况下,就需要使用 afl 的 qemu mode 进行 fuzz。这个地方我使用的是 AFLPlusPlus,之前已经大概看过 afl 的源码,afl++ 源码的结构更加清晰,因此后面都是在使用 afl++ 作为 fuzz 工具。
待 fuzz 程序和函数劫持 so
手上有的程序是 x64 的一个简易 cgi,源码如下,是从环境变量中读取数据存放在 buffer 中,当前的目标就是对其中的一个环境变量进行模糊测试,使用 hook 对 REQUEST_METHOD 赋予给定值 GET,然后对 QUERY_STRING 环境变量从标准输入中读取。具体的 hook 实现过程可以参考如何劫持一个 cgi 的 getenv 函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h>
int main(int argc, char** argv) { char* method = getenv("REQUEST_METHOD"); char* url; char buffer[256]; memset(buffer, '\0', sizeof(buffer)); if (!method) { printf("[!] no init env!\n"); return 0; } if (!strcmp(method, "GET")) { printf("[+] this is get method\n"); url = getenv("QUERY_STRING"); printf("[+] get query string %s\n", url); printf("[*] copy url to buffer\n"); strcpy(buffer, url); printf("[+] buffer is %s\n", buffer); } else if (!strcmp(method, "POST")) { printf("[+] this is post method\n"); gets(buffer); printf("[+] get stdin: %s\n", buffer); } return 0; }
|
hook 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
#define _GNU_SOURCE #include <stdio.h> #include <string.h> #include <dlfcn.h>
char *(*original_getenv_func)(const char *) = NULL;
char buffer[1024];
char *getenv(const char *name) { memset(buffer, '\0', sizeof(buffer)); if (!original_getenv_func) original_getenv_func = dlsym(RTLD_NEXT, "getenv"); char *result = original_getenv_func(name); printf("[hook] hook env %s, origin = %s\n", name, result); if (!strncmp(name, "REQUEST_METHOD", 14)) { strncpy(buffer, "GET\0", strlen("GET\0")); printf("[hook] changing to %s\n", buffer); return buffer; } else if (!strncmp(name, "QUERY_STRING", 12)) { printf("[hook] hook env URL,change to STDIN\n"); gets(buffer); printf("[hook] from STDIN %s\n", buffer); return buffer; } else { return NULL; }
return NULL; }
|
编译 qemu-mode
要使用 qemu-mode,首先要编译和待 fuzz 程序架构一致的 afl-qemu-trace。打开 qemu-mode 文件夹,准备编译。
1 2 3
| CPU_TARGET=x86_64 ./build_qemu_support.sh cd .. sudo make install
|
如果要 fuzz 其他架构的程序,那么对应把 CPU_TARGET 进行设定即可,例如 arm、i386 等等。
设置 QEMU 的 so 劫持
正常情况下如果要劫持一个程序的某些函数,使用环境变量 LD_PRELOAD 即可。afl 的 qemu mode 本质上也是使用的 qemu,如果要劫持待 fuzz 程序的函数,需要在启动 afl 的时候使用环境变量 QEMU_SET_ENV 设置程序在 QEMU 运行下的环境变量。那么 fuzz 的启动命令如下:
1
| QEMU_SET_ENV=LD_PRELOAD="./hook_getenv.so" REQUEST_METHOD=GET afl-fuzz -i input -o output -m none -Q ./cgi_nolen
|
可以对上面的启动命令做解释:
QEMU_SET_ENV=LD_PRELOAD="./hook_getenv.so"
设置待 fuuz 程序的环境变量,设置劫持
REQUEST_METHOD=GET
相当于是将 afl-fuzz 和待 fuzz 程序的 REQUEST_METHOD 值都设置成了 POST,因为在 fuzz 的时候使用 fork 创建的子进程继承了 afl-fuzz 的环境变量。
afl-fuzz -i input -o output -m none -Q ./cgi_nolen
从 input 文件夹读取种子,fuzz 结果存放在 output 文件夹,无内存限制,使用 QEMU 模式。
跑了 11 分钟,出来了 5000 多个 crash,其中的两个是 unique crash。