学习网站,,这次主要基于libexif,fizz共享库。
封校了,安心学习ing
本次尝试使用afl++对一个library进行fuzz。fuzz本身倒是不难,我觉得难的是配置这样那样的环境,找到合适的训练样本。
按照练习的说法,我们的workflow是
- Find an interface application that makes use of the libexif library
- Create a seed corpus of exif samples
- Compile libexif and the chosen application to be fuzzed using afl-clang-lto
- Fuzz libexif until you have a few unique crashes
- Triage the crashes to find a PoC for each vulnerability
- Fix the issues
环境
环境配置
找到libexif倒不是很困难,难的是编译的时候总是报错。直接在github上面搜索libexif
即可。链接。但是注意要下特定版本的(0.6.14)。这里遇到了第一个问题,怎么下载历史版本library。看了答案,完全不理解怎么找到的这个网站。甚至直接浏览器搜索https://github.com/libexif/libexif/archive/都是404,但是却可以下载。
1 | wget https://github.com/libexif/libexif/archive/refs/tags/libexif-0_6_14-release.tar.gz |
接下来解压
1 | tar -xzvf libexif-0_6_14-release.tar.gz |
接下来是编译。这里找到了一个很好的介绍编译选项的网站,然后发现其实在这里也有下载链接,不用去github那个诡异的链接里面下载了。
通过这个网站的学习,了解到configure的用法——生成makefile文件。具体的我不搬运了,讲的挺好的。重点写一下自己学到的。
一般git clone下载的文件,由于不知道要在那些系统上(windows,linux等),用什么编译器(gcc,clang)编译,因此需要生成一个符合系统配置的makefile文件。这时就需要用configure生成了。除此以外,手工编写makefile文件也是很大的挑战。具体来说,有以下几步
利用命令
1 autoreconf -fvi来生成一个全新的configure文件。参考
之后设置configure的参数。我们重点设置安装路径和使用的编译器种类。(编译器可能不设置默认为gcc?)这里是看了答案的。类似于上一次lab,指定安装路径为/install文件夹即可。
1 ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/"之后就可以make了。
1 make && sudo make install
期间遇到了一些问题,记录在下文了。
之后嘞,需要找一个使用libexif的文件,最好不是GUI的。我们在libexif的官网上其实能找到。
看到答案是下了exif,我这里也下这个吧。之后编译exif的过程和上面差不多,网上也能查到,看答案也没问题。需要注意的是这里的configure文件编写。
1 | ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/" PKG_CONFIG_PATH=$HOME/fuzzing_libexif/install/lib/pkgconfig |
这里的PKG_CONFIG_PATH
是什么意思呢?网上查
PKG_CONFIG_PATH
是一个环境变量,它指定pkg-config
将在其中搜索其.pc文件的其他路径。此变量用于增强pkg-config的默认搜索路径。在典型的Unix系统上,它将搜索目录
/usr/lib/pkgconfig
和/usr/share/pkgconfig
。这通常包括系统安装的模块。但是,某些本地模块可能安装在不同的前缀中,例如/usr/local
。在这种情况下,必须预先设置搜索路径,以便pkg-config可以找到.pc文件。
我的理解是,既然我们把libexif它安装在了我们指定路径下(编译libexif时的configure),这里就需要指定pkgconfig,因为它并不是安装在上面写的默认搜索路径中。在这两步之后,就能够正常运行libexif了。
环境配置遇到的问题
尝试./configure
时:
1 | * Fatal: libexif command line interface requires libexif to build. |
解决:这个是exif而不是libexif。需要先安装libexif。
尝试make install
时
1 | make[2]: *** No rule to make target 'install-apidocs', needed by 'install-data-local'. Stop. |
解决:这个似乎完全不需要管,程序照样能运行。(输入/home/fuzz/fuzzing_libexif/install/bin/exif)可以看到输出。
fuzz
在fuzz之前,按照练习的说法,需要用afl-clang-lto
重新编译上述两个文件。只需要指定CC与CXX两个环境变量即可。(这里为什么是llvm-config-11不太清楚,不过看说明这应该是一个专门写在config上面的配置文件)
以下是代码。
1 | rm -r $HOME/fuzzing_libexif/install |
之后可以用afl-clang-lto编译上述文件。查看文档,afl-lto实际上是首选项。这样会导致之前讲到的hashmap冲突发生的可能性更小。
1 | +--------------------------------+ |
使用以下语句完成编译
1 | afl-fuzz -i /home/fuzz/fuzzing_libexif/exif-samples-master/jpg -o /home/fuzz/fuzzing_libexif/out -s 123 -- $HOME/fuzzing_libexif/install/bin/exif @@ |
解释一下上面语句的含义。-i指定了输入文件,-o指定输出文件夹(这两个都是fuzzer所需要的),-s是seed,一个随机种子。后面的--
代表命令行的输入,也就是我们如何从命令行调用这个文件,@@表示afl将在这里替换我们的输入,也就是上面-i的内容。跑了一会儿之后得到下面的内容。
发现还是出现了蛮多错误的,都是sigmentation fault。
调试
之前是用gdb调试,这次练习教的是用eclipse。但是我不想用,linux中图形化支持有点慢。
1 | Program received signal SIGSEGV, Segmentation fault. |
还是用的gdb。可以看到停在了这里
这里$rcx并不是一个合理的值,可能是溢出之后的结果。就是这里导致了segmentation fault。
源码查看
使用以下命令递归查找文件
1 | find . -name "exif-utils.c" |
之后利用先前的gdb中backtrace(bt)查找调用栈。这里学到的一点是不能再fault之后再backtrace,好像和断在之前不一样。以下是断在segmentation fault之前的结果。
漏洞函数为
1 | ExifSShort |
由上面的分析,是传入的第一个参数有问题。
向上追溯,定位到以下关键函数
1 | ExifShort |
接下来向上寻找,到exif_data_load_data
第804行和819行
1 | /* Fixed value */ |
可以看到这里给d也就是buf加了一个8。可能就是这里的问题吗?(假设d为空?)断在这个函数之前调试一下。
看源码发现memcmp用的参数1和exif_get_short用的参数是一样的,查看了一下寄存器的值
memcpy结束后,将会判定两者相等,从而跳出循环到下面的漏洞函数中。注意观察这一段汇编代码,正是判断是否等于0x002a的一段。
这一段应该直接被优化成内联汇编展开了,而不是函数调用(卡了好久,,就说为什么没有断上去)但是这一段现在没有问题,直接经过了??
找这个问题还是挺麻烦的。但是我们知道了汇编代码的行数,尝试直接在汇编代码上面下断点?
终于找到了,原来有两个地方调用了exif_get_short
,在819行还有一个。这里看的就比较清楚了,是offset越界导致的错误。注意4294967295是0xFFFFFFFF!好的,找到了一个整数溢出的bug。
接下来就可以看源码找offset在哪里了。和offset相关的代码一定在这句话附近。我们逐个调试一下。分别在808,813行,最终定位到808行会出现问题。
1 | /* IFD 0 offset */ |
那搞了半天,应该是exif_get_long返回值的问题,如下
下面是汇编部分,只用了一句汇编指令就完成了(不愧是intel!)关键是这里
Description
Reverses the byte order of a 32-bit (destination) register: bits 0 through 7 are swapped with bits 24 through 31, and bits 8 through 15 are swapped with bits 16 through 23. This instruction is provided for converting little-endian values to big-endian format and vice versa.
确实一句话完成了这个工作。
1 | (gdb) si |
感觉已经接近真相了!看看exp是哪里来的,是0xa(%r15),应该就是这个函数的参数d+10!(exif-data.c第808行)
1 | offset = exif_get_long (d + 10, data->priv->order); |
这里应该就是一个越界读的操作了,通过以下命令可以看出。至此,完成了我们的分析。
问题解决
对上面offset返回值加上一个sanity-check,控制大小范围。
总结
这次是fuzz共享库,一方面要找共享库文件、使用共享库的文件、输入集合;另外一方面要学会怎么编译一个共享库,怎么使用afl-clang-lto编译。最重要的是,调试并复现漏洞(真的花了将近六个小时),学到了一些,还是很有收获的。
- 常用的下载网站
http://blog.fpliu.com/it/software/development/language/c/library/libexif/build-for-current-host