The Isolated Heap for DOM objects included in the Microsoft Patch Tuesday for June 2014 was just a fire drill aimed at making the exploitation of use-after-free (UAF) vulnerabilities more difficult. The patch for July 2014, however, has been quite a shock to exploit developers! In this release, Microsoft showed some determination in fighting back against UAF bugs with this improvement - the introduction of a new memory protector in Microsoft Internet Explorer, which would make exploitation of UAF vulnerabilities extremely difficult.
An Overview Of The Changes
In the July 2014 update, a total of fourteen MSHTML!MemoryProtection::* functions and one global variable have been added to improve memory freeing of HTML and DOM elements.
In this update, we can also see that significant changes are made in the deconstructor of most elements:
The HeapFree function has been replaced with MemoryProtection::CMemoryProtector::ProtectedFree.
The HeapFree function has been replaced with ProcessHeapFree (a fastcall version of MemoryProtection::CMemoryProtector::ProtectedFree).
Some deconstructors just implement the same mechanism of using MemoryProtection::CMemoryProtector::ProtectedFree directly.
How do these new functions work? And how do these changes stop the exploitation of use-after-free bugs and other kinds of exploits? The following technical analysis will tell you.
Memory allocations typically reference similarly-sized chunks of memory that have been previously freed. Taking advantage of this behavior, the traditional way of exploiting a use-after-free bug consists of the following process:
1) The program allocates and then later frees object A. 2) The attacker allocates object B over the freed memory, carefully controlling the data that will be used later. 3) The program uses freed object A, referencing the attacker-controlled data.
The most effective mitigation should be in making Step 2 above, the replacement of an object, become harder or even impossible. There are some ways that the defender can do this:
A) The ultimate way is to use a type-safe memory management system which would prevent the attacker from reclaiming the freed memory using a different type of object. The attacker could only replace the memory using the same type of object, which does not help at all in exploitation.
B) The cheaper way is to do some tricks in the current memory management system to make the memory allocation and freeing behavior out of the attacker's control.
Specifically, the method in B is the improvement that we have seen in IE in June 2014, where an isolated heap in the memory allocate method has been added; and in July 2014, where the protected free method has been implemented.
The isolated heap that was introduced could bring some trouble to attackers, but if the mitigation only relies on this, it should still be fairly easy to bypass; an attacker could still reference the freed objects with a different type, especially when the chunks of memory have been merged.
The story has changed with the newly added protected free method in the July 2014 update. In each thread of an IE process, a MemoryProtector structure has been introduced to protect the current thread, as its name implies.
The BlockArray in this structure stores the size and address of the elements to be freed. The new function MemoryProtection::CMemoryProtector::ProtectedFree then makes use of the MemoryProtector structure to make freeing of the elements' memory safer.
Generally speaking, this function that is responsible for freeing the protected memory acts like a simple conservative garbage collector. Instead of releasing the unused space immediately which could allow attackers to have a chance to re-allocate the space, MemoryProtection::CMemoryProtector::ProtectedFree first holds these unused spaces (filling the content with zeroes) until the number or total size meets a specific threshold. When this threshold is met, it does not release every stored element's memory at that moment just yet as it still needs to implement a "Mark and Sweep" process. In Figure 4, this can be seen in the calls to MarkBlocks and ReclaimUnmarkedBlocks.
The following shows the marking process which scans and marks every element that is still being referenced in the stack; these marked elements will not be freed in the sweeping process. By doing this, the possible use of a freed object in the stack would be prevented.
Eventually, the non-marked elements are freed in the sweeping process.
Now, let's switch our point-of-view to the attacker's side. To exploit a use-after-free bug under the above new conditions, we first need to find a controlled allocation method in the same heap as the freed object. We then need to predict the memory status (how many frees are still needed before triggering the actual free) when freeing an object. After that, we need to perfectly build the release sequence to make sure that the previously freed object's space has actually been freed. That already sounds difficult, but that's not all. We also have to predict the possible memory coalesces and handle the conflicts from other unknown allocations, and then reclaim the memory at a good timing. After all of that, we might then be able to take control of the previously freed element's space. At this time, I'm afraid that most attackers who are trying to exploit use-after-free bugs have given up already.
As we can see in the beginning of the function MemoryProtection::CMemoryProtector::ProtectedFree in Figure 4, if the variable MemoryProtection::CMemoryProtector::tlsSlotForInstanc is equal to -1 or if TlsGetValue() fails, the old function HeapFree is directly called, but this is obviously tough to achieve.
Some heap manipulation techniques would be affected by the joint action of the isolated heap and protected free because such techniques need to free some elements to make holes for the vulnerable buffer. The delayed free mechanism should bring some headache to attackers, but manipulation is not impossible, as long as attackers could find a controlled allocation method in the same heap and are able to free a reasonable amount of elements.
Would the simple conservative garbage collector introduce a new attacking surface, such as the classic ASLR bypass by Dion? It's possible to place an integer value into the stack and then brute-guess the address of the elements that are to be freed. However, even if an attacker could guess this address, one still cannot get a useful pointer such as the vftable that would leak a base DLL address due to the fact that the contents of the memory have already been zeroed out.
In conclusion, building a reliable use-after-free exploit in IE is extremely difficult now. Well done, MSRC guys!
Special Contribution by Margarette Joven