作者:Joey@天玄安全實驗室
原文鏈接: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 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/2008/