😀
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-lto
和afl-clang-fast
都是用于基于 LLVM 的 American Fuzzy Lop (AFL) 语法分析器的编译器插件,主要用于对 C/C++ 代码进行模糊测试(fuzz testing)。它们之间的区别主要体现在以下几个方面:
- 比较:
afl-clang-lto
的优点在于支持更高效的LLVM链接时间优化(LTO),因为它可以在全局层次上优化整个代码库,而不仅仅是每个单独的编译单元。afl-clang-fast
则专注于最小化模糊测试期间的运行时间损耗。- 编译速度:由于 LTO 是一个非常耗费时间和资源的过程,使用
afl-clang-lto
编译代码会比使用afl-clang-fast
更慢。afl-clang-fast
能够提供比 LTO 更快的编译速度,但是会导致一些对于特定目标程序的性能影响。- 运行时的内存消耗:由于
afl-clang-lto
生成的二进制文件包含了更多的优化信息和链接代码,因此在运行时需要更多的内存空间。相反,afl-clang-fast
生成的二进制文件通常比较小,并且需要的内存空间也相应更少。- 支持:
afl-clang-fast
是官方支持的编译器插件,而afl-clang-lto
是非官方的第三方产品。因此,后者可能会存在支持问题或更少的文档资料。总之,
afl-clang-lto
和afl-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
bashafl-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

不打断点直接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
之前的工作主要就是进行了参数解析 以及对参数进行基础的校验 防止非法参数 然后新建了两个结构体ExifLoader
和ExifLog
并且将图片数据读入ExifLoader
最后将其作为参数传递进exif_loader_log
cint
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
内包含的数据如下
cvoid
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);
}

关于exif_loader_write
具体操作不需要深究 其主要是对图片数据进行扫描 寻找EXIF数据
然后将其写入到ExifLoader
中
exif_loader_get_data
流程很简单 就是为读取数据做准备 新建了一个ExifData
结构体 并对其进行配置 然后调用exif_data_load_data
开始读取数据工作
cExifData *
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
等等
cvoid
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
}
cvoid
exif_data_fix (ExifData *d)
{
exif_data_foreach_content (d, fix_func, NULL);
}
转入data->fix_func
参数为func(data->ifd[i],NULL)
注意这里是从ifd[0]
开始传的
cvoid
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
cstatic 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
来进行不同的处理 以提取数据
cvoid
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
有三种情况 经过验证为第一种路径造成的崩溃
exif_entry_realloc
本质上就是对realloc
的再封装 调用之后发现realloc
抛出realloc(): invalid next size
错误 明显是下一个堆块被破坏 但是经过查看 exif_entry_realloc()
及其下属的函数没有对d_orig
也就是e->data
有读写操作 证明溢出操作在上级
cstatic 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
中
开始循环前 其结构依然正常
循环结束后其结构遭到破坏 确定溢出点在循环处
将循环单拎出来看
cfor (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_MOTOROLA
对e->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)
相关的值
cvoid
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
cstatic 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
cstatic 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)
感觉没有什么大问题 写入都是小写入一次写入只写入了两个字节 那么问题只能出在循环次数上
cfor (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
通过调试确定有调用行为
cexif_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字节处

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