CVE-2013-3112: From NULL to Control - Persistence pays off with crashes

Months ago, my fuzzer found a bug that was initially flagged as a NULL pointer dereference. The crash instruction was different from the others, so I decided to minimize the crash and have a closer look. Things got quite interesting, and with some persistence, ended up in control of EIP (Extended Instruction Pointer).  This article walks through the whole analysis process from a null pointer crash to fully controlling execution. 


Details of the initial crash:


First, I simplified the POC (Proof of Concept) from 2000+ lines to the following:



Running the POC in Windows 7 x64 would crash Internet Explorer (IE) 9 in 32-bit mode with both PageHeap enabled and disabled. 



The NULL pointer dereference was caused by the following:



I ended up changing and adding some code in the simplified POC, hoping to get something more interesting.  Eventually, I ended up adding "ret.innerHTML=ret.innerHTML" right after the second appendChild() call.


Things got very interesting this time when I got something totally different:



Then with PageHeap enabled I got the following:



So, why did "ret.innerHTML=ret.innerHTML" change the game?  To understand what caused this behavior let's start by setting the following break points (BP) and re-run the POC without setting ret.innerHTML.


This is pretty uninteresting.  Now let's re-run with the same breakpoints but this time with "ret.innerHTML=ret.innerHTML" but without PageHeap.



From the debugger output above, we can draw a conclusion that “ret.innerHTML=ret.innerHTML” influenced applyElement's execution flow and reached MSHTML!CDoc::CreateMarkupFromInfo.


The next test would be break pointing on MSHTML!CDoc::CreateMarkupWithElement whenever MSHTML!CElement::EnsureInMarkup is hit, and studying the results both with and without setting “ret.innerHTML”.


First, running without "ret.innerHTML=ret.innerHTML", but with the following breakpoints set:



We notice that the second break point never triggers.  Running the POC with ret.innerHTML set we get a different result:



So, I jumped into IDA to understand what's going on within EnsureInMarkup:



If the check succeeds then the execution flow changes and reaches another check:



If this check fails, it gets us where we want to go, which is here:



So what's ESI and what sets the values at [ESI+26] and [ESI+0C]?  To start answering these questions, let's set the following breakpoint for the second test (with PageHeap enabled):



Now our next strategy would be setting a breakpoint on MSHTML!CSemanticElement::CreateElement, to get the new object address and set a memory breakpoint at offset 0x26:



The above shows that when "ret.innerHTML=ret.innerHTML" is run it sets the byte at [ESI+26] to 0.

This behavior would change the execution flow in MSHTML!CElement::EnsureInMarkup leading to the execution of CDoc::CreateCMarkupFromInfo:



Notice “applyElement” gets executed twice.  The first execution creates a CMarkup object via “CDoc::CreateCMarkupFromInfo” through “CDoc::CreateMarkupWithElement”.  The second time applyElement executes, it frees the object then re-uses it, leading to a “potentially” exploitable Use-After-Free (UAF - Use after free errors occur when a program continues to use a pointer after it has been freed.[i]).


Analysis of the new crash:


With PageHeap enabled I got the following:



The above clearly shows that a CMarkup object has been freed via MSHTML!CMarkup::`vector deleting destructor'.  The next step would be finding exactly where the object has been allocated and the exact size.



The following breakpoint would show the allocation (PageHeap enabled).  Some output has been truncated or edited for readability.



Apparently the object has been allocated from MSHTML!CDoc::CreateMarkupFromInfo with size 0x190.  Verifying this in IDA:



The next step would be checking where the freed object has been referenced. Assuming we have the following callstack:



If we set a breakpoint on MSHTML!CElement::PrivateEnterTree, re-run the PoC, and trace a bit through:




The CPhraseElement object contains a reference to the freed object at offset 0x2C.  To verify this, we can set a breakpoint on CPhraseElement::CreateElement, grab the address of the newly created object, then finally set a memory breakpoint at offset 0x2C.



ESI below shows the address of the freed object.



If we go back to where the crash happened and try to understand what should have been called in a perfect world. This is what we would get:



Then if we examine this:



The method that should have been called is AddRef.  Apparently the object has been freed and J it fails to run AddRef leading to potential code execution.


To summarize:

1. The CMarkup object is freed.

2. A reference is kept at offset 0x2C of the CPhraseElement object.

3. Failure to call AddRef leads to potential remote code execution (RCE)


Controlling the freed object - To LFH or not to LFH (Low Fragmentation Heap):


Usually researchers would go with the option of activating LFH and then try to fill up the freed memory.  Here's what happens when choosing the LFH path:





Almost everything was filled up.  However, the object that we need to control was not.  The reason is applyElement was called twice.  The object is created the first time applyElement is called.  The second time it's called the free/re-use happens, making it very difficult to win the race condition with LFH enabled.  To verify this:




If we check the memory around ESI, we would see that it has been filled right after the first applyElement was called:



The current situation makes it hard to win a race condition and have that object overwritten.  So, I decided to play around without LFH enabled and here's what happens:




Notice the two forward and backward link pointers (Flink and Blink) at offset 0 and 4.  Things are getting more interesting now.  Increasing the allocations and being lucky enough to write the adjacent chunk would eventually give you the following result (not 100 percent stable):






A lot of crashes may seem pretty much useless.  However minor modifications can completely change the game. In our case, you can take a crash manifesting as a null pointer in an entirely different direction. What may seem like a useless call instruction can suddenly get very interesting if you are persistent. Sometimes you just have to flex your reverse engineering skills, and sometimes you just get plain lucky.  Finally, take advantage of how the memory manager handles the free lists, this may turn hard-to-control Use-After-Free bugs into exploitable ones.


Step it up!  Take your 0x00000000 to 0-Day. 


-- Abdul-Aziz Hariri, HP Security Research


Note: The vulnerability used for this demonstration is described by the following references:

Labels: crashes| security
Francis Provencher | ‎09-26-2013 06:05 PM

Clearly explain, Nice work Abed!



Leave a Comment

and posted as promptly as possible during regular business hours


Showing results for 
Search instead for 
Do you mean 
About the Author

Follow Us