Driller源码分析01

Driller 项目搭建及测试

由于年代久远,参考了好多搭建的博客都失败了,各种各样的问题,这里贴两个时间比较靠后的博客:

https://blog.grimm-co.com/2020/05/guided-fuzzing-with-driller.html

https://n132.github.io/2020/03/04/Driller-Installation.html

换了各种 Ubuntu 版本以及 python 的版本,忙活了一天最后也没搭成功。好在,无意间在 docker 仓库里发现了ZJUchenyuan大佬上传的 driller 的镜像,直接 pull 下来用了,测试能跑通!!!

感谢 Docker,感谢 ZJU 的chenyuan大佬救我狗命Orz!!!

安装好 docker 后直接运行下面的命令,拉取镜像就好了。

1
docker pull zjuchenyuan/driller

碎碎念:这不比自己装一堆东西方便多了,第一次深切的体会到 docker 的好用,再次感谢 docker!貌似以前对于docker跟虚拟机一直搞混,这次好像对于 docker 的理解更深一步了,算了理解了一点 docker 的运行方式,有时间再开文详写,挖坑ing!

Driller 测试

镜像文件中是有完整的项目,但是由于对镜像还不是很熟悉,并且镜像里也没有把 afl-fuzz 加入到环境变量,这里就没有选择镜像里的 afl-fuzz,采用本地搭建的 afl,由于需要 Qemu 模式运行 AFL,搭建过程参考之前的博客《AFL Qemu模式》

测试文件 buggy.c 如下:(取自于参考博客2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
char buffer[6] = {0};
int i;
int *null = 0;

read(0, buffer, 6);
if (buffer[0] == '7' && buffer[1] == '/' && buffer[2] == '4'
&& buffer[3] == '2' && buffer[4] == 'a' && buffer[5] == '8') {
i = *null;
}

puts("No problem");
}

程序的漏洞是输入一个特定值会crash。正常编译即可

1
2
cd ~/Desktop/driller
gcc -o buggy ./buggy.c

fuzz with afl

首先,调用 AFL 对其 Fuzz,命令如下:

1
2
3
4
5
cd ~/Desktop/driller
mkdir -p workdir/input
echo 'sth' > workdir/input/seed1
echo core | sudo tee /proc/sys/kernel/core_pattern
/path/to/afl-fuzz -M fuzzer-master -i workdir/input -o workdir/output -Q ./buggy

会出现 AFL 的工作界面,如下:

image-20230911212621303

fuzz with dirller

通过种子进行变异到指定的字符串机率特别低,这里可视为 “卡住” ,此时调用 run_driller.py 脚本运行 driller,脚本如下:

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
65
66
67
68
69
70
#!/usr/bin/env python

import errno
import os
import os.path
import sys
import time

from driller import Driller


def save_input(content, dest_dir, count):
"""Saves a new input to a file where AFL can find it.

File will be named id:XXXXXX,driller (where XXXXXX is the current value of
count) and placed in dest_dir.
"""
name = 'id:%06d,driller' % count
with open(os.path.join(dest_dir, name), 'wb') as destfile:
destfile.write(content)


def main():
if len(sys.argv) != 3:
print('Usage: %s <binary> <fuzzer_output_dir>' % sys.argv[0])
sys.exit(1)

_, binary, fuzzer_dir = sys.argv

# Figure out directories and inputs
with open(os.path.join(fuzzer_dir, 'fuzz_bitmap'), 'rb') as bitmap_file:
fuzzer_bitmap = bitmap_file.read()
source_dir = os.path.join(fuzzer_dir, 'queue')
dest_dir = os.path.join(fuzzer_dir, '..', 'driller', 'queue')

# Make sure destination exists
try:
os.makedirs(dest_dir)
except os.error as e:
if e.errno != errno.EEXIST:
raise

seen = set() # Keeps track of source files already drilled
count = len(os.listdir(dest_dir)) # Helps us name outputs correctly

# Repeat forever in case AFL finds something new
while True:
# Go through all of the files AFL has generated, but only once each
for source_name in os.listdir(source_dir):
if source_name in seen or not source_name.startswith('id:'):
continue
seen.add(source_name)
with open(os.path.join(source_dir, source_name), 'rb') as seedfile:
seed = seedfile.read()

print('Drilling input: %s' % seed)
for _, new_input in Driller(binary, seed, fuzzer_bitmap).drill_generator():
save_input(new_input, dest_dir, count)
count += 1

# Try a larger input too because Driller won't do it for you
seed = seed + b'0000'
print('Drilling input: %s' % seed)
for _, new_input in Driller(binary, seed, fuzzer_bitmap).drill_generator():
save_input(new_input, dest_dir, count)
count += 1
time.sleep(10)

if __name__ == '__main__':
main()

由于 driller 的环境在 docker 里,所以这里的运行脚本命令如下:

1
$ docker run --rm -w /work -it -v `pwd`:/work zjuchenyuan/driller python ./run_driller.py ./buggy workdir/output/fuzzer-master

这里简单介绍一下参数的作用:

--rm: 告诉 Docker 在容器停止后删除容器。

-w /work:设置容器的当前工作目录为 /work,在容器内部执行的命令将在这个目录下执行。

-it:组合了两个选项,-i 表示要保持与容器的标准输入连接,-t 表示要为容器分配一个伪终端,以便与容器进行交互。

-v $(pwd):/work:这个标志用于将本地文件系统的当前工作目录 (pwd) 挂载到容器内部的 /work 目录。这意味着容器可以访问主机上的文件,并且容器内对 /work 目录的任何更改都将反映到主机上。

zjuchenyuan/driller:要运行的 Docker 镜像的名称。

执行结果如下:

image-20230911213244364

可以看到 Driller 执行符号执行,输出求解得到的结果。貌似找到 crash 后脚本就不再执行,卡住不动了。

我们可以从 workdir/output/fuzzer-master/crashes 文件夹中找到造成 crash 的输入

1
2
3
4
5
$ cd workdir/output/fuzzer-master/crashes
$ ls
id:000000,sig:11,sync:driller,src:000016 README.txt
$ cat id\:000000\,sig\:11\,sync\:driller\,src\:000016
7/42a8

可以看到 Driller 通过符号执行求解出了造成 crash 的字符串值与程序中设定的一样,至此,Driller 算是成功跑通了,接下来就是分析 Driller 的源码,重点学习其 angr 的使用。

fuzz with shellphuzz

这种方法是通过 driller 作者提供的一键式脚本来跑,使用起来更加方便快捷,但就是封装了很多层而已。

shellphuzz脚本命令如下:

1
docker run --rm -w /work -it -v `pwd`:/work zjuchenyuan/driller shellphuzz -d 1 -c 1 -w workdir/shellphuzz/ -C --length-extension 4 ./buggy

这里我一直卡在 [*] Creating fuzzer... 这里,但是我运行 dockerhub 上面镜像给的 demo 是可以用 shellphuzz 脚本跑起来的,好奇怪,先不管了,感觉不是很重要。