编辑
2023-06-15
CVE
00
请注意,本文编写于 658 天前,最后修改于 647 天前,其中某些信息可能已经过时。

目录

llibexif 缓冲区溢出 CVE-2009-3895漏洞分析
简述
环境配置
漏洞复现
漏洞分析

😀

llibexif 缓冲区溢出 CVE-2009-3895漏洞分析


简述

libexif 0.6.18 中 libexif/exif-entry.c 中的 exif_entry_fix 函数中基于堆的缓冲区溢出允许远程攻击者导致拒绝服务或可能通过无效的 EXIF 图像执行任意代码

环境配置

下载对应版本源码解压

bash
$ https://github.com/libexif/libexif/archive/refs/tags/libexif-0_6_18-release.zip $ tar -xzvf libexif-0_6_18-release.tar.gz

安装基本依赖

bash
$ sudo apt-get install autopoint libtool gettext libpopt-dev

自动构建configure 并进行相关配置

bash
$ autoreconf -fvi $ ./configure --enable-shared=no --prefix="$HOME/install/"

编译

bash
$ make $ make install

因为libcexif只是一个库 需要相关的调用才能实现功能 这里选择exif作为其接口文件

bash
$ wget https://github.com/libexif/exif/archive/refs/tags/exif-0_6_18-release.tar.gz $ tar -xxvf ./exif-0_6_18-release.tar.gz

自动构建configure 并进行相关配置

bash
$ autoreconf -fvi $ ./configure --enable-share=no --prefix="$HOME/install" PKG_CONFIG_PATH="$HOME/install/lib/pkgconfig"

编译

bash
$ make $ make install

如果一切正常 可正常执行exif

bash
$ ~/install/bin/exif

编译fuzz版本libexif和exif

此处afl-clang-lto的环境没有配好 选择afl-clang-fast进行编译

bash
$ cd libexif-libexif-0_6_18-release/ $ make clean $ export LLVM_CONFIG="llvm-config-11" $ CC=afl-clang-fast ./configure --enable-shared=no --prefix="$HOME/install/" $ make $ make install
bash
$ cd exif-exif-0_6_18-release/ $ make clean $ export LLVM_CONFIG="llvm-config-11" $ CC=afl-clang-fast ./configure --enable-shared=no --prefix="$HOME/install/" PKG_CONFIG_PATH=$HOME/install/lib/pkgconfig $ make $ make install

afl-clang-ltoafl-clang-fast 都是用于基于 LLVM 的 American Fuzzy Lop (AFL) 语法分析器的编译器插件,主要用于对 C/C++ 代码进行模糊测试(fuzz testing)。它们之间的区别主要体现在以下几个方面:

  1. 比较:afl-clang-lto的优点在于支持更高效的LLVM链接时间优化(LTO),因为它可以在全局层次上优化整个代码库,而不仅仅是每个单独的编译单元。afl-clang-fast则专注于最小化模糊测试期间的运行时间损耗。
  2. 编译速度:由于 LTO 是一个非常耗费时间和资源的过程,使用 afl-clang-lto 编译代码会比使用 afl-clang-fast 更慢。afl-clang-fast 能够提供比 LTO 更快的编译速度,但是会导致一些对于特定目标程序的性能影响。
  3. 运行时的内存消耗:由于 afl-clang-lto 生成的二进制文件包含了更多的优化信息和链接代码,因此在运行时需要更多的内存空间。相反,afl-clang-fast 生成的二进制文件通常比较小,并且需要的内存空间也相应更少。
  4. 支持:afl-clang-fast 是官方支持的编译器插件,而 afl-clang-lto 是非官方的第三方产品。因此,后者可能会存在支持问题或更少的文档资料。

总之,afl-clang-ltoafl-clang-fast 在一些性能、优化和资源消耗上存在差异,需要根据具体情况进行选择。如果需要更好的性能和更多的代码优化,则可以使用 afl-clang-lto,而如果需要更快的编译速度,则 afl-clang-fast 可能更适合你的需求。

下载测试样例

bash
$ wget https://github.com/ianare/exif-samples/archive/refs/heads/master.zip $ unzip master.zip

漏洞复现

进行fuzz

bash
afl-fuzz -i ./example/exif-samples-master/jpg/ -o ./out/ -s 123 -- /home/du4t/install/bin/exif @@

测试崩溃 成功复现崩溃 且崩溃在realloc

