FUZZ学习
2023-11-20 10:59:54 # extra

前言

对于FUZZ的大名早有耳闻 今天终于开始正式学习这一知识
水平有限 本篇文章可能部分地方存在描述错误等问题
本篇文章使用AFL模糊器

什么是FUZZ

为了弄清楚FUZZ的概念 拜读了《Fuzzing: Art, Science, and Engineering》这篇经典的论文 如果有想看我阅读后总结的 可以去看另外一篇博客 这里就简单概述
FUZZ相比传统的软件测试 其包含的漏洞预测器用于决定测试过程中是否违反了安全策略
二者的目的性不一样 而FUZZ又分为三种 白盒 黑盒 灰盒 三者最大的差别在于模糊器对于PUT(待测程序)的了解程度有多少 是否知晓PUT的内部逻辑等
FUZZ采用模糊算法来生成随机的测试样例 部分复杂的模糊配置可以演变种子池来迭代测试样例 通常依靠代码覆盖率(即PUT执行路径)

配置FUZZ环境

1
2
3
4
wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz
tar xvf afl-latest.tgz
cd afl-2.52b
sudo make && sudo make install

上述是afl的安装 我个人建议是使用afl++ 不过本篇文章均采用afl
同时上述方式安装的afl版本是2.52 实际上最新的版本为2.57 需要去github上下载源代码后make编译 这里看各位的需求

第一次FUZZ

我们先来自己编写一个程序 逻辑很简单 输入对应的字符串就触发段错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
int main(int argc, char *argv[]) {
    char buf[100];
    scanf("%s",buf);
    char buf1[] = "aaaa";
    if(!strcmp(buf,buf1)){
        printf("success!\n");
        raise(SIGSEGV);
    }else{
        printf("faile\n");
    }
}

随便创建两个空目录 一个用来存放测试样例 一个用来存放输出信息
本次模糊测试 测试样例由我们自己填写
image.png
随后使用-i指定前者 -o指定后者 开始fuzz

1
afl-fuzz -i ./fuzz/in -o ./fuzz/out ./test

当然了 如果你使用的是afl 那么你就会发现 模糊器读取到第二个样例的时候就终止了
image.png
原因在于afl的测试样例貌似不能直接导致PUT触发crash
所以这里更改第二个样例 使其为aaa 让模糊器使其迭代 延伸成aaaa这个字符串
随后重新开始fuzz 发现成功找到了三个crash
image.png

看到官方文档说 如果要让模糊器运行完毕 需要几个小时到一周左右 所以这里直接ctrl+c终止了
官方文档提到

1
crashes/ - unique test cases that cause the tested program to receive a fatal signal (e.g., SIGSEGV, SIGILL, SIGABRT). The entries are grouped by the received signal.

所以去crashes目录下找到了三个触发崩溃的样例
image.png
第一个不出所料 是我们原定的字符串aaaa 第二个和第三个有点意外 貌似也是一些无规则字节 打算动调来看看 利用hexdump获取一下16进制格式的ascii字节码
image.png

1
\x68\x68\x68\x68\x32\x00\x00\x68\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xCE\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\x68\x87\x68\xE8\x00\x10\x68\x7F\xFF\xF0\xF0\xF0\xF0\xF0\xCF\xF0\xF0\xF0\xE4\xF0\xF0\xF0\x70\xF0\x68\x68\x68\x68\x32\x68\x68\x68\x68\x68\x68\xE8\x68\x68\x68\x68\xF0\xF0\xF0\xF0\xF0\xF0\xCF\xF0\xF0\xF0\xF0\x68\x68\xF0\xE9\xF0\xF0\xFB\xF0\xCF\xF0\xF0\xF0\xF0\xF0\xF0\x07\x70\xF0\x68\x68\x68\x68\x68\x68\x68\x6E\x00\x00\x01\x00\x70\xF0\x68\x68\x68\x68\x68\x68\x68\x68\x68\x68\xDF\xD1

调试exp:

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import*
io = process("./test")
elf = ELF("./test")
context.arch = "amd64"
context.log_level = "debug"
context.terminal = ['tmux','splitw','-h']


