CVE-2012-2836 in libexif(out-of-bounds read)CVE-2009-3895 haven't found
前期准备
测试用例
跟着教程来就行,不再赘述
Fuzzing
本次使用了 AFL++ 内置的 afl-clang-lto 所具备的链接时优化(Link-Time Optimization,LTO)功能来优化编译以得到更好的 fuzzing 效果,其比我们上次所用的 afl-clang-fast 更优秀
CC=afl-clang-lto ...
然后就可以开始 fuzzing 了
熟悉的界面
调试之前需要做一些准备
之前调试尝试跟踪变量的时候发现很多变量显示 ,给分析过程带来很大麻烦
这是因为编译器的优化会导致变量被优化掉,需要在 configure 前增加个参数降低优化程度
CFLAGS=“-O0 -g”
调试环境依然选择 VS Code + gdb
crash 1
#0 0x000000000022c56f in exif_get_sshort (buf=0x1004558e5 <error: Cannot access memory at address 0x1004558e5>, order=EXIF_BYTE_ORDER_MOTOROLA) at exif-utils.c:92
#1 exif_get_short (buf=0x1004558e5 <error: Cannot access memory at address 0x1004558e5>, order=EXIF_BYTE_ORDER_MOTOROLA) at exif-utils.c:104
#2 exif_data_load_data (data=0x4537b0, d_orig=<optimized out>, ds_orig=<optimized out>) at exif-data.c:819
#3 0x0000000000221886 in exif_loader_get_data (loader=<optimized out>) at /home/asuka/fuzzing101/fuzzing_libexif/libexif-libexif-0_6_14-release/libexif/exif-loader.c:387
#4 main (argc=<optimized out>, argc@entry=2, argv=<optimized out>, argv@entry=0x7fffffffdb28) at main.c:438
#5 0x00007ffff7cb9d90 in __libc_start_call_main (main=main@entry=0x21f100 <main>, argc=argc@entry=2, argv=argv@entry=0x7fffffffdb28) at ../sysdeps/nptl/libc_start_call_main.h:58
#6 0x00007ffff7cb9e40 in __libc_start_main_impl (main=0x21f100 <main>, argc=2, argv=0x7fffffffdb28, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdb18) at ../csu/libc-start.c:392
#7 0x000000000021a9f5 in _start ()
main.c
// ...
args = poptGetArgs (ctx);
if (args) {
while (*args) {
ExifLoader *l;
/*
* Try to read EXIF data from the file.
* If there is no EXIF data, exit.
*/
l = exif_loader_new ();
exif_loader_log (l, log);
exif_loader_write_file (l, *args);
ed = exif_loader_get_data (l); // here
// ...
exif-loader.c
exif_loader_get_data (ExifLoader *loader)
{
ExifData *ed;
if (!loader)
return NULL;
ed = exif_data_new_mem (loader->mem);
exif_data_log (ed, loader->log);
exif_data_load_data (ed, loader->buf, loader->bytes_read); // here in
return ed;
}
前面都是读取照片的 EXIF 信息,没什么问题
exif-data.c
// ...
/* IFD 0 offset */
offset = exif_get_long (d + 10, data->priv->order); // offset = 4294967295(-1)
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) { // check offset
return;
}
n = exif_get_short (d + 6 + offset, data->priv->order); // here in
// ...
到了这里发现 offset 的值是 0xffffffff,明显是不合理的
跟进看看发生了什么
exif-utils.c
ExifSLong
exif_get_slong (const unsigned char *b, ExifByteOrder order)
{
if (!b) return 0;
switch (order) {
case EXIF_BYTE_ORDER_MOTOROLA:
return ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]);
case EXIF_BYTE_ORDER_INTEL:
return ((b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]);
}
/* Won't be reached */
return (0);
}
此时 b 指向的地址是 0x44d8ea,看看内存信息
-exec x/20c 0x44d8e0
0x44d8e0: 69 'E' 120 'x' 105 'i' 102 'f' 0 '-exec x/20c 0x44d8e0
0x44d8e0: 69 'E' 120 'x' 105 'i' 102 'f' 0 '\000' 0 '\000' 77 'M' 77 'M'
0x44d8e8: 0 '\000' 42 '*' -1 '\377' -1 '\377' -1 '\377' -1 '\377' 0 '\000' 12 '\f'
0x44d8f0: 1 '\001' 0 '\000' 3 '\003' 0 '\000'
-exec x/a 0x44d8ea
0x44d8ea: 0x10c00ffffffff
0' 0 '-exec x/20c 0x44d8e0
0x44d8e0: 69 'E' 120 'x' 105 'i' 102 'f' 0 '\000' 0 '\000' 77 'M' 77 'M'
0x44d8e8: 0 '\000' 42 '*' -1 '\377' -1 '\377' -1 '\377' -1 '\377' 0 '\000' 12 '\f'
0x44d8f0: 1 '\001' 0 '\000' 3 '\003' 0 '\000'
-exec x/a 0x44d8ea
0x44d8ea: 0x10c00ffffffff
0' 77 'M' 77 'M'
0x44d8e8: 0 '-exec x/20c 0x44d8e0
0x44d8e0: 69 'E' 120 'x' 105 'i' 102 'f' 0 '\000' 0 '\000' 77 'M' 77 'M'
0x44d8e8: 0 '\000' 42 '*' -1 '\377' -1 '\377' -1 '\377' -1 '\377' 0 '\000' 12 '\f'
0x44d8f0: 1 '\001' 0 '\000' 3 '\003' 0 '\000'
-exec x/a 0x44d8ea
0x44d8ea: 0x10c00ffffffff
0' 42 '*' -1 '7' -1 '7' -1 '7' -1 '7' 0 '-exec x/20c 0x44d8e0
0x44d8e0: 69 'E' 120 'x' 105 'i' 102 'f' 0 '\000' 0 '\000' 77 'M' 77 'M'
0x44d8e8: 0 '\000' 42 '*' -1 '\377' -1 '\377' -1 '\377' -1 '\377' 0 '\000' 12 '\f'
0x44d8f0: 1 '\001' 0 '\000' 3 '\003' 0 '\000'
-exec x/a 0x44d8ea
0x44d8ea: 0x10c00ffffffff
0' 12 '\f'
0x44d8f0: 1 '-exec x/20c 0x44d8e0
0x44d8e0: 69 'E' 120 'x' 105 'i' 102 'f' 0 '\000' 0 '\000' 77 'M' 77 'M'
0x44d8e8: 0 '\000' 42 '*' -1 '\377' -1 '\377' -1 '\377' -1 '\377' 0 '\000' 12 '\f'
0x44d8f0: 1 '\001' 0 '\000' 3 '\003' 0 '\000'
-exec x/a 0x44d8ea
0x44d8ea: 0x10c00ffffffff
1' 0 '-exec x/20c 0x44d8e0
0x44d8e0: 69 'E' 120 'x' 105 'i' 102 'f' 0 '\000' 0 '\000' 77 'M' 77 'M'
0x44d8e8: 0 '\000' 42 '*' -1 '\377' -1 '\377' -1 '\377' -1 '\377' 0 '\000' 12 '\f'
0x44d8f0: 1 '\001' 0 '\000' 3 '\003' 0 '\000'
-exec x/a 0x44d8ea
0x44d8ea: 0x10c00ffffffff
0' 3 '-exec x/20c 0x44d8e0
0x44d8e0: 69 'E' 120 'x' 105 'i' 102 'f' 0 '\000' 0 '\000' 77 'M' 77 'M'
0x44d8e8: 0 '\000' 42 '*' -1 '\377' -1 '\377' -1 '\377' -1 '\377' 0 '\000' 12 '\f'
0x44d8f0: 1 '\001' 0 '\000' 3 '\003' 0 '\000'
-exec x/a 0x44d8ea
0x44d8ea: 0x10c00ffffffff
3' 0 '-exec x/20c 0x44d8e0
0x44d8e0: 69 'E' 120 'x' 105 'i' 102 'f' 0 '\000' 0 '\000' 77 'M' 77 'M'
0x44d8e8: 0 '\000' 42 '*' -1 '\377' -1 '\377' -1 '\377' -1 '\377' 0 '\000' 12 '\f'
0x44d8f0: 1 '\001' 0 '\000' 3 '\003' 0 '\000'
-exec x/a 0x44d8ea
0x44d8ea: 0x10c00ffffffff
0'
-exec x/a 0x44d8ea
0x44d8ea: 0x10c00ffffffff
就是 EXIF 后面的几个 0xff 引起了错误,看看这张图片的信息
然而原图的信息是这样的
另一个问题是:0xffffffff 是如何通过offset + 6 + 2 > ds
的?
ds 的定义如下,ds_orgi 就是 loader->bytes_read,大约是 1100 左右
void
exif_data_load_data (ExifData *data, const unsigned char *d_orig,
unsigned int ds_orig)
{
// ...
unsigned int ds = ds_orig;
而 offset + 6 + 2 = 4294967295 + 8 溢出后等于 7,所以只要 offset 的值在 [-8, -1] 时就能通过检查,非常巧妙
exif-utils.c
ExifShort
exif_get_short (const unsigned char *buf, ExifByteOrder order)
{
return (exif_get_sshort (buf, order) & 0xffff);
}
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]); // line:92 crash
case EXIF_BYTE_ORDER_INTEL:
return ((buf[1] << 8) | buf[0]);
}
/* Won't be reached */
return (0);
}
数组就是在这里越界了
所以这个漏洞是读取 offset 时没有做好边界检查导致的
复现非常容易,只要把 0x002a 后面的四个字节的值按图片的字节序(MOTOROLA,MM 和 INTEL,II)改到 [-8, -1] 之间即可
修复也非常容易,加个对 offset 单独的检查条件即可
/* IFD 1 offset */
- if (offset + 6 + 2 > ds) {
+ if (offset > 0xffffff00 || offset + 6 + 2 > ds) {
return;
}
crash 2
#0 0x0000000000228f2a in exif_get_slong (b=<optimized out>, order=EXIF_BYTE_ORDER_MOTOROLA) at exif-utils.c:135
#1 exif_get_long (buf=<optimized out>, order=EXIF_BYTE_ORDER_MOTOROLA) at exif-utils.c:167
#2 exif_entry_fix (e=0x455840) at exif-entry.c:193
#3 fix_func (e=0x455840, data=0x0) at exif-content.c:231
#4 exif_content_foreach_entry (content=0x4538e0, data=0x0, func=<optimized out>) at exif-content.c:200
#5 exif_content_fix (c=<optimized out>) at exif-content.c:247
#6 0x000000000022f692 in fix_func (c=<optimized out>, data=<optimized out>) at exif-data.c:1159
#7 0x000000000022d748 in exif_data_foreach_content (data=0x4537b0, user_data=0x0, func=<optimized out>) at exif-data.c:1031
#8 exif_data_fix (d=0x4537b0) at exif-data.c:1176
#9 exif_data_load_data (data=0x4537b0, d_orig=<optimized out>, ds_orig=<optimized out>) at exif-data.c:871
#10 0x0000000000221886 in exif_loader_get_data (loader=<optimized out>) at /home/asuka/fuzzing101/fuzzing_libexif/libexif-libexif-0_6_14-release/libexif/exif-loader.c:387
#11 main (argc=<optimized out>, argc@entry=2, argv=<optimized out>, argv@entry=0x7fffffffdb28) at main.c:438
#12 0x00007ffff7cb9d90 in __libc_start_call_main (main=main@entry=0x21f100 <main>, argc=argc@entry=2, argv=argv@entry=0x7fffffffdb28) at ../sysdeps/nptl/libc_start_call_main.h:58
#13 0x00007ffff7cb9e40 in __libc_start_main_impl (main=0x21f100 <main>, argc=2, argv=0x7fffffffdb28, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdb18) at ../csu/libc-start.c:392
#14 0x000000000021a9f5 in _start ()
第二个 crash 没能分析出来,网上说和第一个相似,经过实际调试后我认为不尽然
crash 3
#0 0x00007ffff7e3f586 in __memmove_evex_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:874
#1 0x000000000022f395 in exif_data_load_data_thumbnail (data=0x4537b0, d=0x4558e6 "II*", ds=7204, offset=2036, size=4294967295) at exif-data.c:292
#2 exif_data_load_data_content (data=<optimized out>, ifd=<optimized out>, d=<optimized out>, ds=<optimized out>, offset=794, recursion_depth=<optimized out>) at exif-data.c:381
#3 0x000000000022c6d2 in exif_data_load_data (data=0x4537b0, d_orig=<optimized out>, ds_orig=<optimized out>) at exif-data.c:835
#4 0x0000000000221886 in exif_loader_get_data (loader=<optimized out>) at /home/asuka/fuzzing101/fuzzing_libexif/libexif-libexif-0_6_14-release/libexif/exif-loader.c:387
#5 main (argc=<optimized out>, argc@entry=2, argv=<optimized out>, argv@entry=0x7fffffffdb28) at main.c:438
#6 0x00007ffff7cb9d90 in __libc_start_call_main (main=main@entry=0x21f100 <main>, argc=argc@entry=2, argv=argv@entry=0x7fffffffdb28) at ../sysdeps/nptl/libc_start_call_main.h:58
#7 0x00007ffff7cb9e40 in __libc_start_main_impl (main=0x21f100 <main>, argc=2, argv=0x7fffffffdb28, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdb18) at ../csu/libc-start.c:392
#8 0x000000000021a9f5 in _start ()
前略,exif-data.c
:
static void
exif_data_load_data_content (ExifData *data, ExifIfd ifd,
const unsigned char *d,
unsigned int ds, unsigned int offset, unsigned int recursion_depth)
{
ExifLong o, thumbnail_offset = 0, thumbnail_length = 0;
ExifShort n;
ExifEntry *entry;
unsigned int i;
ExifTag tag;
if (!data || !data->priv)
return;
if ((ifd < 0) || (ifd >= EXIF_IFD_COUNT))
return;
if (recursion_depth > 150) {
exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "ExifData",
"Deep recursion detected!");
return;
}
/* Read the number of entries */
if (offset >= ds - 1)
return;
n = exif_get_short (d + offset, data->priv->order);
exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
"Loading %i entries...", n);
offset += 2;
/* Check if we have enough data. */
if (offset + 12 * n > ds)
n = (ds - offset) / 12;
for (i = 0; i < n; i++) {
tag = exif_get_short (d + offset + 12 * i, data->priv->order);
switch (tag) {
case EXIF_TAG_EXIF_IFD_POINTER:
case EXIF_TAG_GPS_INFO_IFD_POINTER:
case EXIF_TAG_INTEROPERABILITY_IFD_POINTER:
case EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
case EXIF_TAG_JPEG_INTERCHANGE_FORMAT:
o = exif_get_long (d + offset + 12 * i + 8, // o = 4294967295
data->priv->order);
switch (tag) {
// ...
case EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
thumbnail_length = o;
if (thumbnail_offset && thumbnail_length)
exif_data_load_data_thumbnail (data, d,
ds, thumbnail_offset, // ds = 7204, thumbnail_offset=2036
thumbnail_length); // thumbnail_length = o = 4294967295
// ...
static void
exif_data_load_data_thumbnail (ExifData *data, const unsigned char *d,
unsigned int ds, ExifLong offset, ExifLong size)
{
if (ds < offset + size) { // check
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);
data->size = size;
data->data = exif_data_alloc (data, data->size);
if (!data->data)
return;
memcpy (data->data, d + offset, data->size); // crash
}
offset + 4294967295 溢出为 (offset - 1) < ds,故而若不检查 size,只需要 offset < ds + 1 就能触发漏洞
至于这个巨大的 size 是怎么来的没分析出来,可能是前面读 data 的时候越界了
修复方案如下
- if (ds < offset + size) {
+ if ((uint64_t)ds < (uint64_t)offset + (uint64_t)size) {
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;
}
hangs
hangs 是指由于特殊原因导致超时(如死循环、无限递归等)的用例
#0 0x0000000000220378 in exif_loader_write (eld=<optimized out>, buf=0x7fffffffd210 "", len=0) at /home/asuka/fuzzing101/fuzzing_libexif/libexif-libexif-0_6_14-release/libexif/exif-loader.c:264
#1 exif_loader_write_file (l=<optimized out>, path=<optimized out>) at /home/asuka/fuzzing101/fuzzing_libexif/libexif-libexif-0_6_14-release/libexif/exif-loader.c:120
#2 main (argc=<optimized out>, argc@entry=2, argv=<optimized out>, argv@entry=0x7fffffffdb38) at main.c:437
#3 0x00007ffff7cb9d90 in __libc_start_call_main (main=main@entry=0x21f100 <main>, argc=argc@entry=2, argv=argv@entry=0x7fffffffdb38) at ../sysdeps/nptl/libc_start_call_main.h:58
#4 0x00007ffff7cb9e40 in __libc_start_main_impl (main=0x21f100 <main>, argc=2, argv=0x7fffffffdb38, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdb28) at ../csu/libc-start.c:392
#5 0x000000000021a9f5 in _start ()
先来看看这个只有两行的神奇玩意,这到底是怎么生成出来的()
导致这个 hangs 的原因是无限递归
前略,exif-loader.c
:
void
exif_loader_write_file (ExifLoader *l, const char *path)
{
FILE *f;
int size;
unsigned char data[1024];
if (!l)
return;
f = fopen (path, "rb");
if (!f) {
exif_log (l->log, EXIF_LOG_CODE_NONE, "ExifLoader",
_("The file '%s' could not be opened."), path);
return;
}
while (1) {
size = fread (data, 1, sizeof (data), f);
if (size <= 0) // check size
break;
if (!exif_loader_write (l, data, size)) // here in
break;
}
fclose (f);
}
unsigned char
exif_loader_write (ExifLoader *eld, unsigned char *buf, unsigned int len)
{
// ...
/*
* First fill the small buffer. Only continue if the buffer
* is filled. Note that EXIF data contains at least 12 bytes.
*/
// ...
/*
* If we reach this point, the buffer has not been big enough
* to read all data we need. Fill it with new data.
*/
eld->b_len = 0;
return exif_loader_write (eld, buf, len); // hangs
}
流程如下
main
函数调用exif_loader_write_file
读取文件exif_loader_write_file
打开一个文件描述符,从文件内每次读 1024 字节进 buffer,并调用exif_loader_write
把 buffer 内的信息导入ExifLoader
exif_loader_write
函数首先经过一个状态机确保完全读入 exif 信息,如果抵达函数的末尾则证明 buffer 没有大到能存完所有数据,于是递归向 buffer 填充新的数据
观察到递归函数的入口前有判定 len 是否为 0,说明开发者不希望 len 为 0
第一次调用函数时 len 值为 32,但经过跟踪发现 len 在递归的过程中逐渐减为 0,但函数内部没有对 len 进行任何判定,说明状态机对状态的判定遗漏导致了无限递归
官方的修复方案
/*
* If we reach this point, the buffer has not been big enough
* to read all data we need. Fill it with new data.
*/
+ if (!len)
+ return 1;
eld->b_len = 0;
return exif_loader_write (eld, buf, len); // hangs
}
CVE-2009-3895
这个漏洞并没有找到,求证后发现原因是此漏洞并不影响我们 fuzzing 的版本,如果想了解可以参看大佬的文章
Comments NOTHING