bash
$ ./install/bin/exif ./out/default/crashes/id:000000,sig:06,src:000582,time:418814,execs:178538,op:havoc,rep:16

漏洞分析

exif载入gdb

![image-20230614221747008](/Users/du4t/Library/Application Support/typora-user-images/image-20230614221747008.png)

不打断点直接run 复现崩溃

使用bt查看栈帧 可以看到调用链是main -> exif_loader_data -> exif_data_fix -> exif_data_foreach_content -> fix_func -> exif_content_fix -> exif_entry_fix -> exif_entry_realloc 崩溃调用链符合文档描述

静态分析需要将libexif-libexif-0_6_18-release/libexif复制到exif-exif-0_6_18-release下才能正常寻找引用

main函数比较简单 在调用exif_loader_log之前的工作主要就是进行了参数解析 以及对参数进行基础的校验 防止非法参数 然后新建了两个结构体ExifLoaderExifLog 并且将图片数据读入ExifLoader 最后将其作为参数传递进exif_loader_log

c
int main (int argc, const char **argv) { /* POPT_ARG_NONE needs an int, not char! */ poptContext ctx; const char **args; ... ExifData *ed; ExifLog *log = NULL; char fout[1024] = {0, }; ctx = poptGetContext (PACKAGE, argc, argv, options, 0); // 参数解析 poptSetOtherOptionHelp (ctx, _("[OPTION...] file")); while (poptGetNextOpt (ctx) > 0) ; p.width = MIN(MAX_WIDTH, MAX(MIN_WIDTH, p.width)); log = exif_log_new (); // 新建一个ExifLog结构体 并且将重封装之后的函数表写入ExifLog结构体 exif_log_set_func (log, log_func, &log_arg); // 一些基础检验 if (ifd_string) { p.ifd = exif_ifd_from_string (ifd_string); if ((p.ifd < EXIF_IFD_0) || (p.ifd >= EXIF_IFD_COUNT) || !exif_ifd_get_name (p.ifd)) { exif_log (log, -1, "exif", _("Invalid IFD '%s'. Valid IFDs are " "'0', '1', 'EXIF', 'GPS', and " "'Interoperability'."), ifd_string); return 1; } ... } } while (*args) { ExifLoader *l; ... l = exif_loader_new (); // 新分配一个ExifLoader结构体 然后将重封装之后的函数表写入新的ExifLoader结构体 exif_loader_log (l, log); if (create_exif) log_arg.ignore_corrupted = 1; exif_loader_write_file (l, *args); log_arg.ignore_corrupted = 0; if (!log_arg.corrupted) create_exif = 0; if (no_fixup) /* Override the default conversion options */ ed = exif_get_data_opts(l, log, 0, EXIF_DATA_TYPE_UNKNOWN); else ed = exif_loader_get_data(l); // here ... } }

其中需要关注下将照片数据读入的函数exif_loader_write_file 在此函数内对ExifLoader结构体进行了初始化 可以看到在此函数内仅仅是将图片数据读入到data变量中 然后将ExifLoader结构体 数据data 数据长度size传递进函数exif_loader_write 此时的ExifLoader内包含的数据如下

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); // 将数据读入data if (size <= 0) break; if (!exif_loader_write (l, data, size)) break; } fclose (f); }

![image-20230615114004596](/Users/du4t/Library/Application Support/typora-user-images/image-20230615114004596.png)

关于exif_loader_write具体操作不需要深究 其主要是对图片数据进行扫描 寻找EXIF数据 然后将其写入到ExifLoader

exif_loader_get_data流程很简单 就是为读取数据做准备 新建了一个ExifData结构体 并对其进行配置 然后调用exif_data_load_data开始读取数据工作

