0%

Fuzz Lab2-libexif

Fuzz-Lab2:libexif

这次我们将 fuzz libexif EXIF 解析库(EXIF 信息,是可交换图像文件的缩写,专门为数码相机的照片设定,可以记录数码照片的属性信息和拍摄数据),目标是在 libexif 0.6.14 中找到 [CVE-2009-3895] 的 crash/PoC 和 [CVE-2012-2836] 的另一个 crash

  • CVE-2009-3895 是一种基于堆的缓冲区溢出,可以使用无效的 EXIF 图像触发
    • 基于堆的缓冲区溢出是一种发生在堆数据区域的缓冲区溢出,通常与显式动态内存管理(使用 malloc() 和 free() 函数的分配/释放)有关
    • 因此,远程攻击者可以利用此问题,在使用受影响库的应用程序上下文中执行任意代码
  • CVE-2012-2836 是一个越界读取漏洞,可以通过带有精心制作的 EXIF 标签的图像触发
    • 越界读取是当程序读取超出预期缓冲区末尾或开头之前的数据时发生的漏洞
    • 因此,它允许远程攻击者导致拒绝服务或可能从进程内存中获取潜在的敏感信息

学习的目标:

  • 使用外部应用程序对库进行模糊测试
  • 使用 afl-clang-lto,一种比 afl-clang-fast 更快且提供更好结果的无碰撞仪器
  • 使用 Eclipse IDE 作为 GDB 控制台的简单替代品进行分类

Do it yourself!

  • 找到一个使用 libexif 库的接口应用程序
  • 创建 exif 样本的种子语料库
  • 使用 afl-clang-lto 编译 libexif 和选择的应用程序进行模糊测试
  • Fuzz libexif,直到你有一些独特的崩溃
  • 对崩溃进行分类以找到每个漏洞的 PoC
  • 修复问题

CVE-2009-3895

Download and build your target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cd $HOME # 创建目录
mkdir fuzzing_libexif && cd fuzzing_libexif/
sudo apt install build-essential

cd ..
wget https://sourceforge.net/projects/libexif/files/libexif/0.6.18/libexif-0.6.18.tar.gz # 获取libexif
tar -zxvf libexif-0.6.18.tar.gz

wget https://github.com/libexif/exif/archive/refs/tags/exif-0_6_15-release.tar.gz # 下载使用库接口的应用程序
tar -xzvf exif-0_6_15-release.tar.gz

cd libexif-0.6.18 # 编译libexif
sudo apt-get install autopoint libtool gettext libpopt-dev
autoreconf -fvi
./configure --enable-shared=no --prefix="/home/yhellow/fuzzing_libexif/install/"
make
make install

cd ..
cd exif-exif-0_6_15-release/
autoreconf -fvi
./configure --enable-shared=no --prefix="/home/yhellow/fuzzing_libexif/tool/" PKG_CONFIG_PATH=$HOME/fuzzing_libexif/install/lib/pkgconfig
make
make install

测试 exif 能否运行只需要输入:

1
$HOME/fuzzing_libexif/tool/bin/exif 
  • 效果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
➜  exif-exif-0_6_15-release $HOME/fuzzing_libexif/tool/bin/exif 
用法: exif [OPTION...] file
-v, --version Display software version
-i, --ids Show IDs instead of tag names
-t, --tag=tag Select tag
--ifd=IFD Select IFD
-l, --list-tags List all EXIF tags
-|, --show-mnote Show contents of tag MakerNote
--remove Remove tag or ifd
-s, --show-description Show description of tag
-e, --extract-thumbnail Extract thumbnail
-r, --remove-thumbnail Remove thumbnail
-n, --insert-thumbnail=FILE Insert FILE as thumbnail
-o, --output=FILE Write data to FILE
--set-value=STRING Value
-m, --machine-readable Output in a machine-readable (tab delimited)
format
-x, --xml-output Output in a XML format
-d, --debug Show debugging messages

帮助选项:
-?, --help 显示这个帮助信息
--usage 显示简短的使用说明

Fuzz exif

