AFL++实战其二

练习2 - libexif

版本:libexif-0.6.14

漏洞编号:CVE-2009-3895CVE-2012-2836


CVE-2009-3895 是一个基于堆的缓冲区溢出,可以被无效的 EXIF 映像触发。

基于堆的缓冲区溢出是一种发生在堆数据区域的缓冲区溢出类型,通常与显式动态内存管理(通过 malloc()和 free()函数进行分配/释放)有关。

因此,远程攻击者可以利用此问题在使用受影响库的应用程序上下文中执行任意代码。


CVE-2012-2836 是一个可通过带有精心设计的 EXIF 标签的镜像触发的“越界读取”漏洞。

越界读取是指程序读取数据超过预期缓冲区的结束点或开始时发生的漏洞。

因此,它允许远程攻击者实施拒绝服务,或可能从进程内存中获取潜在敏感信息。


下载libexit

1
2
3
4
mkdir $HOME/fuzz_main
cd $HOME/fuzz_main
wget https://github.com/libexif/libexif/archive/refs/tags/libexif-0_6_14-release.tar.gz
tar -xzvf libexif-0_6_14-release.tar.gz

构建并安装:

1
2
3
4
5
6
cd libexif-libexif-0_6_14-release/
sudo apt-get install autopoint libtool gettext libpopt-dev
autoreconf -fvi
./configure --enable-shared=no --prefix="$HOME/fuzz_main/install/"
make
make install

选择接口应用

由于libexif是一个库,我们可以使用作者的另外一个项目来利用这个库。

1
2
3
cd $HOME/fuzz_main
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

构建该应用:

1
2
3
4
5
cd exif-exif-0_6_15-release/
autoreconf -fvi
./configure --enable-shared=no --prefix="$HOME/fuzz_main/install/" PKG_CONFIG_PATH=$HOME/fuzz_main/install/lib/pkgconfig
make
make install

测试你的工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
╰─ $HOME/fuzz_main/install/bin/exif
Usage: 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 options:
-?, --help Show this help message
--usage Display brief usage message

种子语料库创建:

1
2
3
cd $HOME/fuzz_main
wget https://github.com/ianare/exif-samples/archive/refs/heads/master.zip
unzip master.zip