c
ExifData * exif_loader_get_data (ExifLoader *loader) { ExifData *ed; if (!loader || (loader->data_format == EL_DATA_FORMAT_UNKNOWN) || !loader->bytes_read) return NULL; ed = exif_data_new_mem (loader->mem); // 新建一个ExifData结构体 exif_data_log (ed, loader->log); exif_data_load_data (ed, loader->buf, loader->bytes_read); // here return ed; }

exif_data_load_data就比较复杂了 从照片数据中寻找各个exif相关的数据 比如IFD Makernote等等

c
void exif_data_load_data (ExifData *data, const unsigned char *d_orig, unsigned int ds_orig) // data loader->buf loader->bytes_read { unsigned int l; ExifLong offset; ExifShort n; const unsigned char *d = d_orig; // d = loader->buf unsigned int ds = ds_orig, len; // ds = loader->bytes_read = 991 if (!data || !data->priv || !d || !ds) return; exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "Parsing %i byte(s) EXIF data...\n", ds); /* * It can be that the data starts with the EXIF header. If it does * not, search the EXIF marker. */ if (ds < 6) { LOG_TOO_SMALL; return; } if (!memcmp (d, ExifHeader, 6)) { exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "Found EXIF header."); } else { while (1) { while ((d[0] == 0xff) && ds) { d++; ds--; } /* JPEG_MARKER_SOI */ if (d[0] == JPEG_MARKER_SOI) { d++; ds--; continue; } /* JPEG_MARKER_APP0 */ if (d[0] == JPEG_MARKER_APP0) { d++; ds--; l = (d[0] << 8) | d[1]; if (l > ds) return; d += l; ds -= l; continue; } /* JPEG_MARKER_APP1 */ if (d[0] == JPEG_MARKER_APP1) break; /* Unknown marker or data. Give up. */ exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "ExifData", _("EXIF marker not found.")); return; } d++; ds--; if (ds < 2) { LOG_TOO_SMALL; return; } len = (d[0] << 8) | d[1]; exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "We have to deal with %i byte(s) of EXIF data.", len); d += 2; ds -= 2; } /* * Verify the exif header * (offset 2, length 6). */ if (ds < 6) { LOG_TOO_SMALL; return; } if (memcmp (d, ExifHeader, 6)) { // 验证MaigcNum exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "ExifData", _("EXIF header not found.")); return; } exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "Found EXIF header."); /* Byte order (offset 6, length 2) */ if (ds < 14) return; if (!memcmp (d + 6, "II", 2)) data->priv->order = EXIF_BYTE_ORDER_INTEL; else if (!memcmp (d + 6, "MM", 2)) data->priv->order = EXIF_BYTE_ORDER_MOTOROLA; // 识别类型 else { exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "ExifData", _("Unknown encoding.")); return; } /* 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 ID 和 TIFF 头共占据了 8 个字节,其中前 4 个字节是 "Exif" 的 ASCII 码,后 4 个字节是 TIFF 文件头部(Image File Header)。TIFF 文件头部的第 4 个字节是偏移地址,指向了第一个 IFD 的位置,该 IFD 就是 IFD0。因此,通过解析 Exif 头部,我们可以找到 IFD0 的偏移地址,然后跳转到该位置,并读取 IFD0 中的相关信息。 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); // 将读出的数据写入data->priv ... /* * If we got an EXIF_TAG_MAKER_NOTE, try to interpret it. Some * cameras use pointers in the maker note tag that point to the * space between IFDs. Here is the only place where we have access * to that data. */ switch (exif_data_get_type_maker_note (data)) { // 寻找Makernote 很显然我们fuzz使用的样例不可能找到 case EXIF_DATA_TYPE_MAKER_NOTE_OLYMPUS: case EXIF_DATA_TYPE_MAKER_NOTE_NIKON: data->priv->md = exif_mnote_data_olympus_new (data->priv->mem); break; case EXIF_DATA_TYPE_MAKER_NOTE_PENTAX: case EXIF_DATA_TYPE_MAKER_NOTE_CASIO: data->priv->md = exif_mnote_data_pentax_new (data->priv->mem); break; case EXIF_DATA_TYPE_MAKER_NOTE_CANON: data->priv->md = exif_mnote_data_canon_new (data->priv->mem, data->priv->options); break; case EXIF_DATA_TYPE_MAKER_NOTE_FUJI: data->priv->md = exif_mnote_data_fuji_new (data->priv->mem); break; default: break; } ... if (data->priv->options & EXIF_DATA_OPTION_FOLLOW_SPECIFICATION) exif_data_fix (data); // here }

c
void exif_data_fix (ExifData *d) { exif_data_foreach_content (d, fix_func, NULL); }

转入data->fix_func 参数为func(data->ifd[i],NULL) 注意这里是从ifd[0]开始传的

c
void exif_data_foreach_content (ExifData *data, ExifDataForeachContentFunc func, void *user_data) { unsigned int i; if (!data || !func) return; for (i = 0; i < EXIF_IFD_COUNT; i++) func (data->ifd[i], user_data); }

从函数名也能看出来exif_content_get_ifd就是根据内容反查是哪个ifd的 很显然第一次调用的时候是EXIF_IFD_0 那么就是直接调用exif_content_fix

c
static void fix_func (ExifContent *c, void *UNUSED(data)) { switch (exif_content_get_ifd (c)) { case EXIF_IFD_1: if (c->parent->data) exif_content_fix (c); else { exif_log (c->parent->priv->log, EXIF_LOG_CODE_DEBUG, "exif-data", "No thumbnail but entries on thumbnail. These entries have been " "removed."); while (c->count) { unsigned int cnt = c->count; exif_content_remove_entry (c, c->entries[c->count - 1]); if (cnt == c->count) { /* safety net */ exif_log (c->parent->priv->log, EXIF_LOG_CODE_DEBUG, "exif-data", "failed to remove last entry from entries."); c->count--; } } } break; default: exif_content_fix (c); } }

exif_entry_fix根据不同的IFD TAG来进行不同的处理 以提取数据

c
void exif_entry_fix (ExifEntry *e) { unsigned int i; ExifByteOrder o; ExifRational r; ExifSRational sr; if (!e || !e->priv) return; switch (e->tag) { /* These tags all need to be of format SHORT. */ case EXIF_TAG_YCBCR_SUB_SAMPLING: case EXIF_TAG_SUBJECT_AREA: case EXIF_TAG_COLOR_SPACE: case EXIF_TAG_PLANAR_CONFIGURATION: case EXIF_TAG_SENSING_METHOD: case EXIF_TAG_ORIENTATION: case EXIF_TAG_YCBCR_POSITIONING: case EXIF_TAG_PHOTOMETRIC_INTERPRETATION: case EXIF_TAG_CUSTOM_RENDERED: case EXIF_TAG_EXPOSURE_MODE: case EXIF_TAG_WHITE_BALANCE: case EXIF_TAG_SCENE_CAPTURE_TYPE: case EXIF_TAG_GAIN_CONTROL: case EXIF_TAG_SATURATION: case EXIF_TAG_CONTRAST: case EXIF_TAG_SHARPNESS: case EXIF_TAG_ISO_SPEED_RATINGS: switch (e->format) { case EXIF_FORMAT_LONG: case EXIF_FORMAT_SLONG: case EXIF_FORMAT_BYTE: case EXIF_FORMAT_SBYTE: case EXIF_FORMAT_SSHORT: if (!e->parent || !e->parent->parent) break; exif_entry_log (e, EXIF_LOG_CODE_DEBUG, _("Tag '%s' was of format '%s' (which is " "against specification) and has been " "changed to format '%s'."), exif_tag_get_name_in_ifd(e->tag, exif_entry_get_ifd(e)), exif_format_get_name (e->format), exif_format_get_name (EXIF_FORMAT_SHORT)); o = exif_data_get_byte_order (e->parent->parent); for (i = 0; i < e->components; i++) exif_set_short ( e->data + i * exif_format_get_size ( EXIF_FORMAT_SHORT), o, exif_get_short_convert ( e->data + i * exif_format_get_size (e->format), e->format, o)); e->format = EXIF_FORMAT_SHORT; e->size = e->components * exif_format_get_size (e->format); e->data = exif_entry_realloc (e, e->data, e->size); break; case EXIF_FORMAT_SHORT: /* No conversion necessary */ break; default: exif_entry_log (e, EXIF_LOG_CODE_CORRUPT_DATA, _("Tag '%s' is of format '%s' (which is " "against specification) but cannot be changed " "to format '%s'."), exif_tag_get_name_in_ifd(e->tag, exif_entry_get_ifd(e)), exif_format_get_name (e->format), exif_format_get_name (EXIF_FORMAT_SHORT)); break; } break; /* All these tags need to be of format 'Rational'. */ case EXIF_TAG_FNUMBER: case EXIF_TAG_APERTURE_VALUE: case EXIF_TAG_EXPOSURE_TIME: case EXIF_TAG_FOCAL_LENGTH: switch (e->format) { case EXIF_FORMAT_SRATIONAL: if (!e->parent || !e->parent->parent) break; o = exif_data_get_byte_order (e->parent->parent); for (i = 0; i < e->components; i++) { sr = exif_get_srational (e->data + i * exif_format_get_size ( EXIF_FORMAT_SRATIONAL), o); r.numerator = (ExifLong) sr.numerator; r.denominator = (ExifLong) sr.denominator; exif_set_rational (e->data + i * exif_format_get_size ( EXIF_FORMAT_RATIONAL), o, r); } e->format = EXIF_FORMAT_RATIONAL; exif_entry_log (e, EXIF_LOG_CODE_DEBUG, _("Tag '%s' was of format '%s' (which is " "against specification) and has been " "changed to format '%s'."), exif_tag_get_name_in_ifd(e->tag, exif_entry_get_ifd(e)), exif_format_get_name (EXIF_FORMAT_SRATIONAL), exif_format_get_name (EXIF_FORMAT_RATIONAL)); break; default: break; } break; /* All these tags need to be of format 'SRational'. */ case EXIF_TAG_EXPOSURE_BIAS_VALUE: case EXIF_TAG_BRIGHTNESS_VALUE: case EXIF_TAG_SHUTTER_SPEED_VALUE: switch (e->format) { case EXIF_FORMAT_RATIONAL: if (!e->parent || !e->parent->parent) break; o = exif_data_get_byte_order (e->parent->parent); for (i = 0; i < e->components; i++) { r = exif_get_rational (e->data + i * exif_format_get_size ( EXIF_FORMAT_RATIONAL), o); sr.numerator = (ExifLong) r.numerator; sr.denominator = (ExifLong) r.denominator; exif_set_srational (e->data + i * exif_format_get_size ( EXIF_FORMAT_SRATIONAL), o, sr); } e->format = EXIF_FORMAT_SRATIONAL; exif_entry_log (e, EXIF_LOG_CODE_DEBUG, _("Tag '%s' was of format '%s' (which is " "against specification) and has been " "changed to format '%s'."), exif_tag_get_name_in_ifd(e->tag, exif_entry_get_ifd(e)), exif_format_get_name (EXIF_FORMAT_RATIONAL), exif_format_get_name (EXIF_FORMAT_SRATIONAL)); break; default: break; } break; case EXIF_TAG_USER_COMMENT: /* Format needs to be UNDEFINED. */ if (e->format != EXIF_FORMAT_UNDEFINED) { exif_entry_log (e, EXIF_LOG_CODE_DEBUG, _("Tag 'UserComment' had invalid format '%s'. " "Format has been set to 'undefined'."), exif_format_get_name (e->format)); e->format = EXIF_FORMAT_UNDEFINED; } /* Some packages like Canon ZoomBrowser EX 4.5 store only one zero byte followed by 7 bytes of rubbish */ if ((e->size >= 8) && (e->data[0] == 0)) { memcpy(e->data, "\0\0\0\0\0\0\0\0", 8); } /* There need to be at least 8 bytes. */ if (e->size < 8) { e->data = exif_entry_realloc (e, e->data, 8 + e->size); if (!e->data) { e->size = 0; e->components = 0; return; } /* Assume ASCII */ 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.")); break; } /* * If the first 8 bytes are empty and real data starts * afterwards, let's assume ASCII and claim the 8 first * bytes for the format specifyer. */ for (i = 0; (i < e->size) && !e->data[i]; i++); if (!i) for ( ; (i < e->size) && (e->data[i] == ' '); i++); if ((i >= 8) && (i < e->size)) { exif_entry_log (e, EXIF_LOG_CODE_DEBUG, _("Tag 'UserComment' is not empty but does not " "start with a format identifier. " "This has been fixed.")); memcpy (e->data, "ASCII\0\0\0", 8); break; } /* * First 8 bytes need to follow the specification. If they don't, * assume ASCII. */ 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); if (!e->data) { e->size = 0; e->components = 0; break; } /* Assume ASCII */ 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' did not start with a " "format identifier. This has been fixed.")); break; } break; default: break; } }

