前言

通过对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()函数可能会产生无限递归导致程序崩溃。

原文链接:CVE-2019-13288 : In Xpdf 4.01.01, the Parser::getObj() function in Parser.cc may cause infinite recursion via a crafted file. A remote at (cvedetails.com)

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-fastXpdf进行插桩:

# 清空已经编译
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 - CVE-2009-3895 (mitre.org)

CVE-2012-2836:一个越界读取的漏洞,可以从内存中读取潜在的敏感信息。

原文链接:CVE - CVE-2012-2836 (mitre.org)

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-fastafl-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可以看到更多的内存信息。

首先重新编译带调试信息的libexifexif,这样就可以源码调试了:

# 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的动调,找到了offsetds的值。

    # 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:存在一个越界读取的漏洞。

原文链接:https://www.cvedetails.com/cve/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中。

参考链接:https://clang.llvm.org/docs/AddressSanitizer.html

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的过程有点漫长,近乎跑了半天的程序。

image-20230113105602933

运行其中的一个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

image-20230113110117052

Exercise 4:LibTIFF

libtiff库是读取和写入tiff文件最主要的一个开源库。

CVE-2016-9297:越界读取漏洞,可通过构建的TIFF_SETGET_C16_ASCIITIFF_SETGET_C32_ASCII标记值触发。

原文链接:https://cwe.mitre.org/data/definitions/125.html

该挑战我们可以学习到:

  • 如何使用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

image-20230111121333366

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,否则很有可能是编译失败了。

image-20230111164840859

html页面的路径为./html-coverage/index.html

image-20230111165018618

接下来进行插桩编译:

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 @@

结果分析

image-20230112175827228

运行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

image-20230112195957445

  • 蓝色部分为读取操作,大概意思就是在线程为T0中,在0x6020000000b1处读取了大小为2的数据,并显示了函数栈的信息。
  • 绿色部分为溢出的情况,其中还显示了堆块分配的函数栈。

由于第一次阅读这样的ASAN报告,不是很熟练,所以对着CVE做了一下源码的分析,CVE告诉我们漏洞是通过TIFF_SETGET_C16_ASCIITIFF_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的报告。

image-20230112213219005

Exercise 5:LibXML2

LibXML2是C语言的一个库,可以方便对XML文档的各种操作。

CVE-2017-9048:堆栈缓冲区溢出漏洞

在该挑战中我们可以学习到:

  • 使用自定义的字典帮助fuzzer找到新的执行路径
  • 使用多核并行fuzzing提高效率

原文链接:https://nvd.nist.gov/vuln/detail/CVE-2017-9048

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

似乎编译的时间挺长,检查一下编译后的二进制文件

image-20230130194549115

需要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出结果