libexif介绍

  libexif是一个用于解析、编辑和保存EXIF数据的库。它支持EXIF 2.1标准(以及2.2中的大多数)中描述的所有EXIF标签。它是用纯C语言编写的,不需要任何额外的库

漏洞介绍

CVE-2009-3895
在 libexif 0.6.18 的 libexif/exif-entry.c 中,exif_entry_fix函数(又称标记修复例程)中存在基于堆的缓冲区溢出,可允许远程攻击者通过无效的 EXIF 图像造成拒绝服务或可能执行任意代码。注意:其中一些详细信息是从第三方信息中获得的。

环境搭建

libexif环境搭建

wget https://sourceforge.net/projects/libexif/files/libexif/0.6.14/libexif-0.6.14.tar.gz
tar -xzvf libexif-0.6.14.tar.gz
sudo apt-get install autopoint libtool gettext libpopt-dev
autoreconf -fvi		# 自动生成 Makefile
./configure  --prefix="路径" --disable-dls
make
make install

搭建libexif成功

exif环境搭建

autoreconf -fvi
 ./configure --prefix="/home/muxuecen/fuzz101/libxif/install/exif-0.6.18/install" --disable-dls
make
make install

第一次的时候报错了 缺少popt

安装一下再执行就可以了

sudo apt-get install libpopt-dev

现在我们的环境就搭建完成了

漏洞分析-踩坑

以下是我的踩坑经历(想看正确分析 请跳转漏洞分析-正确)

根据漏洞描述可以发现在exif_entry_fix函数中

去静态分析加漏扫 定位到: ps(后面根据crashes分析 发现漏扫定位错了 当个思路参考一下)

接下来用afl-clang-lto重新编译:

libexif

sudo rm -rf install
make clean
CC=afl-clang-lto ./configure  --prefix="/home/muxuecen/fuzz101/libxif/install/libexif-libexif-0_6_18-release/install" --disable-nls
make
make install

exif

sudo rm -rf install
make clean
CC=afl-clang-lto ./configure --enable-shared=no --prefix="/home/muxuecen/fuzz101/libxif/install/exif-0.6.18/install" --disable-nls
make
make install

开始fuzz


afl-fuzz -i ~/fuzz101/libxif/install/sample/exif-samples/jpg/ -o ~/fuzz101/libxif/install/exif-0.6.18/install/bin/out -s 123 -- ./exif @@

验证之后发现确实把程序打崩了 我们这里通过调试去看一下


从这个调用栈可以看出来 exif_entry_fix - > exif_entry_realloc 导致的报错

继续跟踪发现就三个地方调用了 exif_entry_realloc 分别断点看逻辑发现最后是断在了0x014D9D这个位置 帮助我们更好的分析

但是跟进到后面 我感觉 非常的怪异 因为是进入realloc导致分配地址不对而形成的中止,并不符合上面两条CVE的描述 仔细看了一下 发现是我crash太少了 这可能是因为我只fuzz几分钟的原因....... 有点太急着调了 但是还是不对 跑了1小时还是有问题 并没有和cve描述的报错一样

这里踩坑发现是用的0.6.18版本去弄的 如果要复现的话建议是用0.6.14

漏洞分析-正确

换版本重新编译 具体指令和上面一样

这里提一个小tips虽然麻烦但会方便我们调试 此时已经有了crash能导致崩溃的样本 此时我们尽量不要用插桩的代码去分析了 利用已有样本 去调试正常编译的 会比较方便

我们可以通过下面的图直观的看出插桩对代码审计造成的影响、

fuzz前

fuzz后

这回fuzz了一个小时:

fuzz出了三个 测试一下

并且分别看了几个样本

发现这回没啥问题了 然后我们把样本都保存下来 最后 正常编译进行分析

先对第二个样本进行分析

调用链二分析

main->->exif_data_load_data->exif_data_load_data_content->memcpy_xxx

通过漏扫快速定位

问题就在memcpy这里

这里exif_data_alloc是自定义的一个创建堆块 这里v35是堆地址 我们直接断点断到这里可以发现 memcpy了多次 最后一次在0x21这个位置 出现了一字节溢出

这条路线的成因基本就到这里 但要是就这么结束了 那功利性就有点太强了,我们仔细去分析一下整个路线的逻辑 以及什么参数导致的这个漏洞产生