经过阅读源码 很明显如果程序想要流向exif_entry_realloc有三种情况 经过验证为第一种路径造成的崩溃

  • e->tag 和 e->format满足这一堆其中一种
  • e->tag == EXIF_TAG_USER_COMMENT && e->size < 8
  • e->tag == EXIF_TAG_USER_COMMENT && e->data 不符合条件

exif_entry_realloc本质上就是对realloc的再封装 调用之后发现realloc抛出realloc(): invalid next size错误 明显是下一个堆块被破坏 但是经过查看 exif_entry_realloc()及其下属的函数没有对d_orig也就是e->data有读写操作 证明溢出操作在上级

c
static void * exif_entry_realloc (ExifEntry *e, void *d_orig, unsigned int i) { void *d; ExifLog *l = NULL; if (!e || !e->priv) return NULL; if (!i) { exif_mem_free (e->priv->mem, d_orig); return NULL; } d = exif_mem_realloc (e->priv->mem, d_orig, i); if (d) return d; if (e->parent && e->parent->parent) l = exif_data_get_log (e->parent->parent); EXIF_LOG_NO_MEMORY (l, "ExifEntry", i); return NULL; }

回到上级函数exif_entry_fix中 执行到崩溃前最后一轮 在函数开始执行前可以发现e->data及其相邻堆块结构正常 即确定溢出漏洞存在于exif_entry_fix

