Fuzzing101-exe1

介绍

本文是 Fuzzing101 训练的第一篇,目标是找到 Xpdf 3.02 版本不受控制的递归函数漏洞,而每次调用函数都会在栈上开辟新的栈帧,如果一个函数反复调用很多次,会导致栈内存耗尽从而程序崩溃CVE-2019-13288

项目网址: https://github.com/antonio-morales/Fuzzing101

将会学到的知识

  • 对程序进行插桩
  • AFL-FUZZ 的使用
  • GDB 调试程序

环境与搭建

Ubuntu 20(推荐使用)

安装 Xpdf

首先需要下载一些额外工具(即 make 和 gcc)

1
$ sudo apt install build-essential

为此项目创建一个文件

1
2
$ cd $HOME
$ mkdir fuzzing_xpdf && cd fuzzing_xpdf/

下载 Xpdf 3.02:

1
2
$ wget https://dl.xpdfreader.com/old/xpdf-3.02.tar.gz
$ tar -xvzf xpdf-3.02.tar.gz

构建 Xpdf:

1
2
3
4
5
$ cd xpdf-3.02
$ sudo apt update && sudo apt install -y build-essential gcc
$ ./configure --prefix="$HOME/fuzzing_xpdf/install/"
$ make
$ make install

下载一些 PDF 示例:

1
2
3
4
5
$ cd $HOME/fuzzing_xpdf
$ mkdir pdf_examples && cd pdf_examples
$ wget https://github.com/mozilla/pdf.js-sample-files/raw/master/helloworld.pdf #注意
$ wget http://www.africau.edu/images/default/sample.pdf
$ wget https://www.melbpc.org.au/wp-content/uploads/2017/10/small-example-pdf-file.pdf

要注意的是,其中的 helloworld.pdf链接对于 wget 已经失效,但是直接在浏览器上下载还是可以的,所以可以在本机下好后再拖入虚拟机。

现在,可以使用以下命令测试 pdfinfo 二进制文件:

1
$ $HOME/fuzzing_xpdf/install/bin/pdfinfo -box -meta $HOME/fuzzing_xpdf/pdf_examples/helloworld.pdf

将会看到如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
gyx@ubuntu:~/fuzzing_xpdf$ $HOME/fuzzing_xpdf/install/bin/pdfinfo -box -meta $HOME/fuzzing_xpdf/pdf_examples/helloworld.pdf
Tagged: no
Pages: 1
Encrypted: no
Page size: 200 x 200 pts
MediaBox: 0.00 0.00 200.00 200.00
CropBox: 0.00 0.00 200.00 200.00
BleedBox: 0.00 0.00 200.00 200.00
TrimBox: 0.00 0.00 200.00 200.00
ArtBox: 0.00 0.00 200.00 200.00
File size: 678 bytes
Optimized: no
PDF version: 1.7

安装 AFL++

将会用到 AFL++ 的最新版

安装依赖项

1
2
3
4
$ sudo apt-get update
$ sudo apt-get install -y build-essential python3-dev automake git flex bison libglib2.0-dev libpixman-1-dev python3-setuptools
$ sudo apt-get install -y lld-11 llvm-11 llvm-11-dev clang-11 || sudo apt-get install -y lld llvm llvm-dev clang
$ sudo apt-get install -y gcc-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-plugin-dev libstdc++-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-dev

构建 AFL++

1
2
3
4
5
$ cd $HOME
$ git clone https://github.com/AFLplusplus/AFLplusplus && cd AFLplusplus
$ export LLVM_CONFIG="llvm-config-11"
$ make distrib
$ sudo make install

需要注意的是,国内下载 github 相对较慢,所以可以通过git clone git://github.com/AFLplusplus/AFLplusplus && cd AFLplusplus更快速的下载。make distrib这一项,安装过程中可能会出现 qemu_mode、frida 和 unicorn 安装失败,或者 unicorn mode 未工作的情况。这里列举一下每一项的作用

