学长搞Fuzz毕设,顺道跟着学了点
寒假时,主要跟着Github上的Fuzzing-101的项目学习复现
Exercise1 - Xpdf
目标:fuzz Xpdf PDF查看器,在Xpdf 3.02中查找 CVE-2019-13208 崩溃
使用afl-clang-fast编译器构建Xpdf(插桩)
导入llvm环境变量,指明llvm的版本
1 | export LLVM_CONFIG="llvm-config-11" |
指定编译器
1 | CC=$HOME/AFLplusplus/afl-clang-fast CXX=$HOME/AFLplusplus/afl-clang-fast++ ./configure --prefix="$HOME/fuzzing_xpdf/install/" |
编译
1 | make |
运行afl-fuzz
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:指定静态伪随机数种子
--:指向afl-fuzz的输入案例的具体样本
@@:文件占位符,为-i目录中具体文件
处理报错

export环境变量AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1
然后开始运行fuzz

此处我们主要看overall results中的crashes提示
复现crash
重新编译Xpdf,以包含调试信息
1 | rm -r $HOME/fuzzing_xpdf/install |
然后使用gdb调试
1 | gdb --args $HOME/fuzzing_xpdf/install/bin/pdftotext $HOME/fuzzing_xpdf/out/default/crashes/<your_filename> $HOME/fuzzing_xpdf/output |
运行后查看栈帧,发现了循环调用

调试分析
不断跟进直至最后的循环,可以发现最后是在一个在getObj函数中的makeStream处产生循环

单步进入

makeStream中调用了dictLookup函数,dictLookup函数封装了lookup函数

然后lookup函数中又封装了find函数赖来寻找Key=Length

取如下地址返回到lookup函数

e->val.fetch(xref, obj)为Object.fecth调用,但本质上是封装。调试发现find(key)不为空,然后再次调用xref.fetch,打印可以发现e中的val为XRef对象

跟进验证上述说法,当type == objRef && xref不为空时,调用xref->fetch函数

跟进fetch,调用了getObj,完成循环调用

分析原因
产生这种洞的原因是通过fuzz产生的二进制数据满足了某条死循环的执行路径,从而产生拒绝服务
Exercise 2 - libexif 0.16.14 Fuzz
libexif和exif是什么?
libexif是一个可移植C语言编写的库,作用是从图像文件读取和写入EXIF元信息
exif是一种图片格式
我们需要使用libexif解析exif
构建libexif
从GitHub上wget对应的demo版本后
安装工具链
1 | sudo apt-get install autopoint libtool gettext libpopt-dev |
然后使用autoreconf生成Makefile,
1 | autoreconf -fvi |
静态编译,并指定生成路径
1 | ./configure --enable-shared=no --prefix="/home/fuzz/fuzzing_libexif/install" |
然后编译
1 | make |
构建exif并调用libexif解析
1 | autoreconf -fvi |
然后静态编译,并指明pc文件路径
1 | ./configure --enable-shared=no --prefix="/home/fuzz/fuzzing_libexif/install" PGP_CONFIG_PATH=/home/fuzz/fuzzing_libexif/install/lib/pkgconfig |
然后编译
1 | make |
测试
去网上找了个exif图片格式的图片仓库:https://github.com/ianare/exif-samples

使用 QEMU 模式执行模糊测试
添加软件源
1 | sudo vi /etc/apt/sources.list |
然后更新,安装最新版本的libc
1 | sudo apt update |
进行模糊测试

分析 crash
分析堆溢出
分析其中一个crash
直接运行后查看栈帧,发现在malloc_printer中报了realloc(): invalid next size的崩溃,然后在exif_entry_fix处下断点

发现continue12次发生崩溃,通过IDA反汇编查看函数exif_entry_fix处的函数情况

此处调试循环,发现崩溃样例中的循环次数为0x66次

同时由于exif_set_short函数中,$v_{13}$为2字节,所以$&v_{12}$数组每次都以2字节偏移,而$v_9$只有1字节数据
在经过e->components次循环后(调试中为102次),写入2*102了字节
现在查看分配的堆块大小,堆块大小在分配的堆内存前0x10字节,大于分配的堆块大小0x70个字节(0x71中的0x1为标志位,置为1表示前一个堆块正在使用),在realloc中的内存写中会存在溢出,造成覆写后续堆块的chunk_size 导致realloc抛出异常。


那么在循环结束时,调用这个函数,这个函数封装了realloc函数
1 | e->data = (unsigned __int8 *)exif_entry_realloc(e, e->data, e->size); |
崩溃原因
查看glibc中realloc函数源码,可以发现由于堆溢出覆盖了堆上的数据(覆盖后几乎都是0,比较下列两张图),chunksize_nomask (next) <= CHUNK_HDR_SZ满足条件,从而抛出异常
1 | next = chunk_at_offset (oldp, oldsize); |
(循环之前)

(经过循环之后)
