Exercise 1 - Xpdf
一、实验背景
CVE-2019-13288是一个可能通过精心制作的文件导致无线递归的漏洞
程序中每个被调用的函数都在堆栈上分配一个堆栈帧,如果一个函数被递归调用多次,可能会导致堆栈内存耗尽和程序崩溃,因此,远程攻击者可以利用该漏洞进行Dos攻击
非受控制递归漏洞(https://cwe.mitre.org/data/definitions/674.html)
二、实验目的
模糊Xpfd(PDF查看器),目标是在XPDF 3.02中找到CVE-2019-13288漏洞
三、实验环境:
win10,VMware,Ubuntu 20.04.2 LTS(虚拟机用户名密码均为fuzz)
四、实验步骤
1、下载并构建目标
得到模糊目标,为要模糊化的项目创建目录
1 | cd $HOME |
安装make和gcc,准备好环境,注意要先更新apt-get,否则会出现错误Unable to locate package build-essential
1 | sudo apt-get update |
下载Xpdf 3.02压缩包并解压
1 | wget https://dl.xpdfreader.com/old/xpdf-3.02.tar.gz |
构建Xpdf
1 | cd xpdf-3.02 |
下载一些PDF示例用于后续验证
下载pdf时注意在后面加参数–no-check-certificate跳过验证证书,否则会报错unable to establish ssl connection.
1 | cd $HOME/fuzzing_xpdf |
测试pdfinfo二进制文件
1 | $HOME/fuzzing_xpdf/install/bin/pdfinfo -box -meta $HOME/fuzzing_xpdf/pdf_examples/helloworld.pdf |
看到如下页面说明验证成功
2、安装AFL++
采用本地安装的方式,首先安装依赖项
1 | sudo apt-get update |
构造AFL++
1 | cd $HOME |
Docker image
安装docker,第一次安装后无法使用docker,使用snap重新下载后可以使用
1 | sudo apt install docker |
Pull图像,要加上sudo以管理员身份运行,否则会报错connect: permission denied
1 | docker pull aflplusplus/aflplusplus |
启动AFLPlusPlus docker容器
1 | docker run -ti -v $HOME:/home aflplusplus/aflplusplus |
再运行
1 | export $HOME="/home" |
输入afl-fuzz运行afl-fuzz,出现下图说明运行成功
3、开始实验
为了使我们的目标应用程序启用插装,我们需要使用AFL的编译器编译代码,首先,我们要清理所有之前编译过的目标文件和可执行文件
1 | rm -r $HOME/fuzzing_xpdf/install |
使用afl-clang-fast编译器构建xpdf
1 | export LLVM_CONFIG="llvm-config-11" |
每个选项的简要说明:
- -i 表示我们必须放置输入大小写的目录(即文件示例)
- -o 表示afl++存储修改后文件的目录
- -s 表示使用静态随机种子
- @@ 是AFL将用每个输入文件名替换的占位符目标的命令行
fuzzer基本上会运行下面这个命令,用于每个不同的输入文件
1 | $HOME/fuzzing_xpdf/install/bin/pdftotext <input-file-name> |
使用以下命令运行fuzzer,出现错误如下图
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 |
如果收到消息“Hmm, your system is configured to send core dump notifications to an external utility…”,只要这样做
1 | sudo su |
再运行fuzzer,几分钟后,可以看到如下页面
看到saved crashes的值,是崩溃的数量
可以在“$HOME/fuzzing_xpdf/out/”目录中找到这些崩溃文件
4、调试和分类
通过调试和分类,来找到关于这个bug的信息
1)用指定的文件重现崩溃
2)调试崩溃找出问题所在
3)修复问题
在’ $HOME/fuzzing_xpdf/out/ ‘目录中找到与崩溃对应的文件。文件名类于“id:000000,sig:11,src:000526,time:534057,execs:257582,op:havoc,rep:8”
将此文件作为输入传递给pdftotext二进制文件
1 | $HOME/fuzzing_xpdf/install/bin/pdftotext '$HOME/fuzzing_xpdf/out/default/crashes/<your_filename>' $HOME/fuzzing_xpdf/output |
它将导致分割错误,并导致程序崩溃
使用gdb找出使用此输入导致程序崩溃的原因
GDB的简单入门(http://people.cs.pitt.edu/~mosse/gdb-note.html)
首先,使用调试信息重新构建Xpdf,以获得符号堆栈跟踪
1 | rm -r $HOME/fuzzing_xpdf/install |
运行GDB
1 | gdb --args $HOME/fuzzing_xpdf/install/bin/pdftotext $HOME/fuzzing_xpdf/out/default/crashes/id:000000,sig:11,src:000526,time:534057,execs:257582,op:havoc,rep:8 $HOME/fuzzing_xpdf/output |
正常情况下可看到如下输出
看到Program received signal SIGSEGV, Segmentation fault,说明存在内存泄漏
报错位置是in _int_malloc (av=av@entry=0x7ffff7c63b80
再输入bt获取回溯
滚动调用堆栈,可以看到许多对“Parser::getObj”方法的调用,这些调用似乎表示无限递归。访问https://www.cvedetails.com/cve/CVE-2019-13288/可以看到描述从GDB获得的回溯匹配。
执行流信息,分析一下可以看出调用过程是循环的,判断为无限循环漏洞
根据函数调用找到漏洞位置
从fuzzing_xpdf/xpdf-3.02/xpdf/Parse.cc 94行的makeStream调用,一路跟着报错往下翻就会找到这个套娃
5、修复问题
方法
- 修复后重建目标,并检查用例是否不再导致分段错误
- 安装错误已经修复的Xpdf 4.02,并检查分段错误是否消失
我选择的是安装已修复的Xpdf 4.02,对比源代码,发现再Xpdf 4.02的文件Parse.cc中,加了个变量,记录循环次数,超过一定次数就结束进程
1 | wget https://dl.xpdfreader.com/old/xpdf-4.02.tar.gz |
五、总结
通过这个实验,我了解了CVE-2019-13288漏洞,学习了**AFL++**,如何使用带有插装的afl编译器编译目标,如何启动afl++,如何检测目标崩溃。
界面信息介绍
process timing:执行时间信息
run time:运行总时间
last new find:距离最近一次发现新路径的时间
last saved crash:距离最近一次保存程序崩溃的时间
last saved hang:距离最近一次保存挂起的时间
overall results:
cycles done:运行的总周期数
corpus count:语料库计数
saved crashes:保存的程序崩溃个数
saved hang:保存的挂起个数
cycle progress:
now processing:当前的测试用例ID(所在输入队列的位置)
runs timed out:超时数量
map coverage:覆盖率
map density:目前已经命中多少分支元组,与位图可以容纳多少的比例
count coverage:位图中每个被命中的字节平均改变的位数
stage progress:
now trying: 指明当前所用的变异输入的方法
stage execs: 当前阶段的进度指示
total execs: 全局的进度指示
exec speed: 执行速度
findings in depth:种子变异产生的信息
favored items: 基于最小化算法产生新的更好的路径
new edges on: 基于更好路径产生的新边
total crashes: 基于更好路径产生的崩溃
total tmouts: 基于更好路径产生的超时 包括所有超时的超时
fuzzing strategy yields: 进一步展示了AFL所做的工作,在更有效路径上得到的结果比例,对应上面的now trying
bit flips: 比特位翻转,例如:
bitflip 1/1,每次翻转1个bit,按照每1个bit的步长从头开始
bitflip 2/1,每次翻转相邻的2个bit,按照每1个bit的步长从头开始
byte flips: 字节翻转
arithmetics: 算术运算,例如:
arith 16/8,每次对16个bit进行加减运算,按照每8个bit的步长从头开始,即对文件的每个word进行整数加减变异
know ints: 用于替换的基本都是可能会造成溢出的数,例:
interest 16/8,每次对16个bit进替换,按照每8个bit的步长从头开始,即对文件的每个word进行替换
dictionary: 有以下子阶段:
user extras (over),从头开始,将用户提供的tokens依次替换到原文件中
user extras (insert),从头开始,将用户提供的tokens依次插入到原文件中
auto extras (over),从头开始,将自动检测的tokens依次替换到原文件中
其中,用户提供的tokens,是在词典文件中设置并通过-x选项指定的,如果没有则跳过相应的子阶段。
havoc:顾名思义,是充满了各种随机生成的变异,是对原文件的“大破坏”。具体来说,havoc包含了对原文件的多轮变异,每一轮都是将多种方式组合(stacked)而成
splice:在任意选择的中点将队列中的两个随机输入拼接在一起.
py/custom/req:
trim:修建测试用例使其更短,但保证裁剪后仍能达到相同的执行路径
eff
item geometry:
levels: 表示测试等级
pending: 表示还没有经过fuzzing的输入数量
pend fav: 表明fuzzer感兴趣的输入数量
own finds: 表示在fuzzing过程中新找到的,或者是并行测试从另一个实例导入的数量
imported: n/a表明不可用,即没有导入
stability: 表明相同输入是否产生了相同的行为,一般结果都是100%
参考博客:https://blog.csdn.net/qq_40025866/article/details/127823491