AFL++实战其六

练习6 - gimp

版本:GIMP 2.8.16

漏洞编号:CVE-2016-4994

CVE-2016-4994 是一个“免费后使用”(Use-After-Free)漏洞,可以通过精心制作的 XCF 文件触发。

使用后错误是指程序在释放指针后仍继续使用指针。

这可能带来多种不利后果,从有效数据的损坏到任意代码的执行。


不儿哥们,环境也太老了吧。

致命点在于需要安装 python2 。

apt 已经没有这个了(我的环境是ubuntu24.04)。

所以我们这次练习就当一个理论者吧。


下载你的目标

首先你得装上环境依赖:

1
sudo apt-get install build-essential libatk1.0-dev libfontconfig1-dev libcairo2-dev libgudev-1.0-0 libdbus-1-dev libdbus-glib-1-dev libexif-dev libxfixes-dev libgtk2.0-dev python2.7-dev libpango1.0-dev libglib2.0-dev zlib1g-dev intltool libbabl-dev

第一步就寄了。

这是 GEGL 0.2 ,原生 ubuntu 没这个:

1
2
wget https://download.gimp.org/pub/gegl/0.2/gegl-0.2.0.tar.bz2
tar xvf gegl-0.2.0.tar.bz2 && cd gegl-0.2.0

对源代码做点改动:

1
2
sed -i 's/CODEC_CAP_TRUNCATED/AV_CODEC_CAP_TRUNCATED/g' ./operations/external/ff-load.c
sed -i 's/CODEC_FLAG_TRUNCATED/AV_CODEC_FLAG_TRUNCATED/g' ./operations/external/ff-load.c

构建他:

1
2
3
./configure --enable-debug --disable-glibtest  --without-vala --without-cairo --without-pango --without-pangocairo --without-gdk-pixbuf --without-lensfun --without-libjpeg --without-libpng --without-librsvg --without-openexr --without-sdl --without-libopenraw --without-jasper --without-graphviz --without-lua --without-libavformat --without-libv4l --without-libspiro --without-exiv2 --without-umfpack
make -j$(nproc)
sudo make install

现在下载 GIMP:

1
2
3
cd ..
wget https://mirror.klaus-uwe.me/gimp/pub/gimp/v2.8/gimp-2.8.16.tar.bz2
tar xvf gimp-2.8.16.tar.bz2 && cd gimp-2.8.16/

使用 afl-clang-lto 构建他:

1
2
3
CC=afl-clang-lto CXX=afl-clang-lto++ PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$HOME/Fuzzing_gimp/gegl-0.2.0/ CFLAGS="-fsanitize=address" CXXFLAGS="-fsanitize=address" LDFLAGS="-fsanitize=address" ./configure --disable-gtktest --disable-glibtest --disable-alsatest --disable-nls --without-libtiff --without-libjpeg --without-bzip2 --without-gs --without-libpng --without-libmng --without-libexif --without-aa --without-libxpm --without-webkit --without-librsvg --without-print --without-poppler --without-cairo-pdf --without-gvfs --without-libcurl --without-wmf --without-libjasper --without-alsa --without-gudev --disable-python --enable-gimp-console --without-mac-twain --without-script-fu --without-gudev --without-dbus --disable-mp --without-linux-input --without-xvfb-run --with-gif-compression=none --without-xmc --with-shm=none --enable-debug  --prefix="$HOME/Fuzzing_gimp/gimp-2.8.16/install"
make -j$(nproc)
make install

持久模式

就是通过写一个标志使得单个分支被连续多次变异后的文件输入,使得模糊效率大大提升。

主要是跳过了程序的初始化进程。

对于当前程序,我们可以修改 app.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*  Load the images given on the command-line. app.c:224
*/
if (filenames)
{
gint i;

for (i = 0; filenames[i] != NULL; i++)
{
if (run_loop) {

#ifdef __AFL_COMPILER
while(__AFL_LOOP(1000)) {
file_open_from_command_line (gimp, filenames[i], as_new);
}

exit(0);

#else
file_open_from_command_line (gimp, filenames[i], as_new);
#endif
}
}
}