这里分别设置了本地日志 然后取出参数 并且把这个参数进行检测 如果小于等于1 就弹出 帮助也就是--help的输出 然后

处理 IFD 字符串,使用 将字符串转换为 IFD 类型,并检查该 IFD 是否有效。如果无效,记录一条日志。exif_ifd_from_string

处理 EXIF 标签字符串,将其转换为实际的 EXIF 标签。如果标签无效,则记录错误日志。

这里加一点IFD和EXIF这方面的知识补充

1. IFD(Image File Directory)字符串
IFD是一个目录结构,用来存储和组织图像文件中的各种元数据。每个IFD包含多个条目,每个条目都指向一个特定的元数据标签(Tag)。这些标签包含关于图像的信息,如拍摄时间、设备型号、曝光时间、GPS信息等。

在EXIF数据中,IFD可以看作是一个容器,它将相关的元数据标签组合在一起。不同类型的IFD存储不同种类的元数据,常见的IFD包括:

0 IFD:存储图像的基本信息,例如图像的宽度和高度。
1 IFD:存储图像的EXIF数据,比如相机的设置、曝光时间、ISO等。
EXIF IFD:存储与图像相关的EXIF元数据,如拍摄设备、拍摄参数等。
GPS IFD:存储图像的地理位置信息,例如经度、纬度、高度等。
Interoperability IFD:存储用于EXIF数据互作的信息。
在代码中,IFD字符串如, , , 等用来指定要访问的特定IFD。"0""1""EXIF""GPS"

2. Exif标签(Exif Tag)
Exif标签是EXIF数据格式中的基本元素,用于存储图像的元数据。每个标签代表一个具体的元数据项,例如图像的拍摄日期、相机型号、镜头信息等。标签在EXIF数据中是通过整数标识的,每个标签都有一个唯一的编号。

常见的Exif标签包括:

拍摄日期和时间(Tag 0x9003):记录图像拍摄的日期和时间。
相机型号(Tag 0x0110):记录拍摄图像的相机型号。
曝光时间(Tag 0x829a):记录图像的曝光时间。
ISO(Tag 0x8827):记录图像拍摄时使用的ISO感光度。
GPS信息(Tag 0x0000等):记录图像拍摄时的地理坐标。
在代码中,是将标签名称(如“ISO”)或标签值(如“0x829a”)转换为对应的标签ID,并进行处理。exif_tag_from_string(tag_string)

这个是-s指令

获取并显示与某个EXIF标签相关的描述、名称、标题等信息。
通过调用 , , 和 等函数来获取不同的标签元数据。exif_tag_get_description_in_ifdexif_tag_get_name_in_ifdexif_tag_get_title_in_ifd
转换字符编码(从UTF-8到LAT1)。
格式化并输出标签的详细信息。

然后就进到了这个部分

前面是创建一个内存管理实例 和 日志 然后把内容写入到创建的实例中

这里和上面的逻辑基本一样 都是创建一个实例 然后读入日志 和 内容

这里的话就是检测你loader的几个部分 buf bytes 是否都有 况且还有对字节的限制 如果太小会报错

  if ( *(_DWORD *)d_orig != 1718188101 || *((_WORD *)d_orig + 2) )
  {
    while ( 1 )
    {
      v7 = *v5;
      if ( *v5 == 0xFF )
      {
LABEL_13:
        if ( !v6 )
        {
LABEL_14:
          v10 = "EXIF marker not found.";
          goto LABEL_15;
        }
      }
      else
      {
        while ( v7 != 0xD8 )
        {
          if ( v7 != 0xE0 )
          {
            if ( v7 != 0xE1 )
              goto LABEL_14;
            if ( v6 - 1 > 1 )
            {
              v6 -= 3;
              exif_log(
                data->priv->log,
                EXIF_LOG_CODE_DEBUG,
                "ExifData",
                "We have to deal with %i byte(s) of EXIF data.",
                (unsigned __int16)__ROL2__(*(_WORD *)(v5 + 1), 8));
              if ( v6 > 5 )
              {
                v5 += 3;
                goto LABEL_19;
              }
            }
            goto LABEL_23;
          }
          v8 = v6 - 1;
          v9 = (unsigned __int16)__ROL2__(*(_WORD *)(v5 + 1), 8);
          if ( v8 < (unsigned __int16)v9 )
            return;
          v6 = v8 - v9;
          v5 += v9 + 1;
          v7 = *v5;
          if ( *v5 == 0xFF )
            goto LABEL_13;
        }
      }
      ++v5;
      --v6;
    }
  }
  exif_log(data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "Found EXIF header.");
