Root Cause Analysis of CVE-2014-1772 – An Internet Explorer Use After Free Vulnerability

We see many kinds of vulnerabilities on a regular basis. These range from user-after-free (UAF) vulnerabilities, to type confusion, to buffer overflows, to cross-site scripting (XSS) attacks. It’s rather interesting to understand the root cause of each of these vulnerability types, so we looked at the root cause of an Internet Explorer vulnerability – CVE-2014-1772.

We’d privately disclosed this vulnerability to Microsoft earlier in the year, and it had been fixed as part of the June Patch Tuesday update, as part of MS14-035. While this vulnerability was already patched some time ago, it is still a good example of UAF vulnerabilities in general.

The code to trigger this vulnerability is below:

Figure 1. HTML code to trigger vulnerability

Before debugging, several flags must be set to make the job of analysis easier. Run the command gflags.exe /i iexplore.exe +hpa +ust to to enable the page heap (HPA) and user stack trace (UST) flags. This will make finding memory corruption and tracing heap allocation and frees easier. This file can be found in the Windbg installation folder. You can now run windbg, attach Internet Explorer, and use it to access the HTML file.

Examining the JavaScript execution flow, when line 18 of the HTML code is executed, the crash happens:

Figure 2. Output of crash

We can see the EDI register point to a freed memory space, which leads to an access violation.

What is the value of the EDI register? Let us look at the code below.

Figure 3. Assembly code

The above code tells us that the EDI is from the first argument, which is the CTreePos* type. We can assume the EDI is a pointer of CTreePos. Since the CTreePos object is freed, how can we get where the object is freed? Because the UST flag is set, we can use the !heap -p -a edi command in windbg.

Figure 4. Call stack

The above figure shows us the call stack of the CTreePos object freed. The call stack has a lot of information. We see the function CMarkup::FreeTreePos; this evidence gives us evidence that the freed object is CTreePos object and that this is a use-after-free issue.

Since it is a UAF issue, we want to deeply understand the issue. We need to locate where the CTreePos object is created, where the object is freed, and where the freed object is used again. Figure 4 gives us where the object was freed. To find where is used again, we need to examine the crash point. The call stack is as follows:

Figure 5. Call stack

How do we find the location where the CTreePos object was created? There are many ways. I prefer to run the sample again, and break the object freed point and use the !heap -p -a xxxx command to trace back to where the object is created. The call stack is as follows:

Figure 6. Call stack

For UAF problems, I prefer to compare the 3 locations (create, free, use again) to find some clues.

Figure 7. Call stacks

There are 3 columns in Figure 7. They are call stack trace summaries: from left to right, it is when the object is created, freed, and used again. In the above example, the direction of the stack is from the bottom to the top.

There is plenty of useful information here. First, we can find the relationship between the 3 parts. Under the yellow line, CDoc::CutCopyMove is the last identical function in the creation call stack trace and the free call stack trace. This means the execution flow creates the CTreePos object and then frees the object in CDoc::CutCopyMove. Under the red line, the execution flow frees the object and then uses it again (and crashes) in CSpliceTreeEngine::InsertSplice.

In the second column, we find the execution flow in the CSpliceTreeEngine::InsertSplice function encounters a failure and call the Fire_onerror function. The function will call the JavaScript object’s onerror event. At the event, the execution will call CMarkupPointer::UnEmbed to free the object.

Right away, we have four questions.

Why does it trigger an onerror event?

Why does it create the CTreePos object?

Why does it free the CTreePos object?

Why does it use the freed object again?

Before answering these questions, I want to summarize some background knowledge about how Internet Explorer’s DOM tree implementation. Because IE is not open source, this information is gathered by reverse engineering, so it may not be 100% accurate.

One page has a CMarkup object to represent the page’s skeleton or DOM tree. The CMarkup object contain a pointer to the root CElement object. This is the parent class of many concrete element classes. In Figure 1, the Javascript object e_1 and e_2 are the CObjectElement objects which are inherited from CElement. The CElement object has a pointer to a CTreeNode object. The CTreeNode object also has a pointer to a related CElement object. The CTreeNode’s object has a pair of pointer to CTreePos objects.

Why is a CTreePos object needed? This is because IE uses a Splay Tree algorithm to manuiplate the DOM tree. In the Splay Tree, the CTreePos object is the node that is involved in the algorithm. The CMarkupPointer object represents a location in the CMarkup object (DOM tree). So the CMarkupPointer object has a pointer to CTreePos to represent its location. CMarkupPointer has several statuses which are related to UAF issues.

Embed status: this means CMarkupPointer created CTreePos, which is added to the Splay Tree.

Unembed status: this means CMarkupPointer removes the CTreePos from the Splay Tree and frees it.

The following graph describes the interactions involving the splay tree.

Figure 8. Splay tree graph

Going back to our four questions, we can now attempt to answer them.