或是将AFL_LOOP 宏插入xcf_load_invoker函数中:

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
static GValueArray *
xcf_load_invoker (GimpProcedure *procedure,
Gimp *gimp,
GimpContext *context,
GimpProgress *progress,
const GValueArray *args,
GError **error)
{
XcfInfo info;
GValueArray *return_vals;
GimpImage *image = NULL;
const gchar *filename;
gboolean success = FALSE;
gchar id[14];

gimp_set_busy (gimp);

filename = g_value_get_string (&args->values[1]);
#ifdef __AFL_COMPILER
while(__AFL_LOOP(10000)){
#endif
info.fp = g_fopen (filename, "rb");

if (info.fp)
{
// ...

区别是前一种允许我们针对不同的输入格式,而第二种更快。

种子语料库:

1
2
mkdir seeds&&cd seeds
wget https://github.com/antonio-morales/Fuzzing101/blob/main/Exercise%206/SampleInput.xcf

开始模糊

移除不必要的插件来节省时间:

1
rm ./install/lib/gimp/2.0/plug-ins/*

开始 fuzz:

1
ASAN_OPTIONS=detect_leaks=0,abort_on_error=1,symbolize=0 afl-fuzz -i ./seeds -o ./out -D -t 100 -- ./install/bin/gimp-console-2.8 --verbose -d -f @@

gimp-console-2.8 是 GIMP 仅限控制台版本。

模糊而不能,直接看结果修复吧。


看看源码

没有程序也没有输入,真难调试。官方修复示例也没了说是。

贴一个 ai 发现的可疑漏洞链吧。

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
static gboolean
xcf_load_level (XcfInfo *info,
TileManager *tiles)
{
guint32 saved_pos;
guint32 offset, offset2;
guint ntiles;
gint width;
gint height;
gint i;
gint fail;
Tile *previous;
Tile *tile;

info->cp += xcf_read_int32 (info->fp, (guint32 *) &width, 1);
info->cp += xcf_read_int32 (info->fp, (guint32 *) &height, 1);

if (width != tile_manager_width (tiles) ||
height != tile_manager_height (tiles))
return FALSE;

/* read in the first tile offset.
* if it is '0', then this tile level is empty
* and we can simply return.
*/
info->cp += xcf_read_int32 (info->fp, &offset, 1);
if (offset == 0)
return TRUE;

/* Initialize the reference for the in-memory tile-compression
*/
previous = NULL;

ntiles = tiles->ntile_rows * tiles->ntile_cols;
for (i = 0; i < ntiles; i++)
{
fail = FALSE;

if (offset == 0)
{
gimp_message_literal (info->gimp, G_OBJECT (info->progress),
GIMP_MESSAGE_ERROR,
"not enough tiles found in level");
return FALSE;
}

/* save the current position as it is where the
* next tile offset is stored.
*/
saved_pos = info->cp;

/* read in the offset of the next tile so we can calculate the amount
of data needed for this tile*/
info->cp += xcf_read_int32 (info->fp, &offset2, 1);

/* if the offset is 0 then we need to read in the maximum possible
allowing for negative compression */
if (offset2 == 0)
offset2 = offset + TILE_WIDTH * TILE_WIDTH * 4 * 1.5;
/* 1.5 is probably more
than we need to allow */

/* seek to the tile offset */
if (! xcf_seek_pos (info, offset, NULL))
return FALSE;

/* get the tile from the tile manager */
tile = tile_manager_get (tiles, i, TRUE, TRUE);

/* read in the tile */
switch (info->compression)
{
case COMPRESS_NONE:
if (!xcf_load_tile (info, tile))
fail = TRUE;
break;
case COMPRESS_RLE:
if (!xcf_load_tile_rle (info, tile, offset2 - offset)) //问题在这
fail = TRUE;
break;
// ...

offsetoffset2都是无符号型的,这里当offset2<offset时,会造成整数溢出,xcf_load_tile_rle内会调用g_malloc(offset2 - offset)这就是原因。

修复

加一个:

1
if(offset2 < offset) exit(-1); 

Q^Q


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