前言
通过对Fuzzing101
项目的学习,逐步深入了解模糊测试,掌握工具的安装与使用,以及模糊测试结果的分析和调试。本文是在作者学习fuzz
源码之后所写,根据项目中每一篇挑战的Readme.md
进行翻译和学习,并参考了网上师傅们的文章。在这里,特别感谢hollk
师傅的文章和指导,希望详细学习模糊测试的读者请查看hollk
师傅的博客:hollk的博客_CSDN博客-pwn,模糊测试,堆溢出领域博主
项目搭建:
git clone https://github.com/antonio-morales/Fuzzing101
Exercise 1:Xpdf
CVE-2019-13288:Parser.cc
源码中的Parser::getObj()
函数可能会产生无限递归导致程序崩溃。
Fuzz准备
搭建Xpdf
环境:
cd $HOME
mkdir fuzzing_xpdf && cd fuzzing_xpdf/
# 相关依赖
sudo apt install build-essential
sudo apt update && sudo apt install -y build-essential gcc
# Download Xpdf 3.02
wget https://dl.xpdfreader.com/old/xpdf-3.02.tar.gz
tar -xvzf xpdf-3.02.tar.gz
# Download PDF examples to test Xpdf
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
# Build Xpdf
cd xpdf-3.02
make
测试Xpdf
:
$HOME/fuzzing_xpdf/xpdf-3.02/xpdf/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
搭建Fuzz
环境:
# 相关依赖
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
# 搭建 Fuzz
cd $HOME
git clone https://github.com/AFLplusplus/AFLplusplus && cd AFLplusplus
export LLVM_CONFIG="llvm-config-11"
make distrib
sudo make install
使用afl-clang-fast
对Xpdf
进行插桩:
# 清空已经编译
cd $HOME/fuzzing_xpdf/xpdf-3.02/
make clean
# 使用 afl-clang-fast 编译
export LLVM_CONFIG="llvm-config-11"
export CC=$HOME/AFLplusplus/afl-clang-fast
export CXX=$HOME/AFLplusplus/afl-clang-fast++
make
开始模糊测试,主要参数设置如下:
-i
:设置输入实例的文件夹-o
:设置用于存放模糊测试结果的文件夹-s
:设置一个静态随机数作为种子--
:设置测试目标@@
:占位符,指代每一个输入的文件
# 关闭核心转储
sudo su
echo core >/proc/sys/kernel/core_pattern
exit
# 开始 Fuzz
afl-fuzz -i $HOME/fuzzing_xpdf/pdf_examples/ -o $HOME/fuzzing_xpdf/out/ -s 123 -- $HOME/fuzzing_xpdf/xpdf-3.02/xpdf/pdftotext @@ $HOME/fuzzing_xpdf/output
--------------------------------------------------------------------------------------------
# 该结果是作者在程序崩溃了7次后记录
american fuzzy lop ++4.05a {default} (...df/xpdf-3.02/xpdf/pdftotext) [fast]
┌─ process timing ────────────────────────────────────┬─ overall results ────┐
│ run time : 0 days, 0 hrs, 21 min, 8 sec │ cycles done : 0 │
│ last new find : 0 days, 0 hrs, 0 min, 19 sec │ corpus count : 977 │
│last saved crash : 0 days, 0 hrs, 0 min, 59 sec │saved crashes : 7 │
│ last saved hang : 0 days, 0 hrs, 21 min, 1 sec │ saved hangs : 1 │
├─ cycle progress ─────────────────────┬─ map coverage┴──────────────────────┤
│ now processing : 557.4 (57.0%) │ map density : 3.53% / 4.86% │
│ runs timed out : 0 (0.00%) │ count coverage : 3.98 bits/tuple │
├─ stage progress ─────────────────────┼─ findings in depth ─────────────────┤
│ now trying : splice 2 │ favored items : 79 (8.09%) │
│ stage execs : 174/294 (59.18%) │ new edges on : 138 (14.12%) │
│ total execs : 1.13M │ total crashes : 7 (7 saved) │
│ exec speed : 931.5/sec │ total tmouts : 138 (0 saved) │
├─ fuzzing strategy yields ────────────┴─────────────┬─ item geometry ───────┤
│ bit flips : disabled (default, enable with -D) │ levels : 10 │
│ byte flips : disabled (default, enable with -D) │ pending : 825 │
│ arithmetics : disabled (default, enable with -D) │ pend fav : 0 │
│ known ints : disabled (default, enable with -D) │ own finds : 974 │
│ dictionary : n/a │ imported : 0 │
│havoc/splice : 712/549k, 269/356k │ stability : 100.00% │
│py/custom/rq : unused, unused, unused, unused ├───────────────────────┘
│ trim/eff : 2.41%/214k, disabled │ [cpu000:350%]
└────────────────────────────────────────────────────┘
结果分析
在fuzzing_xpdf/out/default/crashes
文件夹中可以看到导致程序崩溃的输入,作者这里是7个:
pursue@pursue-virtual-machine:~/桌面/Fuzzing101-main/Exercise 1/fuzzing_xpdf/out/default/crashes$ ls
id:000000,sig:11,src:000532,time:98000,execs:105391,op:havoc,rep:8
id:000001,sig:11,src:000532,time:100895,execs:107844,op:havoc,rep:16
id:000002,sig:11,src:000846,time:592160,execs:473175,op:havoc,rep:8
id:000003,sig:11,src:000729,time:666679,execs:535614,op:havoc,rep:8
id:000004,sig:11,src:000557,time:670840,execs:539289,op:havoc,rep:8
id:000005,sig:11,src:000593,time:944864,execs:788622,op:havoc,rep:16
id:000006,sig:11,src:000607+000580,time:1209629,execs:1067185,op:splice,rep:4
README.txt
接下来,我们就需要将这些文件放入gdb中进行调试,看看到底是哪里出错了?
首先重新编译带调试的程序:
cd $HOME/fuzzing_xpdf/xpdf-3.02/
make clean
CFLAGS="-g -O0" CXXFLAGS="-g -O0"
make
开启gdb调试:
gdb --args $HOME/fuzzing_xpdf/xpdf-3.02/xpdf/pdftotext $HOME/fuzzing_xpdf/out/default/crashes/id:000000,sig:11,src:000532,time:98000,execs:105391,op:havoc,rep:8 $HOME/fuzzing_xpdf/output
-----------------------------------------------------------------------------
# gdb
pwndbg> run
# 回溯使用过的函数
pwndbg> bt
#3977 0x000000000049f0c9 in Parser::getObj (this=<optimized out>, obj=0x7fffff85c3c0, fileKey=0x0, encAlgorithm=cryptRC4, keyLength=0, objNum=7, objGen=0) at Parser.cc:94
#3978 0x00000000004d1641 in XRef::fetch (this=0x7965b0, num=7, gen=0, obj=0x7fffff85c3c0) at XRef.cc:823
#3979 0x000000000049f5e5 in Object::dictLookup (this=0x7fffff85c540, key=0x529 <error: Cannot access memory at address 0x529>, obj=0x7fffff85c3c0) at ./Object.h:253
#3980 Parser::makeStream (this=this@entry=0x2081320, dict=dict@entry=0x7fffff85c540, fileKey=fileKey@entry=0x0, encAlgorithm=encAlgorithm@entry=cryptRC4, keyLength=keyLength@entry=0, objNum=objNum@entry=7, objGen=0) at Parser.cc:156
#3981 0x000000000049f0c9 in Parser::getObj (this=<optimized out>, obj=0x7fffff85c540, fileKey=0x0, encAlgorithm=cryptRC4, keyLength=0, objNum=7, objGen=0) at Parser.cc:94
#3982 0x00000000004d1641 in XRef::fetch (this=0x7965b0, num=7, gen=0, obj=0x7fffff85c540) at XRef.cc:823
#3983 0x000000000049f5e5 in Object::dictLookup (this=0x7fffff85c6c0, key=0x529 <error: Cannot access memory at address 0x529>, obj=0x7fffff85c540) at ./Object.h:253
#3984 Parser::makeStream (this=this@entry=0x2080e40, dict=dict@entry=0x7fffff85c6c0, fileKey=fileKey@entry=0x0, encAlgorithm=encAlgorithm@entry=cryptRC4, keyLength=keyLength@entry=0, objNum=objNum@entry=7, objGen=0) at Parser.cc:156
#3985 0x000000000049f0c9 in Parser::getObj (this=<optimized out>, obj=0x7fffff85c6c0, fileKey=0x0, encAlgorithm=cryptRC4, keyLength=0, objNum=7, objGen=0) at Parser.cc:94
#3986 0x00000000004d1641 in XRef::fetch (this=0x7965b0, num=7, gen=0, obj=0x7fffff85c6c0) at XRef.cc:823
#3987 0x000000000049f5e5 in Object::dictLookup (this=0x7fffff85c840, key=0x529 <error: Cannot access memory at address 0x529>, obj=0x7fffff85c6c0) at ./Object.h:253
#3988 Parser::makeStream (this=this@entry=0x2080960, dict=dict@entry=0x7fffff85c840, fileKey=fileKey@entry=0x0, encAlgorithm=encAlgorithm@entry=cryptRC4, keyLength=keyLength@entry=0, objNum=objNum@entry=7, objGen=0) at Parser.cc:156
# 发现一直调用 Parser::getObj 函数
查看源码Parser.cc:94
:
// stream objects are not allowed inside content streams or
// object streams
if (allowStreams && buf2.isCmd("stream")) {
if ((str = makeStream(obj, fileKey, encAlgorithm, keyLength,
objNum, objGen))) {
...
发现问题所在,解决方法:只需在分支中加入递归次数的检测,使得超过一定次数程序退出,由于本人的代码能力不是很强,所以没能自己成功修复bug,学习了Xpdf-4.02
中的Parser.cc
源码:
// 增加了递归的上限
// Max number of nested objects. This is used to catch infinite loops
// in the object structure.
#define recursionLimit 500
// 增加了递归次数的检查
// array
if (!simpleOnly && recursion < recursionLimit && buf1.isCmd("[")) {
...
// dictionary or stream
} else if (!simpleOnly && recursion < recursionLimit && buf1.isCmd("<<")) {
...
// 对比之前版本,发现多了 recursion 这个参数
// stream objects are not allowed inside content streams or
// object streams
if (allowStreams && buf2.isCmd("stream")) {
if ((str = makeStream(obj, fileKey, encAlgorithm, keyLength,
objNum, objGen, recursion + 1))) {
...
Exercise 2:Libexif
CVE-2009-3895:基于堆缓冲区溢出漏洞,可以劫持程序流。
CVE-2012-2836:一个越界读取的漏洞,可以从内存中读取潜在的敏感信息。
Fuzz准备
首先编译libexif
链接库和用于运行这个链接库的程序exif
:
cd $HOME
mkdir fuzzing_libexif && cd fuzzing_libexif/
# 编译和安装 libexif 库文件
wget https://sourceforge.net/projects/libexif/files/libexif/0.6.14/libexif-0.6.14.tar.gz
tar -xzvf libexif-0.6.14.tar.gz
cd libexif-0.6.14/
sudo apt-get install autopoint libtool gettext libpopt-dev
autoreconf -fvi # 自动生成 Makefile
./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/" # 使用静态编译
make
make install
# 编译和安装 exif 调用 libexif 库文件
cd $HOME/fuzzing_libexif
wget https://sourceforge.net/projects/libexif/files/exif/0.6.15/exif-0.6.15.tar.gz
tar -xzvf exif-0.6.15.tar.gz
cd exif-exif-0_6_15-release/
autoreconf -fvi
./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/" PKG_CONFIG_PATH=$HOME/fuzzing_libexif/install/lib/pkgconfig
make
make install
测试是否编译成功:
cd $HOME/fuzzing_libexif
git clone https://github.com/ianare/exif-samples.git
# 测试
./exif exif-samples-master/jpg/tests/11-tests.jpg
-------------------------------------------------------------------
# 测试结果
EXIF tags in 'exif-samples/11-tests.jpg' ('英特尔' byte order):
--------------------+----------------------------------------------------------
Tag |Value
--------------------+----------------------------------------------------------
Manufacturer |Canon
Model |Canon DIGITAL IXUS 40
Date and Time |2007:09:03 16:03:45
YCbCr Positioning |centered
RelatedImageWidth |2272
RelatedImageLength |1704
Custom Rendered |正常过程
曝光模式 |自动曝光
白平衡 |自动白平衡
数码变焦倍率 |1.00
场景捕获类型 |标准
Exposure Time |1/500 sec.
FNumber |f/2.8
Exif Version |Exif版本2.2
Date and Time (origi|2007:09:03 16:03:45
Date and Time (digit|2007:09:03 16:03:45
ComponentsConfigurat|Y Cb Cr -
Compressed Bits per |3.00
Shutter speed |8.97 EV (APEX: 22, 1/501 sec.)
光圈 |2.97 EV (f/2.8)
曝光偏差 |0.00 EV
MaxApertureValue |2.97 EV (f/2.8)
测距模式 |样式
闪光灯 |Flash did not fire, auto mode.
焦距 |5.8 mm
制作者备忘 |1176 字节未知数据
用户备注 |
FlashPixVersion |FlashPix版本 1.0
色彩空间 |sRGB
PixelXDimension |2272
PixelYDimension |1704
Focal Plane x-Resolu|10142.86
Focal Plane y-Resolu|10142.86
焦平面分辨率�|英寸
传感方式 |单芯片色彩区域传感器
File Source |DSC
--------------------+----------------------------------------------------------
测试成功,接下来用afl-clang-lto
重新编译:
# 重新编译 libexif
rm -r $HOME/fuzzing_libexif/install
cd $HOME/fuzzing_libexif/libexif-libexif-0_6_14-release/
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/"
make
make install
# 重新编译 exif
cd $HOME/fuzzing_libexif/exif-exif-0_6_15-release
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/" PKG_CONFIG_PATH=$HOME/fuzzing_libexif/install/lib/pkgconfig
make
make install
这里说明一下afl-clang-fast
和afl-clang-lto
区别。原文这样解释到:afl-clang-lto
是一种无碰撞检测,它比afl-clang-fast
更快并提供更好的结果。对于这些编译器在何种情况下适用,原文给出了这样一幅图,可以说是很清晰了:
+--------------------------------+
| clang/clang++ 11+ is available | --> use LTO mode (afl-clang-lto/afl-clang-lto++)
+--------------------------------+ see [instrumentation/README.lto.md](instrumentation/README.lto.md)
|
| if not, or if the target fails with LTO afl-clang-lto/++
|
v
+---------------------------------+
| clang/clang++ 6.0+ is available | --> use LLVM mode (afl-clang-fast/afl-clang-fast++)
+---------------------------------+ see [instrumentation/README.llvm.md](instrumentation/README.llvm.md)
|
| if not, or if the target fails with LLVM afl-clang-fast/++
|
v
+--------------------------------+
| gcc 5+ is available | -> use GCC_PLUGIN mode (afl-gcc-fast/afl-g++-fast)
+--------------------------------+ see [instrumentation/README.gcc_plugin.md](instrumentation/README.gcc_plugin.md) and
[instrumentation/README.instrument_list.md](instrumentation/README.instrument_list.md)
|
| if not, or if you do not have a gcc with plugin support
|
v
use GCC mode (afl-gcc/afl-g++) (or afl-clang/afl-clang++ for clang)
开始Fuzz:
sudo su
echo core >/proc/sys/kernel/core_pattern
exit
afl-fuzz -i $HOME/fuzzing_libexif/exif-samples-master/jpg/ -o $HOME/fuzzing_libexif/out/ -s 123 -- $HOME/fuzzing_libexif/install/bin/exif @@
结果分析
原文是通过Eclipse-CDT
来调试的,不过本人更倾向于用pwndbg,主要是省去配置的工作,用一条命令就可以开整;还有就是用习惯了pwndbg,通过pwndbg可以看到更多的内存信息。
首先重新编译带调试信息的libexif
和exif
,这样就可以源码调试了:
# libexif
rm -r $HOME/fuzzing_libexif/install
cd $HOME/fuzzing_libexif/libexif-libexif-0_6_14-release/
make clean
CFLAGS="-g -O0" CXXFLAGS="-g -O0"
make
# exif
cd $HOME/fuzzing_libexif/exif-exif-0_6_15-release
make clean
CFLAGS="-g -O0" CXXFLAGS="-g -O0" PKG_CONFIG_PATH=$HOME/fuzzing_libexif/install/lib/pkgconfig
make
通过对报错输入的调试,主要有以下两条执行流:
pwndbg> bt
#0 0x000000000022c1bf in exif_get_sshort (buf=0x100451e85 <error: Cannot access memory at address 0x100451e85>, order=EXIF_BYTE_ORDER_MOTOROLA) at exif-utils.c:92
#1 exif_get_short (buf=0x100451e85 <error: Cannot access memory at address 0x100451e85>, order=EXIF_BYTE_ORDER_MOTOROLA) at exif-utils.c:104
#2 exif_data_load_data (data=0x452680, d_orig=<optimized out>, ds_orig=<optimized out>) at exif-data.c:819
#3 0x00000000002214d6 in exif_loader_get_data (loader=<optimized out>) at /home/pursue/桌面/Fuzzing101-main/Exercise 2/fuzzing_libexif/libexif-0.6.14/libexif/exif-loader.c:387
#4 main (argc=<optimized out>, argv=<optimized out>) at main.c:438
#5 0x00007ffff7cb7d90 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#6 0x00007ffff7cb7e40 in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
#7 0x000000000021a925 in _start ()
pwndbg> bt
#0 0x00007ffff7e2eed0 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#1 0x000000000022efe5 in exif_data_load_data_thumbnail (data=0x452610, d=0x451e16 "MM", ds=2026, offset=702, size=4294967168) at exif-data.c:292
#2 exif_data_load_data_content (data=<optimized out>, ifd=<optimized out>, d=<optimized out>, ds=<optimized out>, offset=674, recursion_depth=<optimized out>) at exif-data.c:381
#3 0x000000000022c322 in exif_data_load_data (data=0x452610, d_orig=<optimized out>, ds_orig=<optimized out>) at exif-data.c:835
#4 0x00000000002214d6 in exif_loader_get_data (loader=<optimized out>) at /home/pursue/桌面/Fuzzing101-main/Exercise 2/fuzzing_libexif/libexif-0.6.14/libexif/exif-loader.c:387
#5 main (argc=<optimized out>, argv=<optimized out>) at main.c:438
#6 0x00007ffff7cb7d90 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#7 0x00007ffff7cb7e40 in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
#8 0x000000000021a925 in _start ()
对于
libexif
中的api
,该文档做了功能的解释:EXIF library (libexif) API: Globals
第一条执行流:
main -> exif_loader_get_data -> exif_data_load_data -> exif_get_short -> exif_get_sshort
可以在回溯中看到buf超越到了不可访问的空间:
0x000000000022c1bf in exif_get_sshort (buf=0x100451e85 <error: Cannot access memory at address 0x100451e85>, order=EXIF_BYTE_ORDER_MOTOROLA) at exif-utils.c:92 # Cannot access memory at address 0x100451e85
往回找一下,发现在
exif-data.c
的819行找到了buf的真相:/* IFD 1 offset */ // ExifLong offset; unsigned int ds; if (offset + 6 + 2 > ds) { // 检查 return; } n = exif_get_short (d + 6 + offset, data->priv->order);
由于这里因为gdb优化代码编译的原因,所以我们不能从回溯中找到参数的信息。这里通过gdb的动调,找到了
offset
和ds
的值。# pwndbg ──────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────── *RBX 0x7c3 *RBP 0xffffffff ───────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────── ► 0x22c117 <exif_data_load_data+1239> lea eax, [rbp + 8] <__afl_area_initial> 0x22c11a <exif_data_load_data+1242> cmp eax, ebx 0x22c11c <exif_data_load_data+1244> jbe exif_data_load_data+1306
可以看到
offset
的值为0xffffffff
,也就是-1
,加上8那就是7
;而ds
的值是0x7c3
,显然是可以通过检查的。总结那就是当offset
的值在-8 ~ -1
之间是能够绕过检查触发程序崩溃的。所以修改如下:if (offset > 0xfffffff0 | offset + 6 + 2 > ds) { return; }
第二条执行流:
main -> exif_loader_get_data -> exif_data_load_data -> exif_data_load_data_content -> exif_data_load_data_thumbnail
可以在回溯中清晰的看到
memcpy()
函数复制的size过大导致崩溃:# pwndbg 288 data->size = size; 289 data->data = exif_data_alloc (data, data->size); 290 if (!data->data) 291 return; ► 292 memcpy (data->data, d + offset, data->size); 293 } # bt 0x000000000022efe5 in exif_data_load_data_thumbnail (data=0x452610, d=0x451e16 "MM", ds=2026, offset=702, size=4294967168) at exif-data.c:292 # size=4294967168
以下是
exif_data_load_data_thumbnail
函数的源码:static void exif_data_load_data_thumbnail (ExifData *data, const unsigned char *d, unsigned int ds, ExifLong offset, ExifLong size) { // typedef uint32_t ExifLong; /* 4 bytes */ if (ds < offset + size) { // 在这里,ds=2026, offset=702, size=0xFFFFFF80=-128,显然是可以通过检查的 exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "Bogus thumbnail offset and size: %i < %i + %i.", (int) ds, (int) offset, (int) size); return; } if (data->data) exif_mem_free (data->priv->mem, data->data); /* typedef struct _ExifData ExifData; struct _ExifData { ExifContent *ifd[EXIF_IFD_COUNT]; unsigned char *data; unsigned int size; // 问题就在此处 ExifDataPrivate *priv; }; */ data->size = size; // 强制转换的时候出现了问题 // size=0xFFFFFF80=4294967168 data->data = exif_data_alloc (data, data->size); if (!data->data) return; memcpy (data->data, d + offset, data->size); }
所以只需要更新一下这个检查就行:
if ((unsigned long int) ds < (unsigned long int) offset + (unsigned long int) size){ ... }
总结一下:不难发现,这两个洞都和整数溢出有关。
Exercise 3:TCPdump
简单来说,TCPdump
就是一款Linux平台下的抓包工具。
CVE-2017-13028:存在一个越界读取的漏洞。
Fuzz准备
搭建环境:
cd $HOME
mkdir fuzzing_tcpdump && cd fuzzing_tcpdump/
wget https://www.tcpdump.org/release/tcpdump-4.9.2.tar.gz
tar -xzvf tcpdump-4.9.2.tar.gz
wget https://www.tcpdump.org/release/libpcap-1.8.0.tar.gz
tar -xzvf libpcap-1.8.0.tar.gz
# 编译 libpcap-1.8.0
cd $HOME/fuzzing_tcpdump/libpcap-1.8.0/
./configure --enable-shared=no
make
# 编译和安装 tcpdump-4.9.2
cd $HOME/fuzzing_tcpdump/tcpdump-4.9.2/
./configure --prefix="$HOME/fuzzing_tcpdump/install/"
make
make install
# 测试
$HOME/fuzzing_tcpdump/install/sbin/tcpdump -h
--------------------------------------------------------------
pursue@pursue-virtual-machine:~/fuzzing_tcpdump/install/sbin$ ./tcpdump -h
tcpdump version 4.9.2
libpcap version 1.8.0
OpenSSL 3.0.2 15 Mar 2022
Usage: tcpdump [-aAbdDefhHIJKlLnNOpqStuUvxX#] [ -B size ] [ -c count ]
[ -C file_size ] [ -E algo:secret ] [ -F file ] [ -G seconds ]
[ -i interface ] [ -j tstamptype ] [ -M secret ] [ --number ]
[ -Q in|out|inout ]
[ -r file ] [ -s snaplen ] [ --time-stamp-precision precision ]
[ --immediate-mode ] [ -T type ] [ --version ] [ -V file ]
[ -w file ] [ -W filecount ] [ -y datalinktype ] [ -z postrotate-command ]
[ -Z user ] [ expression ]
接下来使用ASan
对其编译,什么是AddressSanitizer(ASan)
?原文给出了这样的解释:AddressSanitizer (ASan)
是C和C++的快速内存错误检测器。它由编译器插装模块和运行库组成。该工具能够发现堆栈和全局对象的越界访问,以及释放后使用、双重释放和内存泄漏错误。ASan
是开源的,从3.1版开始与LLVM编译器工具链集成。虽然它最初是作为LLVM的项目开发的,但它已被移植到GCC,并包含在GCC版本>= 4.8中。
rm -r $HOME/fuzzing_tcpdump/install
cd $HOME/fuzzing_tcpdump/libpcap-1.8.0/
make clean
cd $HOME/fuzzing_tcpdump/tcpdump-4.9.2/
make clean
cd $HOME/fuzzing_tcpdump/libpcap-1.8.0/
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzzing_tcpdump/install/"
AFL_USE_ASAN=1 make
cd $HOME/fuzzing_tcpdump/tcpdump-4.9.2/
AFL_USE_ASAN=1 CC=afl-clang-lto ./configure --prefix="$HOME/fuzzing_tcpdump/install/"
AFL_USE_ASAN=1 make
AFL_USE_ASAN=1 make install
但是在编译完成之后,运行程序发现报错:
pursue@pursue-virtual-machine:~/桌面/Fuzzing101-main/Exercise 3/fuzzing_tcpdump/tcpdump-4.9.2$ ./tcpdump -h
==59267==ERROR: AddressSanitizer failed to allocate 0x0 (0) bytes of SetAlternateSignalStack (error code: 22)
==59267==Process memory map follows:
0x000000200000-0x0000003ac000 /home/pursue/桌面/Fuzzing101-main/Exercise 3/fuzzing_tcpdump/tcpdump-4.9.2/tcpdump
0x0000003ac000-0x00000086b000 /home/pursue/桌面/Fuzzing101-main/Exercise 3/fuzzing_tcpdump/tcpdump-4.9.2/tcpdump
0x00000086b000-0x000000870000 /home/pursue/桌面/Fuzzing101-main/Exercise 3/fuzzing_tcpdump/tcpdump-4.9.2/tcpdump
0x000000870000-0x000000998000 /home/pursue/桌面/Fuzzing101-main/Exercise 3/fuzzing_tcpdump/tcpdump-4.9.2/tcpdump
0x000000998000-0x0000015f6000
0x00007fff7000-0x00008fff7000
0x00008fff7000-0x02008fff7000
0x02008fff7000-0x10007fff8000
0x7f7b2d5fe000-0x7f7b2d962000
0x7f7b2d962000-0x7f7b2d98a000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7f7b2d98a000-0x7f7b2db1f000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7f7b2db1f000-0x7f7b2db77000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7f7b2db77000-0x7f7b2db7b000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7f7b2db7b000-0x7f7b2db7d000 /usr/lib/x86_64-linux-gnu/libc.so.6
0x7f7b2db7d000-0x7f7b2db8a000
0x7f7b2db8a000-0x7f7b2db8d000 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7f7b2db8d000-0x7f7b2dba4000 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7f7b2dba4000-0x7f7b2dba8000 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7f7b2dba8000-0x7f7b2dba9000 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7f7b2dba9000-0x7f7b2dbaa000 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7f7b2dbaa000-0x7f7b2dbb8000 /usr/lib/x86_64-linux-gnu/libm.so.6
0x7f7b2dbb8000-0x7f7b2dc34000 /usr/lib/x86_64-linux-gnu/libm.so.6
0x7f7b2dc34000-0x7f7b2dc8f000 /usr/lib/x86_64-linux-gnu/libm.so.6
0x7f7b2dc8f000-0x7f7b2dc90000 /usr/lib/x86_64-linux-gnu/libm.so.6
0x7f7b2dc90000-0x7f7b2dc91000 /usr/lib/x86_64-linux-gnu/libm.so.6
0x7f7b2dc91000-0x7f7b2dd43000 /usr/lib/x86_64-linux-gnu/libcrypto.so.3
0x7f7b2dd43000-0x7f7b2dfa0000 /usr/lib/x86_64-linux-gnu/libcrypto.so.3
0x7f7b2dfa0000-0x7f7b2e072000 /usr/lib/x86_64-linux-gnu/libcrypto.so.3
0x7f7b2e072000-0x7f7b2e0cd000 /usr/lib/x86_64-linux-gnu/libcrypto.so.3
0x7f7b2e0cd000-0x7f7b2e0d0000 /usr/lib/x86_64-linux-gnu/libcrypto.so.3
0x7f7b2e0d0000-0x7f7b2e0d3000
0x7f7b2e0d9000-0x7f7b2e0e5000
0x7f7b2e0e5000-0x7f7b2e0e7000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7f7b2e0e7000-0x7f7b2e111000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7f7b2e111000-0x7f7b2e11c000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7f7b2e11c000-0x7f7b2e11d000
0x7f7b2e11d000-0x7f7b2e11f000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7f7b2e11f000-0x7f7b2e121000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
0x7ffe26590000-0x7ffe265b1000 [stack]
0x7ffe265bf000-0x7ffe265c3000 [vvar]
0x7ffe265c3000-0x7ffe265c5000 [vdso]
0xffffffffff600000-0xffffffffff601000 [vsyscall]
==59267==End of process memory map.
==59267==AddressSanitizer CHECK failed: /build/llvm-toolchain-11-mnvtwk/llvm-toolchain-11-11.1.0/compiler-rt/lib/sanitizer_common/sanitizer_common.cpp:54 "((0 && "unable to mmap")) != (0)" (0x0, 0x0)
<empty stack>
原因是用的Ubuntu版本不对,错误是在Ubuntu22.04上出现的,而在Ubuntu20.04上正常。
开始Fuzz:
sudo su
echo core >/proc/sys/kernel/core_pattern
exit
afl-fuzz -m none -i tcpdump-4.9.2/tests/ -o out/ -s 123 -- $HOME/fuzzing_tcpdump/install/sbin/tcpdump -vvvvXX -ee -nn -r @@
结果分析
Fuzz的过程有点漫长,近乎跑了半天的程序。
运行其中的一个crash样本
tcpdump-4.9.2/tcpdump -vvvvXX -ee -nn -r out/default/crashes/id:000000,sig:06,src:010739,time:56472860,execs:16241028,op:havoc,rep:8
Exercise 4:LibTIFF
libtiff库
是读取和写入tiff文件最主要的一个开源库。
CVE-2016-9297:越界读取漏洞,可通过构建的TIFF_SETGET_C16_ASCII
或TIFF_SETGET_C32_ASCII
标记值触发。
该挑战我们可以学习到:
- 如何使用LCOV测量代码覆盖率
- 如何使用代码覆盖率数据来提高模糊测试的有效性
代码覆盖率是一个软件指标,显示每行代码被触发的次数。通过使用代码覆盖率,我们将了解模糊器到达了代码的哪些部分,并可视化模糊处理过程。
Fuzz准备
源码环境搭建:
sudo apt install lcov
cd $HOME
mkdir fuzzing_tiff && cd fuzzing_tiff/
wget https://download.osgeo.org/libtiff/tiff-4.0.4.tar.gz
tar -xzvf tiff-4.0.4.tar.gz
cd tiff-4.0.4/
export LDFLAGS="--coverage"
export CFLAGS="--coverage"
./configure --prefix="$HOME/fuzzing_tiff/install/" --disable-shared
make
make install
测试程序是否编译成功,这里加上各种参数是为了提高代码出现bug的机会:
$HOME/fuzzing_tiff/install/bin/tiffinfo -D -j -c -r -s -w test/images/miniswhite-1c-1b.tiff
cd $HOME/fuzzing_tiff/tiff-4.0.4/
lcov --zerocounters --directory ./ # 重置以前的计数器
lcov --capture --initial --directory ./ --output-file app.info # 返回“基线”覆盖数据文件,其中包含每个检测行的零覆盖率
$HOME/fuzzing_tiff/install/bin/tiffinfo -D -j -c -r -s -w $HOME/fuzzing_tiff/tiff-4.0.4/test/images/palette-1c-1b.tiff
lcov --no-checksum --directory ./ --capture --output-file app2.info # 将当前覆盖状态保存到 app2.info 文件中
genhtml --highlight --legend -output-directory ./html-coverage/ ./app2.info # 通过html页面显示
注意:在该项目里必须要有.gcno
文件才能够进行lcov,否则很有可能是编译失败了。
html页面的路径为./html-coverage/index.html
:
接下来进行插桩编译:
export LLVM_CONFIG="llvm-config-11"
export CC=afl-clang-lto
./configure --prefix="$HOME/fuzzing_tiff/install/" --disable-shared
AFL_USE_ASAN=1 make
AFL_USE_ASAN=1 make install
开始Fuzz:
sudo su
echo core >/proc/sys/kernel/core_pattern
exit
afl-fuzz -m none -i tiff-4.0.4/test/images/ -o out/ -s 123 -- $HOME/fuzzing_tiff/install/bin/tiffinfo -D -j -c -r -s -w @@
结果分析
运行crash的实例就可以得到ASAN提示:
$HOME/fuzzing_tiff/install/bin/tiffinfo -D -j -c -r -s -w id:000000,sig:06,src:000016,time:52157,execs:64042,op:havoc,rep:2
- 蓝色部分为读取操作,大概意思就是在线程为T0中,在
0x6020000000b1
处读取了大小为2的数据,并显示了函数栈的信息。 - 绿色部分为溢出的情况,其中还显示了堆块分配的函数栈。
由于第一次阅读这样的ASAN报告,不是很熟练,所以对着CVE做了一下源码的分析,CVE告诉我们漏洞是通过TIFF_SETGET_C16_ASCII
或TIFF_SETGET_C32_ASCII
这两个标志触发的,发现在tif_dirread.c
中出现,这里以后者举例:
case TIFF_SETGET_C32_ASCII:
{
uint8* data;
assert(fip->field_readcount==TIFF_VARIABLE2);
assert(fip->field_passcount==1);
err=TIFFReadDirEntryByteArray(tif,dp,&data);
if (err==TIFFReadDirEntryErrOk)
{
int m;
m=TIFFSetField(tif,dp->tdir_tag,(uint32)(dp->tdir_count),data);
if (data!=0)
_TIFFfree(data);
if (!m)
return(0);
}
}
这里TIFFSetField()
函数的作用在官方文档的解释是根据引用或值获取标记值,对比一下4.2.0
版本的源码:
case TIFF_SETGET_C32_ASCII:
{
uint8* data;
assert(fip->field_readcount==TIFF_VARIABLE2);
assert(fip->field_passcount==1);
err=TIFFReadDirEntryByteArray(tif,dp,&data);
if (err==TIFFReadDirEntryErrOk)
{
int m;
if( data != 0 && dp->tdir_count > 0 && data[dp->tdir_count-1] != '\0' )
{
TIFFWarningExt(tif->tif_clientdata,module,"ASCII value for tag \"%s\" does not end in null byte. Forcing it to be null",fip->field_name);
data[dp->tdir_count-1] = '\0';
}
m=TIFFSetField(tif,dp->tdir_tag,(uint32)(dp->tdir_count),data);
if (data!=0)
_TIFFfree(data);
if (!m)
return(0);
}
}
很清楚,这里限制了data
这个变量的最后一个字节必须为空字节,用4.2.0
版本运行crash的样本,发现没有了ASAN的报告。
Exercise 5:LibXML2
LibXML2是C语言的一个库,可以方便对XML文档的各种操作。
CVE-2017-9048:堆栈缓冲区溢出漏洞
在该挑战中我们可以学习到:
- 使用自定义的字典帮助fuzzer找到新的执行路径
- 使用多核并行fuzzing提高效率
Fuzz准备
cd $HOME
mkdir Fuzzing_libxml2 && cd Fuzzing_libxml2
wget http://xmlsoft.org/download/libxml2-2.9.4.tar.gz
tar xvf libxml2-2.9.4.tar.gz && cd libxml2-2.9.4/
# build libxml2
sudo apt-get install python-dev
export CC=afl-clang-lto
export CXX=afl-clang-lto++
export CFLAGS="-fsanitize=address"
export CXXFLAGS="-fsanitize=address"
export LDFLAGS="-fsanitize=address"
AFL_USE_ASAN=1 ./configure --prefix="$HOME/Fuzzing_libxml2/libxml2-2.9.4/install" --disable-shared --without-debug --without-ftp --without-http --without-legacy --without-python LIBS='-ldl'
AFL_USE_ASAN=1 make -j$(nproc)
AFL_USE_ASAN=1 make install
似乎编译的时间挺长,检查一下编译后的二进制文件
需要XML实例和字典
mkdir afl_in
cp ./SampleInput.xml afl_in/
mkdir dictionaries && cd dictionaries
wget https://github.com/AFLplusplus/AFLplusplus/blob/stable/dictionaries/xml.dict
cd ..
关于fuzz参数的使用
-x
:标记使用的字典-D
:表示启用了确定性突变-M
:表示主模糊器-S
:表示从模糊器
afl-fuzz -m none -i ./afl_in -o afl_out -s 123 -x ./dictionaries/xml.dict -D -M master -- ./libxml2-2.9.4/xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@
afl-fuzz -m none -i ./afl_in -o afl_out -s 234 -S slave1 -- ./libxml2-2.9.4/xmllint --memory --noenc --nocdata --dtdattr --loaddtd --valid --xinclude @@
结果分析
暂时还未fuzz出结果