1
2
3
4
5
6
7
8
9
all:只是主要的 AFL++ 二进制文件
binary-only:仅二进制模糊测试的所有内容:qemu_mode、unicorn_mode、libdislocator、libtokencap、radamsa
source-only:源代码模糊测试的所有内容:llvm_mode、libdislocator、libtokencap、radamsa
distrib:一切(仅用于二进制和源代码模糊测试)
install:安装您使用上面的构建选项编译的所有内容
clean:清洁一切。对于 qemu_mode 和 unicorn_mode 这意味着它也会删除所有下载
code-format:格式化代码,请在提交之前执行此操作并发送 PR!
tests:运行测试用例以确保所有功能仍然正常工作
help: 显示这些构建选项

llvm_mode 等功能现在暂时还用不上,所以也可以换成 make all指令

键入afl-fuzz即可查看是否安装成功

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
gyx@ubuntu:~/fuzzing_xpdf$ afl-fuzz
afl-fuzz++4.01a based on afl by Michal Zalewski and a large online community

afl-fuzz [ options ] -- /path/to/fuzzed_app [ ... ]

Required parameters:
-i dir - input directory with test cases
-o dir - output directory for fuzzer findings

Execution control settings:
-p schedule - power schedules compute a seed's performance score:
fast(default), explore, exploit, seek, rare, mmopt, coe, lin
quad -- see docs/FAQ.md for more information
-f file - location read by the fuzzed program (default: stdin or @@)
-t msec - timeout for each run (auto-scaled, default 1000 ms). Add a '+'
to auto-calculate the timeout, the value being the maximum.
-m megs - memory limit for child process (0 MB, 0 = no limit [default])
-O - use binary-only instrumentation (FRIDA mode)
-Q - use binary-only instrumentation (QEMU mode)
-U - use unicorn-based instrumentation (Unicorn mode)
-W - use qemu-based instrumentation with Wine (Wine mode)
-X - use VM fuzzing (NYX mode - standalone mode)
-Y - use VM fuzzing (NYX mode - multiple instances mode)

Mutator settings:
-g minlength - set min length of generated fuzz input (default: 1)
-G maxlength - set max length of generated fuzz input (default: 1048576)
-D - enable deterministic fuzzing (once per queue entry)
-L minutes - use MOpt(imize) mode and set the time limit for entering the
pacemaker mode (minutes of no new finds). 0 = immediately,
-1 = immediately and together with normal mutation.
See docs/README.MOpt.md
-c program - enable CmpLog by specifying a binary compiled for it.
if using QEMU, just use -c 0.
-l cmplog_opts - CmpLog configuration values (e.g. "2AT"):
1=small files, 2=larger files (default), 3=all files,
A=arithmetic solving, T=transformational solving.

Fuzzing behavior settings:
-Z - sequential queue selection instead of weighted random
-N - do not unlink the fuzzing input file (for devices etc.)
-n - fuzz without instrumentation (non-instrumented mode)
-x dict_file - fuzzer dictionary (see README.md, specify up to 4 times)

Test settings:
-s seed - use a fixed seed for the RNG
-V seconds - fuzz for a specified time then terminate
-E execs - fuzz for an approx. no. of total executions then terminate
Note: not precise and can have several more executions.

Other stuff:
-M/-S id - distributed mode (see docs/parallel_fuzzing.md)
-M auto-sets -D, -Z (use -d to disable -D) and no trimming
-F path - sync to a foreign fuzzer queue directory (requires -M, can
be specified up to 32 times)
-T text - text banner to show on the screen
-I command - execute this command/script when a new crash is found
-C - crash exploration mode (the peruvian rabbit thing)
-b cpu_id - bind the fuzzing process to the specified CPU core (0-...)
-e ext - file extension for the fuzz test input file (if needed)

To view also the supported environment variables of afl-fuzz please use "-hh".

Compiled with Python 3.8.10 module support, see docs/custom_mutator.md
Compiled without AFL_PERSISTENT_RECORD support.
Compiled with shmat support.
For additional help please consult /usr/local/share/doc/afl/README.md :)