LABEL_19:
  if ( *(_DWORD *)v5 != 1718188101 || *((_WORD *)v5 + 2) )
  {
    v10 = "EXIF header not found.";
    goto LABEL_15;
  }
  exif_log(data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "Found EXIF header.");
  if ( v6 <= 0xD )
    return;
  if ( *((_WORD *)v5 + 3) != 18761 )
  {
    if ( *((_WORD *)v5 + 3) == 19789 )
    {
      v13 = EXIF_BYTE_ORDER_MOTOROLA;
      data->priv->order = EXIF_BYTE_ORDER_MOTOROLA;
      goto LABEL_28;
    }
    v10 = "Unknown encoding.";
LABEL_15:
    v11 = dcgettext("libexif-12", v10, 5);
    log = data->priv->log;
LABEL_16:
    exif_log(log, EXIF_LOG_CODE_CORRUPT_DATA, "ExifData", v11);
    return;
  }
  v13 = EXIF_BYTE_ORDER_INTEL;
  data->priv->order = EXIF_BYTE_ORDER_INTEL;
LABEL_28:
  if ( exif_get_short(v5 + 8, v13) != 42 )
    return;
  v14 = v6 - 6;
  v15 = exif_get_long(v5 + 10, data->priv->order);
  exif_log(data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "IFD 0 at %i.", v15);
  exif_data_load_data_content(data, EXIF_IFD_0, v5 + 6, v6 - 6, v15, 0);
  if ( v6 < (int)v15 + 8 )
    return;
  v16 = 12 * exif_get_short(&v5[(unsigned int)v15 + 6], data->priv->order);
  if ( v6 < (int)v15 + v16 + 12 )
    return;
  v17 = exif_get_long(&v5[(unsigned int)v15 + 8 + (__int64)v16], data->priv->order);
  v18 = v17;
  if ( !v17 )
    goto LABEL_34;
  exif_log(data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "IFD 1 at %i.", v17);
  if ( v14 < v18 )
  {
    v11 = "Bogus offset.";
    log = data->priv->log;
    goto LABEL_16;
  }

上面这代码 就是各种检测和输出