payload = "\x68\x68\x68\x68\x32\x00\x00\x68\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xCE\xC6\xC6\xC6\xC6\xC6\xC6\xC6\xC6\x68\x87\x68\xE8\x00\x10\x68\x7F\xFF\xF0\xF0\xF0\xF0\xF0\xCF\xF0\xF0\xF0\xE4\xF0\xF0\xF0\x70\xF0\x68\x68\x68\x68\x32\x68\x68\x68\x68\x68\x68\xE8\x68\x68\x68\x68\xF0\xF0\xF0\xF0\xF0\xF0\xCF\xF0\xF0\xF0\xF0\x68\x68\xF0\xE9\xF0\xF0\xFB\xF0\xCF\xF0\xF0\xF0\xF0\xF0\xF0\x07\x70\xF0\x68\x68\x68\x68\x68\x68\x68\x6E\x00\x00\x01\x00\x70\xF0\x68\x68\x68\x68\x68\x68\x68\x68\x68\x68\xDF\xD1"
gdb.attach(io,'b *$rebase(0x1244)')
io.sendline(payload)
pause()

在程序执行到pthread_kill函数后 回溯一下执行流 发现了检查canary的函数
image.png
应该是由于栈溢出触发的crash
那么接下来来看第三个
image.png
看这个长度应该也是因为栈溢出导致的crash 那么这里就不进一步动调了

黑盒测试以及读取文件内容

本小节用来记录自己对于afl官方文档阅读后的理解和实操 没啥重要性
文档中粗略介绍了afl所采用的模糊算法
总结一下 可以得到下图
image.png
大体是和论文中描述的模糊器的五个功能大差不差
随后想要研究一下afl的黑盒测试功能 然后在配置环境的时候 不得不说是真的遇到一堆报错
首先进入下载的afl源码目录中的qemu-mode目录 运行build_qemu_support.sh

1
2
cd qemu-mode
./build_qemu_support.sh

这里我首先遇到的是下载qemu的网址报错404 但是也不是虚拟机代理问题 我宿主机也访问不到
进入build文件中 找到对应代码的位置
image.png
将QEMU_URL更改为

1
https://download.qemu.org/qemu-${VERSION}.tar.xz

随后就可以正常下载了 接下来遇到的问题是其默认使用的是python 需求是python2
所以还需要加一个软连接 使python可以指向python2

1
sudo ln -s /usr/bin/python2.7 /usr/bin/python