测试一下:

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
43
44
45
46
47
48
49
50
51
52
╰─ $HOME/fuzz_main/install/bin/exif $HOME/fuzz_main/exif-samples-master/jpg/Canon_40D.jpg
EXIF tags in '/home/zlsf/fuzz_main/exif-samples-master/jpg/Canon_40D.jpg' ('Intel' byte order):
--------------------+----------------------------------------------------------
Tag |Value
--------------------+----------------------------------------------------------
Manufacturer |Canon
Model |Canon EOS 40D
Orientation |top - left
x-Resolution |72.00
y-Resolution |72.00
Resolution Unit |Inch
Software |GIMP 2.4.5
Date and Time |2008:07:31 10:38:11
YCbCr Positioning |co-sited
Compression |JPEG compression
x-Resolution |72.00
y-Resolution |72.00
Resolution Unit |Inch
Exposure Time |1/160 sec.
FNumber |f/7.1
ExposureProgram |Manual
ISO Speed Ratings |100
Exif Version |Exif Version 2.21
Date and Time (origi|2008:05:30 15:56:01
Date and Time (digit|2008:05:30 15:56:01
ComponentsConfigurat|Y Cb Cr -
Shutter speed |7.38 EV (APEX: 12, 1/165 sec.)
Aperture |5.62 EV (f/7.0)
Exposure Bias |0.00 EV
Metering Mode |Pattern
Flash |Flash fired, compulsory flash mode.
Focal Length |135.0 mm
User Comment |
SubsecTime |00
SubSecTimeOriginal |00
SubSecTimeDigitized |00
FlashPixVersion |FlashPix Version 1.0
Color Space |sRGB
PixelXDimension |100
PixelYDimension |68
Focal Plane x-Resolu|4438.36
Focal Plane y-Resolu|4445.97
Focal Plane Resoluti|Inch
Custom Rendered |Normal process
Exposure Mode |Manual exposure
White Balance |Auto white balance
Scene Capture Type |Standard
GPS tag version |0x02, 0x02, 0x00, 0x00
InteroperabilityInde|R98
InteroperabilityVers|0100
--------------------+----------------------------------------------------------
EXIF data contains a thumbnail (1378 bytes).

使用afl-clang-lto编译

1
2
3
4
5
6
7
rm -fr $HOME/fuzz_main/install
cd $HOME/fuzz_main/libexif-libexif-0_6_14-release/
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzz_main/install/"
make
make install
1
2
3
4
5
6
cd $HOME/fuzz_main/exif-exif-0_6_15-release
make clean
export LLVM_CONFIG="llvm-config-11"
CC=afl-clang-lto ./configure --enable-shared=no --prefix="$HOME/fuzz_main/install/" PKG_CONFIG_PATH=$HOME/fuzz_main/install/lib/pkgconfig
make
make install

afl-clang-lto 比其 afl-clang-fast更快,对于什么时候取决哪个编译器可以参照如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
+ -------------------------------- +
| 可使用clang/clang++ 11+ | --> 使用LTO模式(afl-clang-lto/afl-clang-lto++)
+--------------------------------+
|
| 如果不是,或者目标在使用LTO时失败afl-clang-lto/++
|
v
+ --------------------------------- +
| Clang/Clang++ 6.0及以上版本可用 | --> 使用LLVM模式(afl-clang-fast/afl-clang-fast++)
+---------------------------------+
|
| 如果不是,或者目标在使用LLVM afl-clang-fast/++ 编译时失败
|
v
+ -------------------------------- +
| gcc 5 + 可用 | -> 使用GCC_PLUGIN模式 (afl-gcc-fast/afl-g ++-fast)
+ -------------------------------- +
|
| 如果没有,或者您没有支持插件的gcc
|
v
使用GCC模式 (afl-gcc/afl-g ++) (或afl-clang/afl-clang ++ 用于clang)

开始fuzz

1
2
3
mkdir -p $HOME/fuzz_main/fuzz
cd $HOME/fuzz_main/fuzz
afl-fuzz -i $HOME/fuzz_main/exif-samples-master/jpg -o ./out -s 123 -- $HOME/fuzz_main/install/bin/exif @@

这次fuzz应该比较快,大约不到10分钟。大约出现15个左右的特别崩溃就能够停止。

调试崩溃

练习一中我们使用gdb进行调试,这次我们使用vscode配合Remote-SSH插件来进行远程调试。

至于为什么要进行远程调试是因为为了节约资源我把Linux桌面环境关闭了,使用Windows自带的终端装上SSH在完成工作。

首先你得安装好了vscode以及C/C++系列插件和Remote-SSH并将一系列SSH配置好,我采取的方案是使用私钥连接Linux,这样可以做到不用每次连接都需要输入密码的效果,而且vscode也能自动读取配置好的SSH配置文件。具体实现可以问AI。

在连接完成后选择打开文件,导航到:

1
$HOME/fuzz_main/exif-exif-0_6_15-release

新建.vscode文件夹在其中新建lauch.json文件内容:

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
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug exif",
"type": "cppdbg",
"request": "launch",
"program": "${env:HOME}/fuzz_main/dbg_install/bin/exif",
"args": ["${env:HOME}/fuzz_main/fuzz/out/default/crashes/<崩溃输入文件名>"],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"sourceFileMap": {
"/home/zlsf/fuzz_main/libexif-libexif-0_6_14-release": "${env:HOME}/fuzz_main/libexif-libexif-0_6_14-release"
},
"setupCommands": [
{
"description": "Enable pretty-printing",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

在调试之前点击扩展,一定要确定给SSH的远程LInux上也安装了C/C++的插件,不然会调试失败。

完成后点击F5即可开始调试,你也可以打开源代码直接下断点,这样F5会直接停在断点附近。

我们可以在调试启动后在调试控制台最下面执行命令,如果需要使用Linux的gdb命令则需要在最前面加上-exec

源码追踪

CVE-2012-2836

如果某个崩溃输入让你的源码调试卡在了exif-mnote-data-canon.c中,那么这就大概是CVE-2012-2836。

关键位置是exif-mnote-data-canon.c:218:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void
exif_mnote_data_canon_load (ExifMnoteData *ne,
const unsigned char *buf, unsigned int buf_size)
{
ExifMnoteDataCanon *n = (ExifMnoteDataCanon *) ne;
ExifShort c;
unsigned int i, o, s;

// ...
if (o + s > buf_size) return; //问题出在这

/* Sanity check */
n->entries[i].data = exif_mem_alloc (ne->mem, sizeof (char) * s);
if (!n->entries[i].data) return;
n->entries[i].size = s;
memcpy (n->entries[i].data, buf + o, s);
}
}

当执行这个判断时其各个变量如下:

1
2
3
o = 953
s = 4294966796
buf_size = 3245

此时明显o + s > buf_size, 但是该return却没有被触发,通过这几个变量的定义我们可以发现他们都是unsigned int的,不会因为加法变成负数,但是整数溢出不存在变负数这一种情况。经过简单的计算:

1
2
3
4
5
6
7
unsigned int o = 953;
unsigned int s = 4294966796; // 0xFFFFFFEC

o + s = 953 + 4294966796 = 4294967749
= 0x1000003B5
= 0x3B5 (截断到32位)
= 949 < buf_size = 3245

这时出现了位溢出。

修复方法也很简单

1
2
3
// exif-mnote-data-canon.c change line in 218
-- if (o + s > buf_size) return;
++ if (o + s < o || o + s > buf_size) return;

不过实际上根据我在互联网上搜索的内容,这并不是真正的CVE-2012-2836,因为CVE-2012-2836是出现在exif_data_load_data函数中,实际情况应该如下:

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
void
exif_data_load_data (ExifData *data, const unsigned char *d_orig,
unsigned int ds_orig)
{
unsigned int l;
ExifLong offset;
ExifShort n;
const unsigned char *d = d_orig;
unsigned int ds = ds_orig, len;
// ...
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) { //问题校验出在这,这里的offset也是unsigned int类似类型的,同样存在溢出
return; //这里是 exif-data.c:817
}
n = exif_get_short (d + 6 + offset, data->priv->order);
if (offset + 6 + 2 + 12 * n + 4 > ds) {
return;
}
offset = exif_get_long (d + 6 + offset + 2 + 12 * n, data->priv->order); // 这个是问题触发函数
// ...
exif_mnote_data_load (data->priv->md, d, ds); // 而这个是我的问题函数 exif-data.c:867

不过都是同一类型的,位置也相差不远。

CVE-2009-3895

翻遍了我的16个输出数据没有发现这个漏洞,我们使用afl++的中断恢复继续寻找(最好备份以前的out文件夹):

1
2
export AFL_AUTORESUME=1
afl-fuzz -i $HOME/fuzz_main/exif-samples-master/jpg -o ./out -s 123 -- $HOME/fuzz_main/install/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
24
25
26
27
28
29
    AFL ++4.41a {default} (/home/zlsf/fuzz_main/install/bin/exif) [explore]
┌─ process timing ────────────────────────────────────┬─ overall results ────┐
│ run time : 0 days, 0 hrs, 21 min, 20 sec │ cycles done : 1 │
│ last new find : 0 days, 0 hrs, 0 min, 9 sec │ corpus count : 714 │
│last saved crash : 0 days, 0 hrs, 1 min, 28 sec │saved crashes : 25 │
│ last saved hang : none seen yet │ saved hangs : 0 │
├─ cycle progress ─────────────────────┬─ map coverage┴──────────────────────┤
│ now processing : 456*1 (63.9%) │ map density : 10.43% / 23.12% │
│ runs timed out : 1 (0.14%) │ count coverage : 3.84 bits/tuple │
├─ stage progress ─────────────────────┼─ findings in depth ─────────────────┤
│ now trying : trim 8/8 │ favored items : 69 (9.66%) │
│ stage execs : 869/908 (95.70%) │ new edges on : 13 (1.82%) │
│ total execs : 1.41M │ total crashes : 73 (25 saved) │
exec speed : 975.8/sec │ total tmouts : 0 (0 saved) │
├─ fuzzing strategy yields ────────────┴─────────────┬─ item geometry ───────┤
│ bit flips : 0/839k, 1/839k, 1/839k │ levels : 16 │
│ byte flips : 0/104k, 0/104k, 0/104k │ pending : 69 │
│ arithmetics : 0/7.35M, 0/14.7M, 0/14.7M │ pend fav : 0 │
│ known ints : 0/944k, 3/3.99M, 0/5.88M │ own finds : 618 │
│ dictionary : 1/3.15M, 0/3.15M, 0/0, 0/0 │ imported : 0 │
│havoc/splice : 152/228k, 0/0 │ stability : 100.00% │
│py/custom/rq : unused, unused, unused, unused ├───────────────────────┘
│ trim/eff : 82.14%/586k, 99.99% │ [cpu000: 50%]
└─ strategy: explore ────────── state: in progress ──┘^C

+++ Testing aborted by user +++
[*] Writing ./out/default/fastresume.bin ...
[+] fastresume.bin successfully written with 114962 bytes.
[+] We're done here. Have a nice day!

通过追踪源码我发现问题出在:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
exif_entry_fix (ExifEntry *e)
{
unsigned int i;
ExifByteOrder o;
ExifRational r;
ExifSRational sr;
// ...
exif_set_short (
e->data + i *
exif_format_get_size (
EXIF_FORMAT_SHORT), o,
(ExifShort) exif_get_long (
e->data + i *
exif_format_get_size (
EXIF_FORMAT_LONG), o)); // exif-entry.c:189
// ...

其中exif_get_long (e->data + i * exif_format_get_size (EXIF_FORMAT_LONG), o)这里,其中i在某一时刻的值为2147483649导致大规模越界超出了的堆的范围。

向上追踪则是:

1
2
3
4
5
6
7
8
9
10
11
12
13
exif_data_load_data_entry (ExifData *data, ExifEntry *entry,
const unsigned char *d,
unsigned int size, unsigned int offset)
{
unsigned int s, doff;

entry->tag = exif_get_short (d + offset + 0, data->priv->order);
entry->format = exif_get_short (d + offset + 2, data->priv->order);
entry->components = exif_get_long (d + offset + 4, data->priv->order);
exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
"Loading entry 0x%x ('%s')...", entry->tag,
exif_tag_get_name (entry->tag)); //这里出了问题 exif-data.c:166
// ...

这里是读取文件到内存的函数,看来是文件的非法格式导致后续i过大的问题。

修复:

在for循环之前判断一下e->components是否大的不合常理即可。


AFL++实战其二
https://zlsf-zl.github.io/2026/04/06/AFL-实战其二/
作者
ZLSF
发布于
2026年4月6日
许可协议