avatar

Catalog
AFL环境搭建

前言

本篇博客以此文的内容为基础进行扩展延申,并记录了用Qemu模式Fuzz时的踩坑过程及解决方案。

AFL简介

  • 概述:AFL(American Fuzzy Lop)是一款开源的Fuzzing测试工具,由Google安全工程师MIchal Zalewski开发

  • Fuzz模式

    • 有源码模式:通过对源码重新编译时进行插桩(instrumentation)的方式,自动产生测试用例来探索二进制程序内部新的执行路径
    • 无源码模式:配合QEMU等工具,对闭源二进制代码进行fuzzing,但执行效率会受到影响
  • 工作原理(有源码模式)

    1. 使用afl-clang/clang++ 或 afl-gcc/g++ 来编译工程代码
    2. 将testcase写入文件(文件大小尽量<1K)作为输入
    3. 启动afl-fuzz,读取testcase(seed),作为输入执行程序
    4. 如果发现新的路径则保存此testcase到一个queue中,afl-fuzz依据最初的testcase进行突变,以产生不同的样例输入
    5. 如果程序崩溃,则记录crash
  • 特点

    与其他基于插装技术的fuzzers相比,afl-fuzz具有较低的性能消耗,有各种高效的fuzzing策略和tricks最小化技巧,不需要先行复杂的配置,能无缝处理复杂的现实中的程序

安装

github下载压缩包,解压后在目录中打开终端输入:

shell
1
2
make
sudo make install

输入以上命令后基本就能安装成功了,在终端输入afl-后tab,就能出现以下这些命令了

说明安装成功

有源码Fuzz

编写测试程序

c
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
35
36
// 编写测试程序test.c,这里头文件都用"",主要是防止博客识别markdown时将其清空。实际编写时请替换回<>
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "string.h"
#include "signal.h"

int vuln(char *str)
{
int len = strlen(str);
if(str[0] == 'A' && len == 66)
{
// 如果输入的字符串的首字符为A并且长度为66,则异常退出
raise(SIGSEGV);
}
else if(str[0] == 'F' && len == 6)
{
// 如果输入的字符串的首字符为F并且长度为6,则异常退出
raise(SIGSEGV);
}
else
{
printf("it is good!\n");
}
return 0;
}

int main(int argc, char *argv[])
{
char buf[100]={0};
gets(buf); // 存在栈溢出漏洞
printf(buf); // 存在格式化字符串漏洞
vuln(buf);

return 0;
}

插桩编译

  • 普通程序编译:
    1. 执行afl-gcc test.c -o afl_test对测试程序进行编译,如果是一个c++的源码,那就需要用afl-g++。afl-clang和afl-clang++的使用方法类似
    2. 建立两个文件夹:fuzz_infuzz_out,用来存放程序的输入和fuzz的输出结果
    3. 在fuzz in中需要创建一个testcase文件,AFL在fuzz时会从该文件中读取测试样例。在本例中,在testcase中写入abc就可以了
  • 编译项目:
    1. 在编译项目时,通常有Makefile,这时就需要在Makefile中添加内容
    2. gcc/g++重新编译目标程序的方法如下:
      • C程序:设置CC=/path/to/afl/afl-gcc ./configure
      • C++程序:设置CXX=/path/to/afl/afl-g++ .
      • 执行make clean all
    3. afl-clang和afl-clang++的使用方法类似

开始Fuzz

对那些可以直接从stdin读取输入的目标程序来说,语法如下:

shell
1
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program […params…]

对从文件读取输入的目标程序来说,要用“@@”,语法如下:

shell
1
$ ./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@

输入命令:afl-fuzz -i fuzz_in -o fuzz_out ./afl_test

表示,从fuzz_in中读取输入,输出放入fuzz_out中,afl_test是我们要进行fuzz的程序,-f参数表示:testcase的内容会作为afl_test的stdin

如果出现如下错误(我没遇到,直接执行成功了):

需要根据提示设置core_pattern:

  • 切换到root用户
  • echo core > /proc/sys/kernel/core_pattern

再次执行afl-fuzz -i fuzz_in -o fuzz_out ./afl_test

AFL界面

执行成功后,便会开始fuzz,出现下图所示界面:

界面上的数据含义都比较清晰,有不明白的地方可以直接参考官方文档

Crash分析

由上图,经过一段时间的fuzz后,发现了6个crash,进入fuzz_out目录下查看情况,其中自动生成了一些文件夹,含义如下:

  • crashes:产生crash的测试用例
  • hangs:产生超时的测试用例
  • queue:每个不同执行路径的测试用例

本次实验我们对crashes文件夹更感兴趣,分别查看导致不同crash的测试样例:

由上图可知,AFL成功检测出我们预先设置的导致crash的情况以及潜在的漏洞。

无源码Fuzz

有源码的Fuzz测试主要针对开源软件进行,对于一些不开源的产品,AFL使用了QEMU模式进行测试,只需要在之前命令的基础上加上参数-Q即可。

准备工作

