OneShell

I fight for a brighter tomorrow

0%

使用afl对无源码程序进行测试

使用 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
// cgi_nolen.c
// gcc cgi_nolen.c -o cgi_nolen
#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;
}
// 两条路径:
// GET->URL 两个环境变量导致的缓冲区溢出
// POST->STDIN 一个环境变量+标准输入导致的缓冲区溢出
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
// hook.c
// gcc -D_GUN_SOURCE -shared -fPIC -o hook_getenv.so hook.c -ldl
#define _GNU_SOURCE // 使用RELD_NEXT
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>

char *(*original_getenv_func)(const char *) = NULL;
// 定义全局变量buffer来存储STDIN中读取的数据
char buffer[1024];

char *getenv(const char *name) {
memset(buffer, '\0', sizeof(buffer));
// 这个地方还需要考虑变异出来的数据的长度和大小
if (!original_getenv_func) original_getenv_func = dlsym(RTLD_NEXT, "getenv");
// 这个地方需要定义一个缓冲区才行,用来从stdin中读取数据
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。

undifined