In a previous article, I explained use-after-free (UAF) vulnerabilities and why they are a common bug, especially in large and complex codebases such as Internet Explorer (IE). Because of this, a number of targeted attacks were found to be leveraging zero-day UAF vulnerabilities to infiltrate their targets. This year alone, two well-publicized campaigns leveraged a zero-day IE UAF vulnerability: Operation SnowMan (CVE-2014-0322) in February and Operation Clandestine Fox (CVE-2014-1776) in April.
As a response to these IE UAF vulnerability discoveries and their role in multiple zero-day attacks, Microsoft introduced two exploit mitigations that aim to increase the cost of exploiting IE UAF vulnerabilities.
UAF Exploit Mitigations
Before discussing the new exploit mitigations, let’s first take a look at the two critical steps of UAF exploitation: the “Free operation” and the “Realloc operation.” Using the CVE-2014-0322 zero-day exploit as an example, the diagram below shows how these steps are used to replace a CMarkup object with attacker-controlled data.
The “Free operation” involves forcefully triggering a free of a target object. In this example, the target CMarkup object is freed by executing this.outerHTML = this.outerHTML” inside an event handler. This causes pointers to the CMarkup object to become dangling pointers and the memory chunk allocated for the CMarkup object to be freed and become available for future allocations.
Next, the “Realloc operation” attempts to reallocate and control the contents of the memory chunk previously allocated for the freed target object. In this example, by setting the title property of a div element with an attacker-controlled string whose size is equivalent to the size of the CMarkup object, two events occur. First, an allocation for memory to store the string is performed; this, in turn, then causes the heap manager to return the chunk previously allocated for the recently freed CMarkup object. Second, the attacker-controlled string is written to this reallocated chunk.
For UAF exploits to work, the “Free operation” requires the ability to reliably force a free of the target object’s memory chunk, while the “Realloc operation” requires the ability to reliably reallocate the target object’s memory chunk and write arbitrary data to it.
The Memory Protector feature introduced in the July 2014 IE update includes a protected free (also called deferred free) mechanism that makes it difficult to execute the “Free operation” in certain cases. Specifically, it tries to prevent the freeing of a chunk if dangling pointers to it are found in the stack. With the protected free mechanism, when a chunk is about to be freed, instead of actually freeing the chunk, it is added to a chunks list and the chunk’s contents are zeroed-out. When a certain threshold is reached, the chunks list is consulted and the chunks that are safe to free are actually freed.
To track to-be-freed chunks and to store other information used when deciding when to perform the actual freeing of the chunks, IE uses the CMemoryProtector object. The CMemoryProtector object is a per-thread object whose pointer is stored in the thread local storage. The TLS index of the CMemoryProtector object pointer is tracked by the global variable MemoryProtection::CMemoryProtector::tlsSlotForInstance.
The CMemoryProtector object consists of the following fields:
- Blocks array (offset: +00, type: SBlockDescriptorArray) is a structure that holds information of the to-be-freed chunks.
- Is force mark-and-reclaim (+14, db) is a flag used to determine whether a mark-and-reclaim operation should be performed regardless of BlocksArray.TotalSize. This is controlled by the FEATURE_MEMPROTECT_MODE feature control.
- Stack high address (+18, dd) is the thread’s highest stack address, used in the mark operation when fetching pointer values from the stack.
- Stack marker address (+1C, dd) is the highest stack address when MemoryProtection::CMemoryProtector::ProtectCurrentThread() is invoked. This is used to determine whether the stack has fully rewound and, therefore, whether an unconditional reclaim operation will be performed.
The fields of the SBlockDescriptorArray structure located at offset +00 of the CMemoryProtector object are as follows:
- Blocks (+00, SBlockDescriptor*) is a to-be-freed chunks list. This is a pointer to an array of SBlockDescriptor structures that contains a chunk’s address and size.
- Total Size (+04, dd) is the total size of the to-be-freed chunks. This is used when checking whether a mark-and-reclaim operation needs to be performed.
- Count (+08, dd) is the number of SBlockDescriptor entries in the Blocks array.
- Max Count (+0C, dd) is the maximum number of SBlockDescriptor entries in the Blocks array.
- Is Sorted (+10, db) is the flag used to determine whether the Blocks array is sorted by chunk address. It is used in the mark operation.
The CMemoryProtector object structure can be visualized as follows:
To implement the protected free mechanism, HeapFree() calls are replaced with a call to MemoryProtection::CMemoryProtector::ProtectedFree() or an in-line version of it. ProtectedFree() first checks if the total size of the to-be-freed chunks (CMemoryProtector.BlocksArray.TotalSize) is more than or equal to 100,000 bytes or if the force mark-and-reclaim flag (CMemoryProtector.IsForceMarkAndReclaim) is set. If either of the two is true, a mark-and-reclaim operation to free chunks that are safe to free is performed first.
Next, the size of the to-be-freed chunk is added to CMemoryProtector.BlocksArray.TotalSize and CMemoryProtector.BlocksArray.Count is incremented. Then, an SBlockDescriptor entry in the CMemoryProtector.BlocksArray.Blocks array is updated to contain the chunk’s address and size. ProtectedFree() sets bit 1 of the chunk address if the is chunk is located in the separate, isolated heap. Finally, the contents of the to-be-freed chunk are zeroed-out.
An interesting side effect of the protected free mechanism is that UAF bugs will likely manifest themselves as NULL dereference bugs, even if pageheap is enabled. One way to temporarily disable the Memory Protector when performing root cause analysis of IE memory corruption issues is via the FEATURE_MEMPROTECT_MODE feature control. For the 32-bit IE tab process running on 64-bit Windows, this means setting the following registry entry:
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_MEMPROTECT_MODE::iexplore.exe = 0 (disabled), 1 (enabled), 2 (enabled and force mark-and-reclaim)
As previously mentioned, the mark-and-reclaim operation is performed by ProtectedFree() when certain conditions are met. The mark operation performed by MemoryProtection::CMemoryProtector::MarkBlocks() first sorts the chunks list by ascending chunk address. Next, it looks for chunks that are still referenced by pointers in the stack. For this, MarkBlocks() fetches each DWORD/pointer value in the thread’s stack and marks the chunks in the chunks list whose address range covers the fetched DWORD/pointer value. Marking involves setting bit 0 of the chunk’s address.
Finally, the reclaim operation performed by MemoryProtection::CMemoryProtector::ReclaimUnmarkedBlocks() simply iterates through the chunks list, frees all unmarked chunks and resets bit 0 of marked chunks. Counters in the CMemoryProtector object are also updated in the process.
An unconditional reclaim operation performed by MemoryProtection::CMemoryProtector::ReclaimMemoryWithoutProtection() frees all to-be-freed chunks. This unconditional reclaim operation acts as a cleanup routine that is invoked whenever the thread’s stack has fully rewound since the thread’s initial call to MemoryProtection::CMemoryProtector::ProtectCurrentThread(). Currently, a call to MemoryProtection::CMemoryProtector::ProtectCurrentThread() happens every time mshtml!GlobalWndProc() is triggered as a result of a window message being received.
After discussing the exploit mitigation that makes it difficult to execute the “Free operation” in a UAF exploit, let’s now take a look at the exploit mitigation that will make the “Realloc operation” a challenge: the Isolated Heap. The Isolated Heap feature was introduced in the June 2014 IE update. It aims to limit the options for how an attacker will be able to reallocate and control the chunk contents of certain types of IE objects after they had been freed. This is done by allocating critical objects in an “isolated heap” separate from the heap where other types of objects are allocated.
Most of the objects allocated in the Isolated Heap are objects representing HTML elements (most are C*Element objects) and SVG elements (CSVG*Element objects). Some internal objects such as CMarkup and CTreeNode objects are also allocated in the Isolated Heap. Objects stored in the Isolated Heap are those objects that are deemed critical in a way that they are objects that are likely be involved in UAF vulnerabilities.
Going back to the CVE-2014-0322 exploit example, if Isolated Heap is implemented, the “Realloc operation” will fail because the string containing the attacker-controlled data will be allocated in the default process heap instead of the separate Isolated Heap where CMarkup objects are stored.
To implement the Isolated Heap feature, a new heap is created when mshtml!_DllMainStartup() is called, and the handle to the newly created heap is stored in the global variable g_hIsolatedHeap. Allocation to and freeing from the Isolated Heap simply involves passing the Isolated Heap handle to a call to HeapAlloc() or HeapFree().
It is important to understand that the Memory Protector and the Isolated Heap features have some limitations — instances in which the protection they provide will not be applied.
For Memory Protector, in cases where the dangling pointer is stored in the heap instead of the stack, an attacker may be able to force the freeing of a chunk. For Isolated Heap, not all objects that can be potentially leveraged in a UAF exploit are stored in the Isolated Heap, such as the CMshtmlEd object in CVE-2012-4969. For these cases, previous techniques for the “Realloc operation” can be used.
The introduction of the Memory Protector and the Isolated Heap in IE certainly raises the bar for the exploitation of IE UAF vulnerabilities. It will prevent attackers from easily reusing existing IE UAF exploitation techniques, thereby increasing the cost of developing weaponized exploits for IE UAF vulnerabilities. However, as previous experiences have taught us, determined attackers will adapt either by finding clever ways to get around the exploit mitigations or by favoring the use of other classes of vulnerabilities. It will be very interesting to see what the attackers’ next moves are.