CVE-2021-44707 Adobe Reader 越界写漏洞分析与利用
2022-11-9 17:9:0 Author: paper.seebug.org(查看原文) 阅读量:29 收藏

作者:[email protected]天玄安全实验室
原文链接:https://mp.weixin.qq.com/s/elLI4YvJ0u9yYoyQpsv1og

漏洞概述

该漏洞为2021年天府杯中使用的Adobe Reader越界写漏洞,漏洞位于字体解析模块:CoolType.dll中,对应的Adobe Reader版本为:21.007.20099。

原理分析

开启page heap后打开POC,Adobe崩溃于CoolType + 2013E处:

First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000046 ebx=00000002 ecx=a54d102f edx=5ab2f001 esi=34adeb2c edi=5ab2efd0
eip=6cf9013e esp=34ade848 ebp=34adea70 iopl=0         nv up ei ng nz ac po cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010293
CoolType!CTInit+0x1cb4e:
6cf9013e 807aff00        cmp     byte ptr [edx-1],0         ds:002b:5ab2f000=??
0:005> dd edx -31
5ab2efd0  0000d0c0 00000000 00000000 00000000
5ab2efe0  00000000 00000000 00000000 00000000
5ab2eff0  00000000 00000000 00000000 d0c00000
5ab2f000  ???????? ???????? ???????? ????????
5ab2f010  ???????? ???????? ???????? ????????
5ab2f020  ???????? ???????? ???????? ????????
5ab2f030  ???????? ???????? ???????? ????????
5ab2f040  ???????? ???????? ???????? ????????

从崩溃处可以明显看出越界访问了0x5ab2f000处的内存,崩溃函数为:CoolType +1FCB0,下断于该函数查看参数:

0:011> g
Breakpoint 0 hit
eax=0000002e ebx=34fff0e4 ecx=34fff310 edx=73006500 esi=59e06fd0 edi=00000001
eip=6cf8fcb0 esp=34ffef0c ebp=34fff0a8 iopl=0         nv up ei ng nz ac pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000297
CoolType!CTInit+0x1c6c0:
6cf8fcb0 55              push    ebp
0:011> dps esp+4 L7
34ffef10  59e06fd0
34ffef14  0000002e
34ffef18  34ffefc4
34ffef1c  00000001
34ffef20  00000000
34ffef24  00000001
34ffef28  00000000
0:011> dd 59e06fd0
59e06fd0  d6000800 50015001 51015101 61016101
59e06fe0  62016201 31000100 7a540f51 01521d18
59e06ff0  73006e18 74002000 73006500 d0c07400
59e07000  ???????? ???????? ???????? ????????
59e07010  ???????? ???????? ???????? ????????
59e07020  ???????? ???????? ???????? ????????
59e07030  ???????? ???????? ???????? ????????
59e07040  ???????? ???????? ???????? ????????

传入的参数1为POC中构造的字符串,参数2则为字符串的长度,调试后发现调用了函数MultiToWide后,传入的字符串变成了崩溃时的内存布局:

if ( !a9 || a10_ff || a5 )
    {
      v49 = size;
      MultiToWide(a5, v12, *(unsigned __int16 *)a3, (void *)v12, (int)&v49);// 内部调用MultiByteToWideCharStub
      LOWORD(result) = v49;
      *(_WORD *)a3 = v49;
      result = (unsigned __int16)result;
      goto LABEL_83;
    }