Why does it trigger an onerror event?

From Figure 1’s Javascript code, we can see e_2.onerror sets a handler function. At line 22, e_2.swapNode will trigger DOM tree’s changing; this calls the CObjectElement::CreateObject function. This function checks the object’s CLSID. Because e_2’s CLSID is not set, it triggers the onerror event handler.

In the handler, at line 22, the Javascipt code r.insertNode(e_2) will change the DOM tree once again and change CObjectElement::CreateObject as well; because e_2 has no CLSID , it will again trigger the onerror event handler once again. The second time the this handler runs, at r.setEnd(document.all[1],0)” , it frees the CTreePos object.

Why does it create the CTreePos object?

From Figure 6, the CTreePos object is created in calling the CDomRange::InsertNode function. We can map this function to Figure 1’s line 19: r.insertNode(e_2). The CDomRange::InserNode function will insert elements into the DOM tree. The function is called Doc::CutCopyMove to modify the DOM tree and takes several arguments. The first CMarkupPointer type argument is the source start location in CMarkup (DOM tree) . The second CMarkupPointer type argument is the source end location in CMarkup (DOM Tree). The third CMarkupPointer type argument is the target location in CMarkup (DOM Tree).

Doc::CutCopyMove will copy or move the source sub DOM tree to the target location in DOM tree. Because of the use of the Splay Tree algorithm (which uses CTreePos as a node), the function needs to create a CTreePos object and add it to the SplayTree for source CMarkup (DOM tree) and target CMarkup (DOM Tree). Doc::CutCopyMove calls CMarkup::DoEmbedPointers to let the CMarkupPointer change to embed status. Finally, the CTreePos object is created. The UAF CTreePos object is created for the e_1 JavaScript element.

Why does it free the CTreePos object?

From the call stack trace when the CTreePos object is freed (Figure 4), we can find CDomRange::setEnd. This function can be mapped to line 17 in Figure 1: r.setEnd(e_1,0). That means the CTreePos object is in the implementation of setEnd. The CDomRange::setEnd function wants to replace the original end point with a new end point. This function finally calls CMarkupPointer::MoveToPointer to move to the specific DOM tree location. It will first call CMarkupPointer::UnEmbed to change this CMarkupPointer object to unembed and remove CTreePos from the Splay Tree and free it. The JavaScript code r.setEnd‘s argument is the e_1 element. So the CTreePos object related with the e_1 element is freed.

Why does it use the freed object again?

In Figure 7, under the red line is the function call for CSpliceTreeEngine::InsertSplice. Column B is CSpliceTreeEngine::InsertSplice+0x13ff. Column C is CSpliceTreeEngine::InsertSplice+0x6EDD4A. Column B is the free call stack trace and Column C is the “used again” crash call stack trace. This means that both “free” and “used again” happen in one function called CSpliceTreeEngine::InsertSplice. We trace the execution flow in this function, and find the following:

Figure 9. Assembly code

Instruction 636898C4, eax is the address of the UAF CTreePos Objects. The function saves the address to a local variable var_1E4. Then, it proceeds to 6368AA9A.

Figure 10. Assembly code

At 6368AA9A, it calls a virtual function CObjectElement::Notify. We can find here the call stack trace from Figure 7’s column B. This means when running this call, it encounters an error and calls the onerror event handler . That frees the CTreePos object. However, the CSpliceTreeEngine::InsertSplice function local variable var_1E4 holds a reference to this freed object. It then proceeds to 63D7735B.

At 63D7735B , it calls CElment::RecordTextChange ForTsf with var_1E4 as the second argument. When this function is run, if any instruction accesses the contents of the CTreePos object, a crash occurs.

Summary

In brief, the UAF issue’s root cause is that under the event interaction context, CSpliceTreeEngine::InsertSplice doesn’t handle local variable reference validation properly.

DOM is based on event mechanisms. Under complex event interaction contexts, it is a significant challenge to solve UAF issue completely. However, in recent patches Microsoft has introduced memory protection in Internet Explorer, which helps mitigate UAF issues (especially in cases where a UAF object is referenced from the call stack).

This highlights one important reason to upgrade to latest versions of software as much as possible: frequently, new techniques that make exploits more difficult are part of newer versions, making the overall security picture better.

Trend Micro Deep Security protects users against this particular threat. The following rule, released as part of the regular updates released in June is applicable:

Security Predictions for 2018

Attackers are banking on network vulnerabilities and inherent weaknesses to facilitate massive malware attacks, IoT hacks, and operational disruptions. The ever-shifting threats and increasingly expanding attack surface will challenge users and enterprises to catch up with their security.Read our security predictions for 2018.

Business Process Compromise

Attackers are starting to invest in long-term operations that target specific processes enterprises rely on. They scout for vulnerable practices, susceptible systems and operational loopholes that they can leverage or abuse. To learn more,
read our Security 101: Business Process Compromise.