开始循环前 其结构依然正常

循环结束后其结构遭到破坏 确定溢出点在循环处

将循环单拎出来看

c
for (i = 0; i < e->components; i++) exif_set_short(e->data + i * exif_format_get_size(EXIF_FORMAT_SHORT), o, exif_get_short_convert (e->data + i * exif_format_get_size (e->format), e->format, o) );

主函数exif_set_short 测试样例符合EXIF_BYTE_ORDER_MOTOROLAe->data + i * exif_format_get_size(EXIF_FORMAT_SHORT) 的位置进行赋值 将其设置为exif_get_short_convert (e->data + i * exif_format_get_size (e->format), e->format, o)相关的值

c
void exif_set_short (unsigned char *b, ExifByteOrder order, ExifShort value) { exif_set_sshort (b, order, value); } void exif_set_sshort (unsigned char *b, ExifByteOrder order, ExifSShort value) { if (!b) return; switch (order) { case EXIF_BYTE_ORDER_MOTOROLA: b[0] = (unsigned char) (value >> 8); b[1] = (unsigned char) value; break; case EXIF_BYTE_ORDER_INTEL: b[0] = (unsigned char) value; b[1] = (unsigned char) (value >> 8); break; } }

经过静态分析exif_format_get_size(EXIF_FORMAT_SHORT)的返回值为2