随后虽然可以正常执行一段时间 最后还是遇到了一个报错
/home/chen/AFL/qemu_mode/qemu-2.10.0/linux-user/syscall.c:261:16: error: static declaration of ‘gettid’ follows non-static declaration
找到qemu-mode中的patches目录 更改syscall.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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
--- qemu-2.10.0-clean/linux-user/syscall.c	2020-03-12 18:47:47.898592169 +0100
+++ qemu-2.10.0/linux-user/syscall.c 2020-03-12 19:16:41.563074307 +0100
@@ -34,6 +34,7 @@
#include <sys/resource.h>
#include <sys/swap.h>
#include <linux/capability.h>
+#include <linux/sockios.h> // https://lkml.org/lkml/2019/6/3/988
#include <sched.h>
#include <sys/timex.h>
#ifdef __ia64__
@@ -116,6 +117,8 @@ int __clone2(int (*fn)(void *), void *ch
#include "qemu.h"

+extern unsigned int afl_forksrv_pid;
+
#ifndef CLONE_IO
#define CLONE_IO 0x80000000 /* Clone io context */
#endif

@@ -256,7 +259,9 @@ static type name (type1 arg1,type2 arg2,
#endif

#ifdef __NR_gettid
-_syscall0(int, gettid)
+// taken from https://patchwork.kernel.org/patch/10862231/
+#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. */
@@ -6219,7 +6224,8 @@ static void *clone_func(void *arg)
cpu = ENV_GET_CPU(env);
thread_cpu = cpu;
ts = (TaskState *)cpu->opaque;
- info->tid = gettid();
+ // taken from https://patchwork.kernel.org/patch/10862231/
+ info->tid = sys_gettid();
task_settid(ts);
if (info->child_tidptr)
put_user_u32(info->tid, info->child_tidptr);
@@ -6363,9 +6369,11 @@ static int do_fork(CPUArchState *env, un
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);
+ // taken from https://patchwork.kernel.org/patch/10862231/
+ put_user_u32(sys_gettid(), child_tidptr);
if (flags & CLONE_PARENT_SETTID)
- put_user_u32(gettid(), parent_tidptr);
+ // taken from https://patchwork.kernel.org/patch/10862231/
+ put_user_u32(sys_gettid(), parent_tidptr);
ts = (TaskState *)cpu->opaque;
if (flags & CLONE_SETTLS)
cpu_set_tls (env, newtls);
@@ -11402,7 +11410,8 @@ abi_long do_syscall(void *cpu_env, int n
break;
#endif
case TARGET_NR_gettid:
- ret = get_errno(gettid());
+ // taken from https://patchwork.kernel.org/patch/10862231/
+ ret = get_errno(sys_gettid());
break;
#ifdef TARGET_NR_readahead
case TARGET_NR_readahead:

随后成功完成环境配置
更多报错可以参考该文章: https://blog.csdn.net/qysh123/article/details/114792891?utm_term=aflqemu%E6%A8%A1%E5%BC%8F&utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~sobaiduweb~default-0-114792891&spm=3001.4430
但是随后我们尝试使用-Q选项开始模糊测试 但是发现其找不到afl-qemu-trace
只需要添加环境变量AFL_PATH为afl目录的路径即可
这里还是使用上述的PUT进行测试 不过是用gcc将其编译
image.png
成功获取到两个crash
image.png
不出所料 一个应该是canary导致的栈溢出 一个是因为我们既定的字符串触发的crash
接下来试着研究了下PUT输入样例的两个方式
一种是直接从stdin输入 像本篇文章一直使用的PUT那样
还有一种是从文件中输入 那么接下来就重写一个PUT来实现第二种
不过我们先来搞清楚第一种
官方文档中 记录的指令是这样

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

[params]比较让我在意 这是否意味着不需要in目录? 可以直接从命令行输入测试样例
删除了in目录中的所有测试样例后 我想通过命令行向其传输aaa这个测试样例
image.png
结果还是失败了 那么为了验证 命令行输入的测试样例究竟是否起到了作用 接下来做一个小测试
第一组我们的测试样例中不包含aaa 同时不通过命令行输入测试样例
第二组我们的测试样例保持不变 通过命令行输入测试样例aaa
对比两组的测试结果 如果第二组的crash除了栈溢出之外 还变异出了aaa 而第一组没有 那么就可以证明测试样例成功输入进去
image.png
测试样例两个 内容分别如上图
第一组跑出的crash中不含有aaaa
第二组含有 那么可以证实测试样例确实是传入了
接下来研究一下从文件中写入的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
int main(){
int fd = open("flag",0);
char buf[0x20];
read(fd,buf,0x20);
char buf1[0x20] = "aaaa";
if(!strcmp(buf,buf1)){
puts("success!");
raise(SIGSEGV);
}else{
puts("error");
}
}

编译命令 这里考虑到了canary带来的crash太讨厌了 所以直接关掉了

1
afl-gcc -o test1 -no-pie -fno-stack-protector -g ./test1.c

本次提供给模糊器的测试样例就一个aaa
然后由于是从文件读取输入 并且你可以看到上面的代码 我们是指定了所需的文件名 同时目录应该是位于当前PUT下
所以我们除了使用@@标识当前使用文件输入外 还需要使用-f指定对应路径下的文件名
不过我试了下 把@@删了也是可以的 可能是-f就自动默认了?

1
afl-fuzz -i ./fuzz/in -o ./fuzz/out -f ./flag ./test1 @@

成功找到一个crash
image.png
查看了内容 没错是我们预设的aaaa

fuzz实际利用

接下来准备尝试 使用fuzz来对一些开源的知名项目进行测试 以此来熟悉fuzz实际利用的操作
这里使用upx upx是一款可执行文件压缩工具 https://github.com/upx/upx#
upx使用的压缩算法涉及到了ucl 所以还要先安装ucl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mkdir fuzz-upx
git clone https://github.com/upx/upx.git
wget http://www.oberhumer.com/opensource/ucl/download/ucl-1.03.tar.gz
tar zxvf ucl-1.03.tar.gz
cd ucl-1.03
./configure CPPFLAGS="$CPPFLAGS -std=c90 -fPIC"

cd upx
export CC=/usr/local/bin/afl-gcc
export CXX=/usr/local/bin/afl-g++
export UPX_UCCLDIR="/home/chen/fuzz-upx/ucl-1.03"
export UPX_LZMADIR="/home/chen/fuzz-upx/upx/vendor/lzma-sdk/"
上面这一步要注意一下 upx高版本和低版本的lzma-sdk存放位置不同 我的指令是高版本的
make all

在.bashrc文件中加上
export PATH=$PATH:/home/chen/fuzz-upx/upx/build/release
source ~/.bashrc

在开始对upx模糊测试之前 我们不能像之前一样随便给几个样本 样本收集对于fuzz来说至关重要

Prev
2023-11-20 10:59:54 # extra
Next