......
LABEL_83:
    if ( (_WORD)result )
    {
      v44 = (_BYTE *)(v12 + 1);
      v45 = ~v12;
      v51 = ~v12;
      do
      {
        if ( *(v44 - 1) || *v44 )               // crash

深入分析MultiToWide函数,内部调用了MultiByteToWideCharStub函数,将字符串转化为宽字节字符串:

bool __cdecl MultiToWide(int a1, int lpMultiByteStr, int cbMultiByte, void *MultByte, int MultByteSize)
{
  _BYTE *v5; // edx
  size_t size; // eax
  int v7; // edx
  int v8; // ecx
  char v9; // al
  bool v10; // zf
  unsigned __int16 CodePage; // ax
  int WideCharSize; // eax
  int v14; // esi
  int v15; // [esp+10h] [ebp-210h]
  size_t MultByteSize_1; // [esp+18h] [ebp-208h]
  char lpWideCharStr[512]; // [esp+1Ch] [ebp-204h] BYREF

  v5 = (_BYTE *)lpMultiByteStr;
  v15 = 0;
  size = *(_DWORD *)MultByteSize;
  *(_DWORD *)MultByteSize = 0;
  MultByteSize_1 = size;
  if ( !cbMultiByte )
  {
LABEL_4:
    v7 = cbMultiByte + lpMultiByteStr;
    v8 = 2 * cbMultiByte;
    *(_DWORD *)MultByteSize = 2 * cbMultiByte;
    if ( 2 * cbMultiByte )
    {
      do
      {
        v9 = *(_BYTE *)--v7;
        *((char *)MultByte + v8 - 1) = 0;
        v10 = v8 == 2;
        v8 -= 2;
        *((_BYTE *)MultByte + v8) = v9;
      }
      while ( !v10 );
    }
    return 1;
  }
  while ( (unsigned __int8)(*v5 - 0x20) <= 0x5Du )
  {
    ++v5;
    if ( ++v15 >= (unsigned int)cbMultiByte )
      goto LABEL_4;
  }
  CodePage = GetCodePage(a1);
  WideCharSize = off_82FF304(CodePage, 0, lpMultiByteStr, cbMultiByte, lpWideCharStr, 0x100);// 该函数为MultiByteToWideCharStub
  if ( WideCharSize && WideCharSize <= 0x100 )
  {
    v14 = 2 * WideCharSize;
    sub_800C383(MultByte, MultByteSize_1, lpWideCharStr, 2 * WideCharSize);// 内部调用memcpy
    *(_DWORD *)MultByteSize = v14;
    return 1;
  }
  *(_DWORD *)MultByteSize = MultByteSize_1;
  if ( sub_81443C0(a1, lpMultiByteStr, cbMultiByte, MultByte, (size_t *)MultByteSize) )
    return 1;
  *(_DWORD *)MultByteSize = MultByteSize_1;
  return sub_81442A2(a1, lpMultiByteStr, cbMultiByte, (int)MultByte, MultByteSize) != 0;
}

转换完毕后调用sub_800C383,检查当前MultByteSize大于等于2倍的WideCharSize时才会将转换后的宽字节字符串拷贝至原字符串的位置,否则将原字符串清空:

size_t __cdecl sub_800C383(void *MultByte, size_t MultByteSize, void *WideChar, size_t WideCharSize_double)
{
  size_t v4; // esi
  int *v5; // eax
  int v7; // [esp-8h] [ebp-Ch]

  v4 = WideCharSize_double;
  if ( WideCharSize_double )
  {
    if ( MultByte )
    {
      if ( WideChar && MultByteSize >= WideCharSize_double )
      {
        memcpy(MultByte, WideChar, WideCharSize_double);
        return 0;
      }
      else
      {
        memset(MultByte, 0, MultByteSize);
        if ( WideChar )
        {
          if ( MultByteSize >= WideCharSize_double )
            return 0x16;
          v5 = errno();
          v7 = 0x22;
        }
        else
        {
          v5 = errno();
          v7 = 0x16;
        }
        v4 = v7;
        *v5 = v7;
        invalid_parameter_noinfo();
      }
    }
    else
    {
      v4 = 0x16;
      *errno() = 0x16;
      invalid_parameter_noinfo();
    }
  }
  return v4;
}

调试至MultiByteToWideCharStub函数,转化后WideChar字符串的字符数为0x23个:

0:008> g
Breakpoint 2 hit
eax=000003a8 ebx=1306e8a8 ecx=0b9aea08 edx=1306e8a8 esi=00000024 edi=0b9aec3c
eip=724040e9 esp=0b9ae9d8 ebp=0b9aec0c iopl=0         nv up ei ng nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000282
CoolType!CTGetVersion+0x58ab9:
724040e9 ff1504f35b72    call    dword ptr [CoolType!CTGetVersion+0x213cd4 (725bf304)] ds:002b:725bf304={KERNEL32!MultiByteToWideCharStub (75603da0)}
0:008> dps esp L6
0b9ae9d8  000003a8  //CodePage
0b9ae9dc  00000000  //dwFlags
0b9ae9e0  1306e8a8  //lpMultiByteStr
0b9ae9e4  00000024  //cbMultiByte
0b9ae9e8  0b9aea08  //lpWideCharStr
0b9ae9ec  00000100  //cchWideChar
0:008> dd 1306e8a8 Lc
1306e8a8  5001d608 51015001 61015101 62016101
1306e8b8  31016201 7a540f51 01521d18 20736e18
1306e8c8  74736574 74747474 74747474 00007474
0:008> dd 0b9aea08 Lc
0b9aea08  0c4ef818 0c4ef810 0b9aea4c 6f6c53a8
0b9aea18  1306ded0 0c4c5588 00000000 00000000
0b9aea28  0b9aea50 6dc1901f 6dc19024 1fbe9e77
0:008> p
eax=00000023 ebx=1306e8a8 ecx=c7dacb9e edx=0b9aea08 esi=00000024 edi=0b9aec3c
eip=724040ef esp=0b9ae9f0 ebp=0b9aec0c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
CoolType!CTGetVersion+0x58abf:
724040ef 85c0            test    eax,eax
0:008> dd 0b9aea08 L23*2/4  //转换后0x23个字符的WideChar字符串
0b9aea08  003f0008 00010050 00010050 00010051
0b9aea18  00010051 00010061 00010061 00010062
0b9aea28  00010062 00510031 0054000f 0018007a
0b9aea38  0052001d 00180001 0073006e 00740020
0b9aea48  00730065

继续执行到sub_800C383,由于当前MultiByteSize小于WideCharSize * 2,会执行memset函数清空MultiByteStr:

0:008> pc
eax=0000002e ebx=1306e8a8 ecx=c7dacb9e edx=0b9aea08 esi=00000046 edi=0b9aec3c
eip=7240410d esp=0b9ae9e0 ebp=0b9aec0c iopl=0         nv up ei ng nz na po cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000283
CoolType!CTGetVersion+0x58add:
7240410d e87182ecff      call    CoolType!CTInit+0x8d93 (722cc383)
0:008> dps esp L4
0b9ae9e0  1306e8a8      //MultiByteStr
0b9ae9e4  0000002e      //MultiByteSize
0b9ae9e8  0b9aea08      //WideCharStr
0b9ae9ec  00000046      //WideCharSize * 2
0:008> dd 1306e8a8 Lc   //原MultiByte字符串
1306e8a8  5001d608 51015001 61015101 62016101
1306e8b8  31016201 7a540f51 01521d18 20736e18
1306e8c8  74736574 74747474 74747474 00007474
0:008> p
eax=00000022 ebx=1306e8a8 ecx=7df11661 edx=00000000 esi=00000046 edi=0b9aec3c
eip=72404112 esp=0b9ae9e0 ebp=0b9aec0c iopl=0         nv up ei pl nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000212
CoolType!CTGetVersion+0x58ae2:
72404112 83c410          add     esp,10h
0:008> dd 1306e8a8 Lc   //执行函数后被清空
1306e8a8  00000000 00000000 00000000 00000000
1306e8b8  00000000 00000000 00000000 00000000
1306e8c8  00000000 00000000 00000000 00000000

随后遍历被清空的MultByteStr,遍历的次数为转换后的WideChar的大小。当前MultByte的大小为0x2e,WideChar的大小为0x46。因此遍历到超过MultByte的大小时就造成了越界访问。

LABEL_83:
    if ( (_WORD)MultSize )
    {
      v44 = (_BYTE *)(EmptyMultByteStr + 1);
      v45 = ~EmptyMultByteStr;
      v51 = ~EmptyMultByteStr;
      do
      {
        if ( *(v44 - 1) || *v44 )       //POC崩溃处
        {
          if ( &v44[v45] != (_BYTE *)offset )
          {
            *(_BYTE *)(EmptyMultByteStr + offset) = *(v44 - 1);
            *(_BYTE *)(offset + EmptyMultByteStr + 1) = *v44;
          }
          offset += 2;
        }
        MultSize = *(unsigned __int16 *)a3;
        v44 += 2;
        v46 = (int)&v44[v45] < MultSize;
        v45 = v51;
      }
      while ( v46 );
    }
    *(_WORD *)a3 = offset;
    return MultSize;
  }

利用思路

该漏洞的利用方式和大部分越界写漏洞一致:

  1. 通过堆喷射ArrayBuffer对象制造MultByteStr大小的内存空洞
  2. 触发漏洞,MultByteStr位于两个ArrayBuffer对象之间
  3. 绕过sub_800C383的字符串长度校验,将WideChar的字符串覆盖临近ArrayBuffer对象的Length属性值,构造越界写原语
  4. 通过越界写原语修改下一个临近ArrayBuffer对象的Length属性为0xFFFFFFFF,构造任意地址读写原语

有了思路之后,首先需要确定控制MultByteStr大小的值位于POC中的位置,通过逆向可以得知该值通过一系列运算确定:

int __thiscall GetMultStr(int this, __int16 a2, __int16 a3, __int16 a4, __int16 a5, unsigned __int16 *a6)
{
  int v7; // ebx
  unsigned __int8 *OriginStr; // esi
  __int16 v9; // cx
  __int16 v10; // dx
  unsigned __int16 MultiByteLength; // cx
  int MultiByteStr; // esi
  unsigned __int16 v14; // [esp+10h] [ebp-10h]
  unsigned __int16 v15; // [esp+14h] [ebp-Ch]
  unsigned __int16 v16; // [esp+18h] [ebp-8h]
  unsigned __int8 v17; // [esp+1Eh] [ebp-2h]
  unsigned __int8 v18; // [esp+1Fh] [ebp-1h]

  v7 = 0;
  if ( !*(_DWORD *)(this + 8) )
    return 0;
  OriginStr = *(unsigned __int8 **)(this + 0x18);
  if ( !*(_WORD *)(this + 0x14) )
    return 0;
  while ( 1 )
  {
    v9 = *OriginStr;
    OriginStr += 0xC;
    v10 = *(OriginStr - 11) | (unsigned __int16)(v9 << 8);
    v16 = _byteswap_ushort(*((_WORD *)OriginStr - 5));
    v15 = _byteswap_ushort(*((_WORD *)OriginStr - 4));
    v14 = _byteswap_ushort(*((_WORD *)OriginStr - 3));
    v18 = *(OriginStr - 2);
    MultiByteLength = _byteswap_ushort(*((_WORD *)OriginStr - 2));  //获取MultiByteStr的长度
    v17 = *(OriginStr - 1);
    if ( a2 == v10 && a3 == v16 && a4 == v15 && a5 == v14 )
      break;
    if ( (unsigned __int16)++v7 >= *(_WORD *)(this + 0x14) )
      return 0;
  }
  *a6 = MultiByteLength;
  MultiByteStr = *(_DWORD *)(this + 4) + *(unsigned __int16 *)(this + 0x16) + (v17 | (v18 << 8));   //获取MultiByteStr
  if ( (unsigned __int8)((_DWORD (__stdcall *)(int, _DWORD))sub_801A99B)(MultiByteStr, MultiByteLength) )
    return MultiByteStr;
  else
    return 0;
}

确定MultiByteStr的长度后,会调用malloc函数申请大小为MultiByteStr长度加1的内存空间,并将MultiByteStr拷贝到该块内存中:

while ( 1 )
  {
    oldMultStr = (void *)GetMultStr(3, v13, (__int16)Src, a4, MultiByte);// 循环获取到str
    if ( LOWORD(MultiByte[0]) )
      break;
    if ( ++v13 > 0xA )
      goto LABEL_22;
  }
  v14 = (void *)alloc(LOWORD(MultiByte[0]) + 1);// 调用malloc申请大小为MultiByteStr长度加1的内存空间
  memcpy(v14, oldMultStr, LOWORD(MultiByte[0]));// 拷贝MultiByteStr到新申请的内存中

修改POC使得MultiByteStr的长度为0x10f,通过JS代码堆喷射大小为0x100的ArrayBuffer对象同时制造内存空洞,使得MultiByteStr位于两个ArrayBuffer对象之中,修改后的内存布局如下:

0:009> g
Breakpoint 0 hit
eax=0000010f ebx=0b5aea10 ecx=0b5aec3c edx=0000010f esi=1db0a4a0 edi=00000001
eip=709bfcb0 esp=0b5ae838 ebp=0b5ae9d4 iopl=0         nv up ei ng nz ac pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000297
CoolType!CTInit+0x1c6c0:
709bfcb0 55              push    ebp
0:009> dps esp+4 L7
0b5ae83c  1db0a4a0
0b5ae840  0000010f
0b5ae844  0b5ae8f0
0b5ae848  00000001
0b5ae84c  00000000
0b5ae850  00000001
0b5ae854  00000000
0:009> dd 1db0a4a0 L(110*2+8)/4
//----------------MultiByteStr---------------
1db0a4a0  41004100 41004100 41004100 41004100
1db0a4b0  41004100 41004100 41004100 41004100
1db0a4c0  41004100 41004100 41004100 41004100
1db0a4d0  41004100 41004100 41004100 41004100
1db0a4e0  41004100 41004100 41004100 41004100
1db0a4f0  41004100 41004100 41004100 41004100
1db0a500  41004100 41004100 41004100 41004100
1db0a510  41004100 41004100 41004100 41004100
1db0a520  41004100 41004100 41004100 41004100
1db0a530  41004100 41004100 41004100 41004100
1db0a540  41004100 41004100 41004100 41004100
1db0a550  41004100 41004100 41004100 41004100
1db0a560  41004100 41004100 41004100 41004100
1db0a570  41004100 41004100 41004100 41004100
1db0a580  41004100 41004100 41004100 41004100
1db0a590  41004100 41004100 41004100 41414100
1db0a5a0  41414141 41414141 41414141 00414141
//-------------------------------------------
1db0a5b0  399fd8af 88009700 00000000 00000100   //Arraybuffer.ByteLength = 0x100
1db0a5c0  00000000 00000000 ffffffff ffffffff
1db0a5d0  ffffffff ffffffff ffffffff ffffffff
1db0a5e0  ffffffff ffffffff ffffffff ffffffff
1db0a5f0  ffffffff ffffffff ffffffff ffffffff
1db0a600  ffffffff ffffffff ffffffff ffffffff
1db0a610  ffffffff ffffffff ffffffff ffffffff
1db0a620  ffffffff ffffffff ffffffff ffffffff
1db0a630  ffffffff ffffffff ffffffff ffffffff
1db0a640  ffffffff ffffffff ffffffff ffffffff
1db0a650  ffffffff ffffffff ffffffff ffffffff
1db0a660  ffffffff ffffffff ffffffff ffffffff
1db0a670  ffffffff ffffffff ffffffff ffffffff
1db0a680  ffffffff ffffffff ffffffff ffffffff
1db0a690  ffffffff ffffffff ffffffff ffffffff
1db0a6a0  ffffffff ffffffff ffffffff ffffffff
1db0a6b0  ffffffff ffffffff ffffffff ffffffff
1db0a6c0  ffffffff ffffffff

执行完毕MultiToWide函数后,临近Arraybuffer对象的长度被覆盖为0x410041:

0:009> dd 1db0a4a0 L(110*2+8)/4
//----------------MultiByteStr---------------
1db0a4a0  00410041 00410041 00410041 00410041
1db0a4b0  00410041 00410041 00410041 00410041
1db0a4c0  00410041 00410041 00410041 00410041
1db0a4d0  00410041 00410041 00410041 00410041
1db0a4e0  00410041 00410041 00410041 00410041
1db0a4f0  00410041 00410041 00410041 00410041
1db0a500  00410041 00410041 00410041 00410041
1db0a510  00410041 00410041 00410041 00410041
1db0a520  00410041 00410041 00410041 00410041
1db0a530  00410041 00410041 00410041 00410041
1db0a540  00410041 00410041 00410041 00410041
1db0a550  00410041 00410041 00410041 00410041
1db0a560  00410041 00410041 00410041 00410041
1db0a570  00410041 00410041 00410041 00410041
1db0a580  00410041 00410041 00410041 00410041
1db0a590  00410041 00410041 00410041 00410041
1db0a5a0  00410041 00410041 00410041 00410041
//-------------------------------------------
1db0a5b0  00410041 00410041 00410041 00410041   //Arraybuffer.ByteLength = 0x410041
1db0a5c0  00000000 00000000 ffffffff ffffffff
1db0a5d0  ffffffff ffffffff ffffffff ffffffff
1db0a5e0  ffffffff ffffffff ffffffff ffffffff
1db0a5f0  ffffffff ffffffff ffffffff ffffffff
1db0a600  ffffffff ffffffff ffffffff ffffffff
1db0a610  ffffffff ffffffff ffffffff ffffffff
1db0a620  ffffffff ffffffff ffffffff ffffffff
1db0a630  ffffffff ffffffff ffffffff ffffffff
1db0a640  ffffffff ffffffff ffffffff ffffffff
1db0a650  ffffffff ffffffff ffffffff ffffffff
1db0a660  ffffffff ffffffff ffffffff ffffffff
1db0a670  ffffffff ffffffff ffffffff ffffffff
1db0a680  ffffffff ffffffff ffffffff ffffffff
1db0a690  ffffffff ffffffff ffffffff ffffffff
1db0a6a0  ffffffff ffffffff ffffffff ffffffff
1db0a6b0  ffffffff ffffffff ffffffff ffffffff
1db0a6c0  ffffffff ffffffff

此时已经具有了越界写的能力,再次修改下一个临近Arraybuffer的对象的长度为0xFFFFFFFF即可完成读写原语的构造,剩下的利用过程大同小异就不再赘述了。

最终在Windows 10上完成了整个利用:

总结

该漏洞为Adobe Reader越界写漏洞,由于解析字体将字符串转化为宽字节字符串时没有进行完整的校验导致越界拷贝,利用的难度不大且触发稳定。


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/2008/


文章来源: https://paper.seebug.org/2008/
如有侵权请联系:admin#unsafe.sh