插桩与运行

用 AFL 的编译器编译代码

首先要清理所有之前编译的目标文件和可执行文件

1
2
3
$ rm -r $HOME/fuzzing_xpdf/install
$ cd $HOME/fuzzing_xpdf/xpdf-3.02/
$ make clean

接着使用 afl-clang-fast 编译器构建 Xpdf

1
2
3
4
$ export LLVM_CONFIG="llvm-config-11"
$ CC=$HOME/AFLplusplus/afl-clang-fast CXX=$HOME/AFLplusplus/afl-clang-fast++ ./configure --prefix="$HOME/fuzzing_xpdf/install/"
$ make
$ make install

运行模糊器

1
$ afl-fuzz -i $HOME/fuzzing_xpdf/pdf_examples/ -o $HOME/fuzzing_xpdf/out/ -s 123 -- $HOME/fuzzing_xpdf/install/bin/pdftotext @@ $HOME/fuzzing_xpdf/output

每个选项简要说明:

  • -i 表示我们必须放置输入案例的目录(又名文件示例)
  • -o 表示 AFL++ 将存储变异文件的目录
  • -s 表示要使用的静态随机种子,这边建议设置一个固定种子(如 -s 123),这样模糊测试的结果会和显示结果相似,从而更轻松的练习
  • @@ 是占位符目标的命令行,AFL 将用每个输入文件名替换

如果在 fuzz 过程中遇到有关于 core_pattern 的报错,可以这样设置

1
2
3
$ sudo su
$ echo core >/proc/sys/kernel/core_pattern
$ exit

成功后就会出现 fuzz 界面,可以看到此时有一个 crash

image-20220222164326160

重现崩溃

在目录中找到崩溃对应的文件$HOME/fuzzing_xpdf/out/default/crashes,会发现一个文件名类似于id:000000,sig:11,src:000972,time:155131,execs:90167,op:havoc,rep:8的文件。

image-20220222164722446

将此文件作为输入传递给 pdftotext 二进制文件

1
$ $HOME/fuzzing_xpdf/install/bin/pdftotext '$HOME/fuzzing_xpdf/out/default/crashes/<your_filename>' $HOME/fuzzing_xpdf/output

要注意的是,如果显示打不开文件可以输入文件的绝对路径,例如我这就改为$HOME/fuzzing_xpdf/install/bin/pdftotext '/home/gyx/fuzzing_xpdf/out/default/crashes/id:000000,sig:11,src:000972,time:155131,execs:90167,op:havoc,rep:8' /home/gyx/fuzzing_xpdf/output,网上看到一个方法说可以把文件名改为 xxx.pdf,但是我没试成功。

成功后显示是段错误

image-20220222165218670

gdb 调试

用 gdb 找出程序因为这个输入而崩溃的原因

首先,使用调试信息重建 Xpdf 以获得符号堆栈跟踪

1
2
3
4
5
6
$ rm -r $HOME/fuzzing_xpdf/install
$ cd $HOME/fuzzing_xpdf/xpdf-3.02/
$ make clean
$ CFLAGS="-g -O0" CXXFLAGS="-g -O0" ./configure --prefix="$HOME/fuzzing_xpdf/install/"
$ make
$ make install

运行 GDB

1
$ gdb --args $HOME/fuzzing_xpdf/install/bin/pdftotext $HOME/fuzzing_xpdf/out/default/crashes/<your_filename> $HOME/fuzzing_xpdf/output

然后在 GDB 中输入

1
>> run

可以看到程序有很多报错

image-20220222170132713

然后键入bt以获取回溯

image-20220222171014762

可以发现程序调用了很多次Parser::getObj函数,再结合文档叙述发现确实是这样。

image-20220222171407364

CVE-2019-13288:https://www.cvedetails.com/cve/CVE-2019-13288/


参考链接

https://github.com/antonio-morales/Fuzzing101/tree/main/Exercise%201