执行如下指令,完成对QEMU的配置

shell
1
2
3
4
$ cd qemu_mode
$ ./build_qemu_support.sh
$ cd ..
$ make install

执行上述指令时,必定会报错,下面来看可能的错误类型与修复方案。

报错修复

我在执行指令./build_qemu_support.sh时,就遇到报错了,

  1. 看雪找到一个解决的方案是安装qemu

    对于16.0.4之后版本的Ubuntu,Qemu的安装方式为apt-get install qemu-user。执行完该指令后,在终端输入qemu-后tab,就能出现以下这些命令了

  2. 但实际上仍未解决这个问题,在看雪的评论区找到另一个方案:通过打补丁来解决。上述报错中,其中一个问题是“static declaration of ‘gettid’ follows non-static declaration”。这里层主参考qemu的一个补丁,编写了afl的补丁。这里我们直接拿过来用就可以了。完整补丁如下所示:

    diff
    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
    35
    36
    37
    38
    39
    40
    41
    42
    --- qemu-2.10.0-rc3-clean/linux-user/syscall.c	2020-11-06 22:14:34.218924847 -0500
    +++ qemu-2.10.0-rc3/linux-user/syscall.c 2020-11-06 22:17:09.722926317 -0500
    @@ -258,7 +258,8 @@
    #endif

    #ifdef __NR_gettid
    -_syscall0(int, gettid)
    +#define __NR_sys_gettid __NR_gettid
    +_syscall0(int, sys_gettid)
    #else
    /* This is a replacement for the host gettid() and must return a host
    errno. */
    @@ -6221,7 +6222,7 @@
    cpu = ENV_GET_CPU(env);
    thread_cpu = cpu;
    ts = (TaskState *)cpu->opaque;
    - info->tid = gettid();
    + info->tid = sys_gettid();
    task_settid(ts);
    if (info->child_tidptr)
    put_user_u32(info->tid, info->child_tidptr);
    @@ -6365,9 +6366,9 @@
    mapping. We can't repeat the spinlock hack used above because
    the child process gets its own copy of the lock. */
    if (flags & CLONE_CHILD_SETTID)
    - put_user_u32(gettid(), child_tidptr);
    + put_user_u32(sys_gettid(), child_tidptr);
    if (flags & CLONE_PARENT_SETTID)
    - put_user_u32(gettid(), parent_tidptr);
    + put_user_u32(sys_gettid(), parent_tidptr);
    ts = (TaskState *)cpu->opaque;
    if (flags & CLONE_SETTLS)
    cpu_set_tls (env, newtls);
    @@ -11404,7 +11405,7 @@
    break;
    #endif
    case TARGET_NR_gettid:
    - ret = get_errno(gettid());
    + ret = get_errno(sys_gettid());
    break;
    #ifdef TARGET_NR_readahead
    case TARGET_NR_readahead:

    将上述补丁命名为gettid.diff,并保存在patches目录下。然后在build_qemu_support.sh中patch那一段的最后加上patch -p1 <../patches/gettid.diff || exit 1。具体如下图所示:

  3. 过上述操作后,再次执行build_qemu_support.sh时,便解决了gettid的问题。但另一个报错“ ‘SIOCGSTAMPNS’ undeclared here”仍未解决,此时可以参考解决gettid的方式,同样通过补丁的方式来解决。最终,我们找到了一个解决同样问题的补丁。由于需要打补丁的函数syscall本身已经有了相应的补丁文件syscall.diff。因此我们只需要将新的补丁添加至该补丁的开头即可。经修改后的syscall.diff如下所示(红框内容为新添加的部分):

  4. 此时再次执行build_qemu_support.sh,即可成功

  5. 然后回到AFL目录,执行make install。接下来,开始qemu模式下的无源码的fuzz。

开始Fuzz

无源码的fuzz和有源码的fuzz相比,只是多了一个参数Q。这里我们还是用刚刚的测试程序,只不过这里不使用afl-gcc插桩编译,而是用普通的gcc进行编译。然后执行如下语句进行无源码fuzz:

shell
1
$ afl-fuzz -i fuzz_in/ -o fuzz_out/ -Q ./normal_test

然后,非常不幸,又报错了。。。

说是没法定位afl-qemu-trace。这个问题很简单,简单来说就是在终端输入afl-后tab键,不会出现afl-qemu-trace。所以只需将其复制到/usr/local/bin目录下。然后就可以fuzz了。

可以看到,从fuzz的速度和效率上来看,比源码fuzz还是差上不少的。

参考资料

  1. 先知社区:初探AFL-Fuzz
  2. CSDN:ubuntu16.04安装AFL+QEMU
  3. gandalf:AFL(一)源码fuzz
  4. gandalf:AFL(二)afl-qemu无源码fuzz
  5. 看雪:AFL的qemu-mode编译安装问题
  6. patch gettid
  7. patch syscall
  8. Understanding AFL status screnn
Author: cataLoc
Link: http://cata1oc.github.io/2021/12/22/AFL%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