void __fastcall exif_data_load_data_content(
        ExifData *data,
        ExifIfd ifd,
        const unsigned __int8 *d,
        unsigned int ds_0,
        unsigned int offset,
        unsigned int recursion_depth)
{
  ExifDataPrivate *priv; // rax
  __int64 v9; // rbp
  ExifShort v10; // ax
  unsigned int v11; // r12d
  unsigned int v12; // r8d
  unsigned int v13; // r9d
  unsigned int v14; // r14d
  ExifShort v15; // ax
  unsigned int v16; // r12d
  ExifLong v17; // r15d
  ExifDataPrivate *v18; // rax
  ExifEntry *v19; // r12
  ExifLong v20; // eax
  ExifTag tag; // edi
  const char *v22; // rax
  unsigned int v23; // edx
  ExifLong v24; // r10d
  unsigned int v25; // ecx
  unsigned int v26; // eax
  unsigned int v27; // eax
  ExifIfd v28; // edi
  const char *v29; // rax
  unsigned int v30; // eax
  const char *v31; // rax
  const char *v32; // rax
  unsigned int v33; // eax
  ExifLong v34; // eax
  unsigned __int8 *v35; // rax
  unsigned int v36; // r10d
  ExifIfd v37; // edi
  const char *name; // rax
  unsigned __int8 *v39; // rax
  unsigned int ds_0b; // [rsp+4h] [rbp-54h]
  unsigned int recursion_depthb; // [rsp+8h] [rbp-50h]
  unsigned int na; // [rsp+Ch] [rbp-4Ch]
  ExifLong n; // [rsp+Ch] [rbp-4Ch]
  ExifLong thumbnail_offset; // [rsp+10h] [rbp-48h]
  unsigned int v47; // [rsp+14h] [rbp-44h]
  unsigned int v48; // [rsp+18h] [rbp-40h]
  unsigned int v49; // [rsp+18h] [rbp-40h]
  unsigned int v50; // [rsp+18h] [rbp-40h]
  int v51; // [rsp+1Ch] [rbp-3Ch]

  if ( data )
  {
    priv = data->priv;
    if ( priv )
    {
      if ( recursion_depth == 151 )
      {
        exif_log(priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "ExifData", "Deep recursion detected!", offset);
      }
      else if ( offset < ds_0 - 1 )
      {
        v9 = offset + 2;
        v10 = exif_get_short(&d[offset], priv->order);
        na = v10;
        LOWORD(v11) = v10;
        exif_log(data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "Loading %i entries...", v10);
        v12 = na;
        v13 = recursion_depth;
        if ( ds_0 < (unsigned int)v9 + 12 * na )
        {
          v11 = (ds_0 - (unsigned int)v9) / 0xC;
          v12 = (unsigned __int16)v11;
        }
        if ( (_WORD)v11 )
        {
          n = 0;
          v14 = 0;
          thumbnail_offset = 0;
          recursion_depthb = ds_0;
          v47 = v13;
          ds_0b = v12;
          while ( 1 )
          {
            v15 = exif_get_short(&d[v9], data->priv->order);
            v16 = v15;
            if ( v15 == 0x8825 )
              break;
            if ( v15 > 0x8825u )
            {
              if ( v15 == 0xA005 )
              {
                v27 = exif_get_long(&d[v9 + 8], data->priv->order);
                if ( ifd == EXIF_IFD_INTEROPERABILITY )
                {
                  v37 = EXIF_IFD_INTEROPERABILITY;
LABEL_58:
                  name = exif_ifd_get_name(v37);
                  exif_log(
                    data->priv->log,
                    EXIF_LOG_CODE_DEBUG,
                    "ExifData",
                    "Recursive entry in IFD '%s' detected. Skipping...",
                    name);
                  goto LABEL_29;
                }
                if ( !data->ifd[4]->count )
                {
                  exif_data_load_data_content(data, EXIF_IFD_INTEROPERABILITY, d, recursion_depthb, v27, v47 + 1);
                  goto LABEL_29;
                }
                v28 = EXIF_IFD_INTEROPERABILITY;
LABEL_35:
                v29 = exif_ifd_get_name(v28);
                exif_log(
                  data->priv->log,
                  EXIF_LOG_CODE_DEBUG,
                  "ExifData",
                  "Attemt to load IFD '%s' multiple times detected. Skipping...",
                  v29);
                goto LABEL_29;
              }
            }
            else if ( v15 > 0x202u )
            {
              if ( v15 == 0x8769 )
              {
                v30 = exif_get_long(&d[v9 + 8], data->priv->order);
                if ( ifd == EXIF_IFD_EXIF )
                {
                  v37 = EXIF_IFD_EXIF;
                  goto LABEL_58;
                }
                v28 = EXIF_IFD_EXIF;
                if ( !data->ifd[2]->count )
                {
                  exif_data_load_data_content(data, EXIF_IFD_EXIF, d, recursion_depthb, v30, v47 + 1);
                  goto LABEL_29;
                }
                goto LABEL_35;
              }
            }
            else if ( v15 > 0x200u )
            {
              v17 = exif_get_long(&d[v9 + 8], data->priv->order);
              if ( (_WORD)v16 == 513 )
              {
                if ( v17 && n )
                  exif_data_load_data_thumbnail(data, d, recursion_depthb, v17, n);
                thumbnail_offset = v17;
              }
              else if ( thumbnail_offset && v17 )
              {
                exif_data_load_data_thumbnail(data, d, recursion_depthb, thumbnail_offset, v17);
                n = v17;
              }
              else
              {
                n = v17;
              }
              goto LABEL_29;
            }
            if ( exif_tag_get_name_in_ifd((ExifTag)v15, ifd) )
            {
              v18 = data->priv;
LABEL_21:
              v19 = exif_entry_new_mem(v18->mem);
              v19->tag = exif_get_short(&d[(unsigned int)v9], data->priv->order);
              v19->format = exif_get_short(&d[(unsigned int)v9 + 2], data->priv->order);
              v20 = exif_get_long(&d[(unsigned int)v9 + 4], data->priv->order);
              tag = v19->tag;
              v19->components = v20;
              v22 = exif_tag_get_name(tag);
              exif_log(
                data->priv->log,
                EXIF_LOG_CODE_DEBUG,
                "ExifData",
                "Loading entry 0x%x ('%s')...",
                (unsigned int)v19->tag,
                v22);
              v23 = LODWORD(v19->components) * exif_format_get_size(v19->format);
              if ( v23 )
              {
                v24 = v9 + 8;
                if ( v23 > 4 )
                {
                  v48 = v23;
                  v34 = exif_get_long(&d[(unsigned int)v9 + 8], data->priv->order);
                  v23 = v48;
                  v24 = v34;
                }
                v25 = v24;
                v26 = v23 + v24;
                if ( v23 >= v24 )
                  v25 = v23;
                if ( v26 >= v25 && recursion_depthb >= v26 )
                {
                  v49 = v24;
                  v51 = v23;
                  v35 = (unsigned __int8 *)exif_data_alloc(data, v23);
                  v36 = v49;
                  v19->data = v35;
                  if ( v35 )
                  {
                    v19->size = v51;
                    memcpy(v35, (void *)&d[v49], v51);
                    v36 = v49;
                  }
                  if ( v19->tag == EXIF_TAG_MAKER_NOTE )
                  {
                    if ( v19->size > 6 )
                    {
                      v39 = v19->data;
                      v50 = v36;
                      exif_log(
                        data->priv->log,
                        EXIF_LOG_CODE_DEBUG,
                        "ExifData",
                        "MakerNote found (%02x %02x %02x %02x %02x %02x %02x...).",
                        *v39,
                        v39[1],
                        v39[2],
                        v39[3],
                        v39[4],
                        v39[5],
                        v39[6]);
                      v36 = v50;
                    }
                    data->priv->offset_mnote = v36;
                  }
                  exif_content_add_entry(data->ifd[ifd], v19);
                }
              }
              exif_entry_unref(v19);
              goto LABEL_29;
            }
            if ( *(_DWORD *)&d[v9] )
            {
              v32 = exif_ifd_get_name(ifd);
              exif_log(
                data->priv->log,
                EXIF_LOG_CODE_DEBUG,
                "ExifData",
                "Unknown tag 0x%04x (entry %i in '%s'). Please report this tag to <libexif-devel@lists.sourceforge.net>.",
                v16,
                v14,
                v32);
              v18 = data->priv;
              if ( (v18->options & 1) == 0 )
                goto LABEL_21;
            }
            else
            {
              v31 = exif_ifd_get_name(ifd);
              exif_log(
                data->priv->log,
                EXIF_LOG_CODE_DEBUG,
                "ExifData",
                "Skipping empty entry at position %i in '%s'.",
                v14,
                v31);
            }
LABEL_29:
            ++v14;
            v9 += 12LL;
            if ( v14 == ds_0b )
              return;
          }
          v33 = exif_get_long(&d[v9 + 8], data->priv->order);
          if ( ifd == EXIF_IFD_GPS )
          {
            v37 = EXIF_IFD_GPS;
            goto LABEL_58;
          }
          v28 = EXIF_IFD_GPS;
          if ( !data->ifd[3]->count )
          {
            exif_data_load_data_content(data, EXIF_IFD_GPS, d, recursion_depthb, v33, v47 + 1);
            goto LABEL_29;
          }
          goto LABEL_35;
        }
      }
    }
  }
}

这段代码从给定的数据块中读取 EXIF 信息,并按条目逐一存储。每个条目由 结构体表示,并包含标签、格式、组件数等信息。如果条目包含数据,还会分配内存并将数据存储在 中。特殊的 标签会单独处理,确保相关数据被正确存储。整个过程通过递归加载数据,直到所有的 EXIF 数据都被处理并存储到对应的 IFD 中

调用链一分析

main->exif_loader_get_data->exif_data_load_data->exif_get_sshort

这里就是访问到不存在地址buf的代码 这里由于gdb优化代码编译我们不能直接调出 所以手动gdb调试查看一下参数

可以看到offset的值是0xffffffff

前面对offset只有一个检测就是offset+6+2 要小于0x7c3很明显满足

if (offset + 6 + 2 > ds) {
   817                 return;
   818         }

就导致buf这段 直接加了0xffffffff 自然就不存在这个地址

修复

修复的话很简单

关于调用链一 就只需要加一个offset的检测就行、

关于调用链二 加一个对size大小和堆大小方面的检测 防止溢出