c
static const struct { ExifFormat format; const char *name; unsigned char size; } ExifFormatTable[] = { ... {EXIF_FORMAT_SHORT, N_("Short"), 2}, ... }; unsigned char exif_format_get_size (ExifFormat format) { unsigned int i; for (i = 0; ExifFormatTable[i].size; i++) if (ExifFormatTable[i].format == format) return ExifFormatTable[i].size; return 0; }

后续经过静态分析exif_get_short_convert其参数为exif_get_short_convert(e->data + i * 1, EXIF_FORMAT_BYTE, o); 则其返回值为e->data + i

c
static inline ExifShort exif_get_short_convert (const unsigned char *buf, ExifFormat format, ExifByteOrder order) { switch (format) { case EXIF_FORMAT_LONG: return (ExifShort) exif_get_long (buf, order); case EXIF_FORMAT_SLONG: return (ExifShort) exif_get_slong (buf, order); case EXIF_FORMAT_SHORT: return (ExifShort) exif_get_short (buf, order); case EXIF_FORMAT_SSHORT: return (ExifShort) exif_get_sshort (buf, order); case EXIF_FORMAT_BYTE: case EXIF_FORMAT_SBYTE: return (ExifShort) buf[0]; default: /* Unsupported type */ return (ExifShort) 0; } }

所以主函数exif_set_short其参数为exif_set_short(e->data + i * 2, EXIF_BYTE_ORDER_MOTOROLA, e->data + i) 感觉没有什么大问题 写入都是小写入一次写入只写入了两个字节 那么问题只能出在循环次数上

c
for (i = 0; i < e->components; i++) exif_set_short(e->data + i * exif_format_get_size(EXIF_FORMAT_SHORT), o, exif_get_short_convert (e->data + i * exif_format_get_size (e->format), e->format, o) );

循环次数由e->components来控制 可以看到此时的e->components == 257 那么当最大循环次数时调用的就是exif_set_short(e->data + 514, EXIF_BYTE_ORDER_MOTOROLA, e->data + 257) 即向0x5555559d2bf0+0x202处写入0x5555559d2bf0+0x101相关的数据 但是e->data相关堆只有0x110大小 显然出现堆溢出 造成覆写后续堆块的chunk_size 导致realloc抛出异常

那现在的目标就转向这个e->components是从哪来的了 如果是用户输入相关 那么就代表此漏洞可控 最终追到函数exif_data_load_data_entry 通过调试确定有调用行为

c
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); ... }

通过文件比对确定数据来自输入的文件 证明e->components可控

进一步定位 确认components取自0x8E字节处

![image-20230615185713867](/Users/du4t/Library/Application Support/typora-user-images/image-20230615185713867.png)

本文作者:Du4t

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!