在进行 fuzz 前,我们需要先了解 Exif 是什么:

  • Exif 是一种文件格式,可交换图像文件格式(Exchangeable image file format,官方简称Exif),是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据
  • 示例如下:
  • 那么就需要找到这样的文件样本,好在万能的 GitHub 啥都有,可以直接下载:
1
git clone https://github.com/ianare/exif-samples.git
  • 使用exif命令随便查看一张图片的信息:
1
2
cd exif-samples/jpg
$HOME/fuzzing_libexif/tool/bin/exif Nikon_D70.jpg
  • 结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
EXIF tags in 'Nikon_D70.jpg' ('英特尔' byte order):
--------------------+----------------------------------------------------------
Tag |Value
--------------------+----------------------------------------------------------
Manufacturer |NIKON CORPORATION
Model |NIKON D70
Orientation |top - left
x-Resolution |240.00
y-Resolution |240.00
Resolution Unit |英寸
Software |GIMP 2.4.5
Date and Time |2008:07:31 10:03:44
Compression |JPEG 压缩
x-Resolution |72.00
y-Resolution |72.00
Resolution Unit |英寸
Exposure Time |1/200 sec.
FNumber |f/9.0
Exposure Program |手动
ISO Speed Ratings |200
Date and Time (origi|2008:03:15 09:52:01
Shutter speed |7.64 EV (1/199 sec.)
光圈 |6.34 EV (f/9.0)
曝光偏差 |-1.00 EV
Maximum Aperture Val|3.30 EV (f/3.1)
测距模式 |Center-Weighted Average
闪光灯 |未闪光
焦距 |100.0 mm
色彩空间 |sRGB
PixelXDimension |100
PixelYDimension |66
Focal Length In 35mm|150
Exif Version |Exif版本2.1
FlashPixVersion |FlashPix版本 1.0
--------------------+----------------------------------------------------------
EXIF data contains a thumbnail (1700 bytes).

接下来使用 afl 编译器重新编译程序来执行 Fuzz

这次使用 afl-clang-lto 作为编译器来构建程序,afl-clang-lto 相比于 afl-clang-fast 是更好的选择,因为它是一种无碰撞检测,而且比 afl-clang-fast 快

  • 使用编译器重新构建程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rm -r $HOME/fuzzing_libexif/install
cd $HOME/libexif-0.6.18 # 使用afl-clang-lto编译器构建libexif
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install/"
make
make install

rm -r $HOME/fuzzing_libexif/tool
cd $HOME/exif-exif-0_6_15-release # 使用afl-clang-lto编译器构建exif
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/tool/" PKG_CONFIG_PATH=$HOME/fuzzing_libexif/install/lib/pkgconfig
make
make install

开始 fuzz:

1
afl-fuzz -i /home/yhellow/fuzzing_libexif/exif-samples/jpg/ -o /home/yhellow/fuzzing_libexif/out -s 123 -- /home/yhellow/fuzzing_libexif/tool/bin/exif @@

结果:

Reproduce the crash

1
2
3
➜  bin ./exif test.jpg                                                                                                                          
realloc(): invalid next size
[1] 2564 abort ./exif test.jpg
  • 发生错误,看起来像是堆溢出(victim chunk 的 next chunk->size 被破坏)
  • 使用 GDB bt 命令查看栈回溯
  • 函数 exif_content_fix 中发生了堆溢出(最好不要直接根据函数名来找源码,可以通过后面的 文件名+偏移 来找)
  • exif_content_fix 上打断点,结合源码进行调试,发现 exif_content_fix 执行两次以后发生错误,于是在 realloc 上打断点,用 bt 回溯栈:
  • 定位到的代码如下:
1
2
3
4
5
void *
exif_mem_realloc (ExifMem *mem, void *d, ExifLong ds)
{
return (mem && mem->realloc_func) ? mem->realloc_func (d, ds) : NULL;
}
1
2
3
4
5
6
7
8
9
0x238994 <exif_content_fix+6756>    call   rcx                           <exif_mem_realloc_func>
rdi: 0x46fe40 ◂— 0x100000001000100
rsi: 0x600

pwndbg> telescope 0x0000000000238996-0x2
00:00000x238994 (exif_content_fix+6756) ◂— call rcx
01:00080x23899c (exif_content_fix+6764) ◂— add byte ptr [rax], al
02:00100x2389a4 (exif_content_fix+6772) ◂— add al, byte ptr [rax]
03:00180x2389ac (exif_content_fix+6780) ◂— add dl, 1
  • 继续打断点调试:
1
2
3
0x238994 <exif_content_fix+6756>    call   rcx                           <exif_mem_realloc_func>
rdi: 0x46fe40 ◂— 0x100000001000100
rsi: 0x600
  • 找到目标堆:
1
2
3
4
5
6
7
8
9
pwndbg> telescope 0x46fe40-0x10
00:00000x46fe30 ◂— 0x0
01:00080x46fe38 ◂— 0x311
02:0010│ rdi 0x46fe40 ◂— 0x100000001000100
03:00180x46fe48 ◂— 0x100000000000000
04:00200x46fe50 ◂— 0x0

pwndbg> telescope 0x46fe30+0x310
00:00000x470140 ◂— 0x0
  • 发现 0x470140 处的 chunk 已经被破坏,可能是从 0x46fe30 中溢出的数据破坏了该 chunk,接下来的思路就是寻找往 0x46fe30 中写入数据的函数
  • 本来想同样的方法,在 malloc 上打断点,然后回溯栈 ,结果发现 0x46fe30 已经被申请了,但是还没有写入,所以就直接单步硬找目标函数,最后找到了 exif_entry_fix 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void
exif_entry_fix (ExifEntry *e)
{
unsigned int i;
ExifByteOrder o;
ExifRational r;
ExifSRational sr;

......

memmove (e->data + 8, e->data, e->size);
memcpy (e->data, "ASCII\0\0\0", 8);
e->size += 8;
e->components += 8;
exif_entry_log (e, EXIF_LOG_CODE_DEBUG,
_("Tag 'UserComment' has been expanded to at "
"least 8 bytes in order to follow the "
"specification."));

......

if (memcmp (e->data, "ASCII\0\0\0" , 8) &&
memcmp (e->data, "UNICODE\0" , 8) &&
memcmp (e->data, "JIS\0\0\0\0\0" , 8) &&
memcmp (e->data, "\0\0\0\0\0\0\0\0", 8)) {
e->data = exif_entry_realloc (e, e->data, 8 + e->size); /* 调用realloc */
if (!e->data) {
e->size = 0;
e->components = 0;
break;
}

......

}
  • 其函数有大量的 memmove memcpy 等函数,极有可能就是它们导致了堆溢出(exif_mem_realloc 也是在这里调用的),于是在 memcpy 上打断点,进行调试
  • 发现执行 54 次 memcpy 后程序崩溃,于是对比第 54 次 memcpy 执行前后的 heap 空间变化,发现 0x46fe30 中并没有写入数据,只能单步继续跟进了
  • 由于 exif_entry_realloc 无法下断点,最后还是没有找到溢出点

CVE-2012-2836

Download and build your target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
wget https://sourceforge.net/projects/libexif/files/libexif/0.6.14/libexif-0.6.14.tar.gz # 获取libexif
tar -zxvf libexif-0.6.14.tar.gz

cd $HOME/libexif-0.6.14 # 使用afl-clang-lto编译器构建libexif
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/install2/"
make
make install

cd $HOME/exif-exif-0_6_15-release # 使用afl-clang-lto编译器构建exif
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzzing_libexif/tool2/" PKG_CONFIG_PATH=$HOME/fuzzing_libexif/install2/lib/pkgconfig
make
make install

Fuzz exif

1
afl-fuzz -i /home/yhellow/fuzzing_libexif/exif-samples/jpg -o /home/yhellow/fuzzing_libexif/out2 -s 123 -- /home/yhellow/fuzzing_libexif/tool2/bin/exif @@

结果如下:

Reproduce the crash

1
2
➜  bin ./exif test.jpg 
[1] 1101259 segmentation fault ./exif test.jpg
  • 发生段错误,在 GDB 中使用 bt 命令:
  • 段错误发生在 exif_data_load_data 调用的 exif_get_sshort 函数中,源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ExifSShort
exif_get_sshort (const unsigned char *buf, ExifByteOrder order)
{
if (!buf) return 0;
switch (order) {
case EXIF_BYTE_ORDER_MOTOROLA:
return ((buf[0] << 8) | buf[1]); /* target */
case EXIF_BYTE_ORDER_INTEL:
return ((buf[1] << 8) | buf[0]);
}

/* Won't be reached */
return (0);
}

typedef enum {
EXIF_BYTE_ORDER_MOTOROLA, /* '0' */
EXIF_BYTE_ORDER_INTEL /* '1' */
} ExifByteOrder;
  • 单步调试,发现程序执行到以下代码时就停止了:
1
0x22bc6f <exif_data_load_data+1407>    movzx  esi, word ptr [rcx]
  • [rcx] 显然不合法,猜测传入 exif_get_sshort 的参数有误
  • exif_data_load_data 中单步调试,寻找调用 exif_get_sshort 并引发段错误的地方:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void
exif_data_load_data (ExifData *data, const unsigned char *d_orig,
unsigned int ds_orig)
{

......

/* Fixed value */
if (exif_get_short (d + 8, data->priv->order) != 0x002a)
return;

/* IFD 0 offset */
offset = exif_get_long (d + 10, data->priv->order);
exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
"IFD 0 at %i.", (int) offset);

/* Parse the actual exif data (usually offset 14 from start) */
exif_data_load_data_content (data, EXIF_IFD_0, d + 6, ds - 6, offset, 0);

/* IFD 1 offset */
if (offset + 6 + 2 > ds) {
return;
}
n = exif_get_short (d + 6 + offset, data->priv->order); /* targrt */
if (offset + 6 + 2 + 12 * n + 4 > ds) {
return;
}

......

}
  • PS:程序通过 exif_get_short 来调用 exif_get_sshort
1
2
3
4
5
ExifShort
exif_get_short (const unsigned char *buf, ExifByteOrder order)
{
return (exif_get_sshort (buf, order) & 0xffff);
}
  • 在 GDB 中定位到对应的位置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
──────────────────────────────────────────────────────────────────────────────────
RAX 0x7
RBX 0x453
RCX 0x100452c45
RDX 0x452c46 ◂— 0xffffffff2a004d4d /* 'MM' */
*RDI 0x453120 ◂— 0x0 /* buf */
RSI 0x0 /* order */
R8 0xffffffff
R9 0x0
R10 0x224ab0 (log_func_exit) ◂— push rbp
R11 0x7ffff7e4ebe0 (main_arena+96) —▸ 0x454700 ◂— 0x0
R12 0x452c46 ◂— 0xffffffff2a004d4d /* 'MM' */
R13 0x453108 —▸ 0x453120 ◂— 0x0
R14 0x4530d0 —▸ 0x453160 ◂— 0x0
R15 0x452c40 ◂— 0x4d4d000066697845 /* 'Exif' */
RBP 0xffffffff /* 异常 */
RSP 0x7fffffffc5d0 ◂— 0x5b0000006e /* 'n' */
*RIP 0x22bc13 (exif_data_load_data+1315) ◂— mov edx, dword ptr [rdi]
──────────────────────────────────────────────────────────────────────────────────
0x22bbca <exif_data_load_data+1242> cmp eax, ebx
0x22bbcc <exif_data_load_data+1244> jbe exif_data_load_data+1306 <exif_data_load_data+1306>

0x22bc0a <exif_data_load_data+1306> mov ecx, ebp
0x22bc0c <exif_data_load_data+1308> add rcx, r12
0x22bc0f <exif_data_load_data+1311> mov rdi, qword ptr [r13]
0x22bc13 <exif_data_load_data+1315> mov edx, dword ptr [rdi]
0x22bc15 <exif_data_load_data+1317> test edx, edx
0x22bc17 <exif_data_load_data+1319> je exif_data_load_data+1384 <exif_data_load_data+1384> /* if (!buf) return 0; */

0x22bc58 <exif_data_load_data+1384> mov rsi, qword ptr [rip + 0x1f7a9] <0x24b408>
0x22bc5f <exif_data_load_data+1391> mov al, byte ptr [rsi + 0x48b]
0x22bc65 <exif_data_load_data+1397> add al, 1
──────────────────────────────────────────────────────────────────────────────────
pwndbg> telescope 0x453120
00:0000│ rdi 0x453120 ◂— 0x0 /* buf[0] */
01:00080x453128 ◂— 0x0 /* buf[1] */
02:00100x453130 —▸ 0x452b90 ◂— 0x8
03:00180x453138 —▸ 0x452bc0 ◂— 0x7
04:00200x453140 ◂— 0x1
05:00280x453148 ◂— 0x400000003
06:00300x453150 ◂— 0x0
07:00380x453158 ◂— 0x31 /* '1' */
  • 调试后发现一些信息:
    • exif_get_sshort 的两个参数 buf order(已经标出)
    • RBP 的值异常
  • 后来发现 [rcx] 不合法是由 RBP 异常导致的
1
2
3
4
5
*RCX  0xffffffff
RBP 0xffffffff
──────────────────────────────────────────────────────────────────────────────────
0x22bc0a <exif_data_load_data+1306> mov ecx, ebp /* ecx == RBP */
0x22bc0c <exif_data_load_data+1308> add rcx, r12

现在的问题就很明了了:

  • 因为 [rcx] 不合法导致了段错误
  • 因为 RBP 异常导致了 [rcx] 不合法

现在只要找到导致 RBP 异常的代码就可以了:

1
2
3
4
5
  0x22b864 <exif_data_load_data+372>    mov    rbp, qword ptr [rcx + 0x10]
0x22b868 <exif_data_load_data+376> test eax, eax

0x22bb8a <exif_data_load_data+1178> mov ebp, dword ptr [r15 + 0xa]
0x22bb8e <exif_data_load_data+1182> bswap ebp
  • 程序直接控制了 RBP,对应源码如下:(第二处)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
*RAX 0x4502a0 ◂— 0x0
*RBX 0x453
*RCX 0x453120 ◂— 0x0
*RDX 0x1
*RDI 0x452c46 ◂— 0xffffffff2a004d4d /* 'MM' */
*RSI 0x20c394 ◂— 0x6873616c46004d4d /* 'MM' */
*R15 0x452c40 ◂— 0x4d4d000066697845 /* 'Exif' */
*RBP 0x452b90 ◂— 0x8
*RSP 0x7fffffffc5d0 ◂— 0x5b0000006e /* 'n' */
*RIP 0x22bb8a (exif_data_load_data+1178) ◂— mov ebp, dword ptr [r15 + 0xa]
───────────────────────────────────[ DISASM ]───────────────────────────────────
0x22bb8a <exif_data_load_data+1178> mov ebp, dword ptr [r15 + 0xa]
0x22bb8e <exif_data_load_data+1182> bswap ebp
───────────────────────────────[ SOURCE (CODE) ]────────────────────────────────
In file: /home/yhellow/libexif-0.6.14/libexif/exif-utils.c
130 exif_get_slong (const unsigned char *b, ExifByteOrder order)
131 {
132 if (!b) return 0;
133 switch (order) {
134 case EXIF_BYTE_ORDER_MOTOROLA:
135 return ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]);
136 case EXIF_BYTE_ORDER_INTEL:
137 return ((b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]);
138 }
139
────────────────────────────────────────────────────────────────────────────────
pwndbg> telescope 0x452c40+0xa
00:00000x452c4a ◂— 0xffffffffffffffff
... ↓ 2 skipped
03:00180x452c62 ◂— 0xffffffffff
04:00200x452c6a ◂— 0x2000f010c0008
05:00280x452c72 ◂— 0x19e000000090000
06:00300x452c7a ◂— 0x10000000020010
07:00380x452c82 ◂— 0x3001201a80000
  • 应该是程序的逻辑出错了,0x452c40 这里应该是一个结构体
  • 感觉离漏洞点已经很接近了,但就是说不出来具体哪里有问题