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.
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.
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.
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?
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,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).
Why does it free the CTreePos object?
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.
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:
- 1006036 – Microsoft Internet Explorer Memory Corruption Vulnerability (CVE-2014-1772)