The purpose of this article is to review the specific, explorer protection mechanisms integrated into Internet Explorer and Edge browsers.
We decided to combine the review of IE and Edge security mechanisms into one article, because, firstly, both are products of the notorious Microsoft company, and, secondly, this approach allows us to track how the approach to protection has changed and, accordingly, the development of protection of these browsers. Well, also for the reason that IE and Edge have a common code base.
Figure 1 - Development of security mechanisms in IE browser ( source )
We present these mechanisms in the form of a list. For IE, the list looks like this:
For Edge:
Within this article we will consider the following mechanisms:
One of the most common classes of vulnerabilities in the browsers in question, and in other browsers too, are UAF (use-after-free).
Any exploitation of UAF-vulnerabilities can be described in the form of the following scheme:
Figure 2 - UAF vulnerability exploitation scheme
Those. exploitation of UAF-vulnerabilities is done in 2 steps:
Free operation - a condition is triggered under which the object's memory is freed, while a pointer to this object remains - “dangling pointer” or dangling pointer. It will be used in the future to refer to an already non-existent object, for example, to call its method or write some value.
For example, if an object's method is called after it is released prematurely, which makes it possible to occupy the freed memory and overwrite the pointer to the virtual function table, thus changing the address of the method that will be called. As a result, it can be used to transfer control to a given address.
An erroneous reading of data from an object that has already been released leaves it possible to take from the memory already timely values ​​attached there, for example, a pointer to a function in the code section of the relocated ASLR module, i.e. implement address leakage and - hereinafter - ASLR bypass
Consider the mechanisms by which protection against such vulnerabilities in IE and Edge browsers is performed.
The Isolated Heap protection appeared in IE along with the June 2014 update (MS14-035). Its main goal is protection against UAF vulnerabilities. Now, when allocating memory for an object, it is allocated not from the process heap, but from the special “isolated heap” (as the name implies):
Figure 3 - Using an isolated heap when creating a CImgElement
element
Virtually all HTML and SVG DOM objects (CXXXElement) use an isolated heap. They are highly likely to have UAF vulnerabilities, so it is extremely important to isolate these objects.
Thanks to the introduction of a separate heap, an attacker has problems with the second step of exploiting UAF vulnerabilities, namely, reallocating memory and writing its own controlled values ​​or code to it, because such “convenient” objects as strings are allocated in another “heap” "And cannot be used to replace values ​​in a vulnerable object.
As can be seen in the figure below, a pointer to an isolated heap is stored in a global variable, the initialization of this heap occurs in the DllProcessAttach () function:
Figure 4 - Initialization of an isolated heap
If you track the XREFs to _g_hIsolatedHeap, you can see 2 functions-allocators that use it:
_MemIsolatedAlloc()
;_MemIsolatedAllocClear()
.The second option calls HeapAlloc with the HEAP_ZERO_MEMORY flag, which should prevent exploitation of vulnerabilities that use uninitialized memory.
Despite the fact that the use of an isolated heap helps to reduce the probability of successful operation of UAF vulnerabilities, not everything is as good as it seems.
First, there are still objects that use a “bunch” of the process - for example, CStr. Secondly, there is a theoretical workaround associated with the fact that the implementation of an isolated “heap” is exactly the same as the “heaps” of the process (there is no check of object types when allocating memory to it), and also with the fact that the selected objects are stored in separate place.
Those. to bypass the Isolated Heap, the attacker must fulfill the following conditions:
The guys from ZDI in 2015 presented a report on Black Hat and paper [1], which offered several Isolated Heap circumvention techniques. They have one common basis, which just satisfies the conditions described above.
The difference between the technicians is only in the details, namely in the preliminary work with the "heap".
Consider the basic technique.
As mentioned earlier, an isolated “heap” is exactly the same as the usual “heap” of the process, that is, when allocating memory, it does not take into account the type of object created. Because of this, the attacker has the option to fill the object being freed with an object of another type. Overwriting a freed object with an object of another type will result in a type confusion condition.
Such an object, in relation to which it is possible to create a type confusion condition, may be larger or even smaller than the size of the object with which we are going to overwrite the memory. This is an important fact that allows an attacker to control certain offsets within a reused object. For example, if we know that a UAF vulnerability occurs when a pointer is dereferenced, at offset 0x30, then all we need is to replace the object being freed with one that contains the value at offset 0x30, which we can control.
Thus, the sequence of actions is as follows:
PoC of this technology can be viewed from the guys from ZDI on github .
If the implementation of the Isolated Heap makes life difficult for an attacker in the operation of the UAF in the second step, the step of reallocating and rewriting memory, then the following mechanism makes it difficult to perform the first step, namely, freeing the memory of the object. This protection is called Memory Protection .
The essence of this mechanism is that it prevents the release of a chunk of memory as long as it is referenced in the stack or in the register (as long as there are “dangling pointers”). This mechanism first appeared in IE with the July security update in 2014 (MS14-037). Check registers added in August 2014.
IE uses a CMemoryProtector object to keep track of the parts of memory that need to be freed. It is created for each thread, and a pointer to it is stored in TLS (thread local storage).
Initialization of this object is performed in the MemoryProtection :: CMemoryProtector :: ProtectCurrentThread () function (it is worth noting that during initialization all fields of the object are set to 0).
Figure 5 - ProtectCurrentThread()
function
The CMemoryProtector object has the following structure (Figure 6) [2]
Figure 6 - Structure of the CMemoryProtector
object
The CMemoryProtector object consists of the following fields:
MemoryProtection::CMemoryProtector::ProtectCurrentThread()
function is MemoryProtection::CMemoryProtector::ProtectCurrentThread()
. It is used to determine whether the stack has been completely completed, and, consequently, whether the Reclamation Sweep operation will be performed (more on this below).The SBlockDescriptorArray structure contains the following fields:
Memory Protection when freeing memory uses the MemoryProtection::CMemoryProtector::ProtectedFree()
HeapFree()
instead of HeapFree()
.
void __userpurge MemoryProtection::CMemoryProtector::ProtectedFree(void *a1@<ecx>, void *Dst, unsigned __int32 a3, void *a4) { void *v4; MemoryProtection::CMemoryProtector *v5; unsigned int v6; MemoryProtection::CMemoryProtector *v7; size_t Size; v4 = a1; if ( Dst ) { if ( MemoryProtection::CMemoryProtector::tlsSlotForInstance != -1 && (v5 = (MemoryProtection::CMemoryProtector *)TlsGetValue(MemoryProtection::CMemoryProtector::tlsSlotForInstance), (v7 = v5) != 0) ) { MemoryProtection::CMemoryProtector::ReclaimMemory(v5, v6); //(1) Size = 0; if ( MemoryProtection::SBlockDescriptorArray::AddBlockDescriptor(v7, Dst, v4 != MemoryProtection::g_heapHandles, &Size) ) { MemoryProtection::SAddressFilter::AddBlock((MemoryProtection::CMemoryProtector *)((char *)v7 + 32), Dst, Size); //(2) memset(Dst, 0, Size); } else { RaiseFailFastException(0, 0, 0); } } else { HeapFree(v4, 0, Dst); } } }
ProtectedFree()
function
ProtectedFree()
with a certain frequency and under certain conditions carries out the procedure Reclamation Sweep (1). It is important to note that it is performed before the block to be released enters the Wait-List.
The essence of the procedure Reclamation Sweep:
A pointer to a block of memory is considered as a pointer to the beginning of the block, and to any place inside it.
Several functions are responsible for the Reclamation Sweep procedure, which are called inside the MemoryProtection::CMemoryProtector::ReclaimMemory()
conditions for starting this procedure are checked there too):
MemoryProtection::CMemoryProtector::MarksBlocks()
.This function first sorts the chunks in order of increasing addresses. Then, it checks to see if there are pointers to these areas in the thread stack or in registers. In the event that such a pointer exists, MarksBlocks()
marks such a block.
MemoryProtection::CMemoryProtector::ReclaimUnmarkedBlocks()
This function performs a simple bypass of the wait-list and frees all unlabeled areas of memory. The counter in the CMemoryProtection
object CMemoryProtection
also updated in the process.
After the Reclamation Sweep procedure, the block to be released is added to the wait-list and filled with zeros.
Figure 7 - Algorithm of the ProtectedFree()
function (source [1] )
It is worth noting that previously there was an unconditional procedure for freeing all areas of memory MemoryProtection::CMemoryProtector::ReclaimMemoryWithoutProtection()
- this happened every time the mshtml!GlobalWndProc()
function was mshtml!GlobalWndProc()
as a result of receiving a message from the main stream window. But later this opportunity was removed.
Memory Protection is an extremely effective technique against UAF vulnerabilities, when the pointer to freed memory remains on the stack or in the register, since Memory Protection ensures that this block will remain in the wait-list until it is used again (when allocating memory ) and will be in the wait-list filled with zeros.
Memory Protection also reduces the likelihood of exploiting other UAF vulnerabilities that are not in the category described above. In this case, when the "hanging pointer" is neither in the stack nor in the register, the attacker must solve the following tasks:
1) Memory release delay
As described above, the release of memory can take place with some delay, until the Reclamation Sweep procedure is performed.
2) Uncertainty due to “garbage” in the stack
The memory block may remain in the wait-list during the Reclamation Sweep procedure, as it may happen that the stack stores a value that is equal to the address somewhere inside the block being released. It is not necessary that this value is a pointer, but Memory Protection will treat it that way.
3) The greater difficulty in determining the time when the release of a section of memory occurs.
The Reclamation Sweep procedure will be implemented only if the amount of memory in the wait-list exceeds 100,000 bytes. This may not happen until a really large block of memory is in the wait-list.
4) More complex heap manager behavior when freeing memory
Summarizing all of the above, it should be borne in mind that the memory block to be released first falls into the wait-list and is there until the wait-list volume exceeds 100,000 bytes, and only then it is released while there are no pointers on the stack or in registers. At the same time, it is impossible to predict the state of the heap after simultaneously releasing a large number of different-size memory areas from the wait-list.
Despite these difficulties, the same guys from ZDI have come up with several techniques for bypassing the Memory Protection mechanism [1] - some of them are obvious and simple, and some are not.
Consider them further.
The most obvious solution is to use the so-called “memory pressure” to run the Reclamation Sweep procedure, i.e. it is necessary to allocate and then immediately free up memory of 100,000 bytes.
// ... // // wait-list var n = 100000 / 0x34 + 1; for (var i = 0; i < n; i++) { document.createElement("div"); } CollectGarbage(); // , … //
But this approach will not relieve from all problems - we will solve the problem of delaying the release of memory, but we will not get rid of all the others. Together with our object many other objects will be freed, and in an unpredictable way. This indefinite behavior leads to a decrease in reliability when attempting to establish control over the contents of released memory.
Also, a second obvious solution was possible earlier - this is the use of an unconditional procedure for freeing all memory areas, which is carried out when the GlobalWndProc()
function is GlobalWndProc()
.
We can interrupt the exploit execution with a delay that is so large that a new call to the GlobalWndProc()
function has exactly occurred, then Memory Protection will release all the blocks in the wait-list.
function step1() { // … ... // // , WndProc , // wait-list window.setTimeout(step2, 3000); } function step2() { // … ... // … // , WndProc , // wait-list, window.setTimeout(step3, 3000); } function step3() { // … }
This solution allows you to minimize the number of foreign objects that will be released along with ours. But it has its drawbacks - using setTimeout()
creates an opportunity for the appearance of an additional unpredictable branch of code that is executed in the current thread. As a result, you can get unpredictable and unwanted heap modifications.
The researchers did not stop there, thought and decided that to bypass Memory Protection, it is necessary to stabilize the wait-list — you need to build a sequence of such script actions in order to monitor and know the status of the wait-list. By creating such a sequence, we will be able to exploit UAF vulnerabilities.
To begin with, suppose we can allocate buffer A with a size of 100,000 bytes. Then we try to free this buffer. As a result, Memory Protection will put our buffer A in the wait-list, and the size of the wait-list will be exactly> = 100,000 bytes.
Figure 8 - The status of the wait-list after adding buffer A to it (source [1] )
Next, we again allocate and free buffer B of the size s we already need. During the ProtectedFree()
call, Memory Protection will see that the size of the wait-list is such that it’s time to start freeing (actually freeing) its elements. After this procedure, our buffer of size s will be in the wait-list.
Figure 9 - The state of the wait-list after adding buffer B to it (source [1] )
It is possible that not all the sections from the wait-list will be released and leave it - some of them may have pointers on the stack (let's call these Wi ). But, first, we can safely say that their total size is much less than 100,000 bytes. Secondly, it does not matter what their total number and total size is for the next steps. We know for sure that as long as there are pointers on the stack, these sections will be in the wait-list.
So, at this stage, we know the approximate state of the wait-list and are ready to perform the desired actions with a “heap” for exploiting vulnerabilities.
Suppose we want to free a block of memory at address C. It is possible that even with the aim of triggering the UAF at this address. In order for the memory block at this address to be exactly freed, and to do it in a predictable way:
ProtectedFree()
for block C. Block C gets into the wait-list and is there along with blocks Wi and block B of size s ;ProtectedFree()
for block E , our desired block C and blocks B and D will fall under the Reclamation Sweep procedure and will be released.
Figure 10 - The final status of the wait-list (source [1] )
Now we bring the practice to this theoretical strategy. You need to define an object that allocates buffers of arbitrary size and releases them through ProtectedFree()
. The guys from ZDI chose the CStr
object - it has a dynamic size and can be allocated from outside. But you can always try to choose another object, good on ProtectedFree 1100 xrefs.
Also, analyzing MSHTML, they found that CStr
uses the CElement::var_getElementsByClassName
. You can reach it via the getElementsByClassName
DOM method on any HTML element (which is exactly what you need).
At runtime, this method creates a CStr
containing string data, which is passed as the getElementsByClassName
parameter, and then deletes this CStr
.
Figure 11 - Function code CElement::var_getElementsByClassName
Thus, one call to getElementsByClassName
can achieve the goal of allocating and freeing an arbitrary size buffer.
One small limitation is that the minimum buffer size with string data that we can pass to getElementsByClassName
is 0x28 bytes. Therefore, CStr
takes 0x28 * 2 + 6 = 0x56 bytes (two bytes per character, plus 6 additional bytes).
var oDiv1 = document.createElement('div'); // / ProtectedFree string1 oDiv1.getElementsByClassName(string1); // ... // / ProtectedFree string1 oDiv1.getElementsByClassName(string1); // ... // / ProtectedFree string2 oDiv1.getElementsByClassName(string2);
Thus, using the technique described above, an attacker can determine any pattern he needs for allocating and freeing memory on the heap. The complexity of the Memory Protection behavior when freeing memory is eliminated.
For the sake of interest, it is highly recommended to read in [1] as far as possible, by using the Memory Protection mechanism of work, bypassing ASLR.
The following mechanism, which we consider, can be called a successor of Memory Protection. It is called Memory Garbage Collector (MemGC), and was introduced in the new Edge engine in Win10. Subsequently appeared in IE11.
The goal of implementing MemGC is the same as that of MP, protection against exploits like UAF.
The guys from Microsoft write [3] that MemGC works in the same spirit as MP, but also scans the “heap”, in addition to the stack and registers, for references to protected object types.
But, of course, they are a little cunning, and the difference is not only in the additional viewing of the heap, but also in the implementation. Let us consider it in more detail [4].
MemGC uses a separately managed heap called the MemGC Heap (straightforward name). It is used to allocate memory to objects and garbage collection - in fact, the same Reclamation Sweep operation, only those memory areas that are not referenced in the MemGC Heap are also released. MemGC Edge JavaScript- Chakra ( JS-, ). , .
, MemGC, , «» ( Segments ), «» ( Pages ) 4096 . , ( Blocks ), , , :
12 – MemGC Heap ( [4] )
EdgeHTML/MSHTML DOM , , MemGC. MemGC «», Isolated Heap, , .
Those. , MemGC Isolated Heap, Memory Protector, UAF- ( 1, ).
MemGC :
.
1. .
MemGC EdgeHTML, , MemGC, , edgehtml!MemoryProtection::HeapAlloc<1>()
edgehtml!MemoryProtection::HeapAllocClear<1>()
, , , chakra!MemProtectedHeapRootAlloc()
.
13 – MemoryProtection::HeapAlloc<1>()
chakra!MemProtectedHeapRootAlloc()
(chunk) (Block) «» (Bucket), “root”. “Root”-, MemGC, / , (directly referenced) « ».
2. .
, edgehtml!MemoryProtection::HeapFree()
, , , chakra!MemProtectHeapUnrootAndZero()
.
14 – MemoryProtection::HeapFree()
chakra!MemProtectHeapUnrootAndZero()
(Block), , «root». «root» , « » , MemGC .
3. (Garbage Collection)
, , «root» , , chakra!MemProtectHeap::Collect()
. « » Reclamation Sweep, “root” , . Reclamation Sweep ( chakra!Memory::Recycler::ThreadProc
), chakra!Memory::Recycler::StartConcurrent()
.
– . , «root»- ( chakra!Memory::Recycler::BackgroundResetMarks()
).
, «root»- (.. , ), . chakra!Memory::Recycler::ScanImplicitRoots()
chakra!MemProtectHeap::FindRoots()
). , . , , .
, ( ….), [5].
, MemGC IE , Edge, mshtml.dll.
, MemGC Memory Protection:
MemGC , «» , , , – .. «»;
MemGC , «» ( , ZDI :) );
, Reclamation Sweep, , Memory Protection, . .
MemGC, ( , ) .
, – «», . , , .
IE 7 Windows Vista, Protected Mode. , IE10 Win8, Protected Mode – Enchanced Protected Mode (EPM).
, EPM .
IE Edge «» Loosely-Coupled IE (LCIE) , 8 IE. . -, . , Low Integrity Level IE AppContainer – Microsoft — Edge. Windows.
15 — Sandbox IE Edge ( [6] )
Windows ACL – access control lists – .
DACL (discretionary access control list – ), (ACE – access control entries): (, , ..). (access token) , SID- – : , .., DACL .
Microsoft , .. MIC – mandatory integrity control – ; .. integrity level – « » ( , « » ). , , . .
16 — Integrity Level IE
. RCE IE, , , , - .
AppContainer Windows. , , , . , , , , . , . SID- , “capabilities”, internetClient, location microphone, .
, ACE DACL, , SID «ALL APPLICATION PACKAGES». , , .
17 —
, , , , ( “Program Files” SID). , Mark Yanson [6], , , , .
:
-, , ;
capcom.sys, . «» :
18 — capcom
: SMEP, , . , .
Another example. Windows 10 dismhost.exe (Disk Cleanup), , . DLL %TEMP%, . , , .
JS Microsoft — IE 11 Edge – Chakra, JS . JS- JIT (just-in-time) , JS- , web-.
, JIT . , . :
JIT , .
JIT , xor, – “ constant blinding ”. ROP- , – “ insert NOPs ”.
? , , – .
ROP-, VirtualProtect(addr, size, flags, oldflags). Windows, -, .. , . : , , , .
x64, WinAPI fastcall, – rcx, rdx, r8 r9, – . , , : pop R + ret. rcx rdx , r8 r9 . “pop R”, R – x64 r8 — r15, pop – REX , , ret . , , :
81 C0 41 58 00 00 add eax, 5841h ___________________________________________ 41 58 pop r8 00 00 add byte [rax], al
: rax - , , , . , ...
:
rax , , 0xc358 :)
58 pop rax C3 ret
. JIT- JS , «».
function f(addr) { return addr + 0x5841; }
19 — : a. ; b. ROP . [7]
«» , .
JIT- , .
0000 mov rax, 2B5C990h 000A cmp rsp, rax 000D jg loc_2AE0034 0013 mov rdx, 500790h 001D mov rcx, 990h 0027 mov rax, 7FEF3435450h 0031 jmp rax 0034 ; --------------------------- 0034 0034 loc_2AE0034: 0034 mov rax, 23D61A8h 003E inc byte ptr [rax] 0040 jnz loc_2AE0049 0046 mov byte ptr [rax], 0FFh 0049 0049 loc_2AE0049: 0049 mov [rsp+20h], r9 004E mov [rsp+18h], r8 0053 mov [rsp+10h], rdx 0058 mov [rsp+8], rcx 005D push rbp 005F mov rbp, rsp 0062 sub rsp, 10h 0066 push rdi 0068 push rsi 006A push rbx 006C sub rsp, 38h 0070 xor eax, eax 0072 mov [rbp-8], rax
NOP- .
0000 mov rax, 33CC990h 000A cmp rsp, rax 000D jg loc_3160035 0013 mov rdx, 326890h 001D mov rcx, 990h 0027 mov rax, 7FEF3435450h 0031 jmp rax 0034 ; --------------------------- 0034 nop 0035 0035 loc_3160035: 0035 mov rax, 2FAC1A8h 003F inc byte ptr [rax] 0041 jnz loc_316004A 0047 mov byte ptr [rax], 0FFh 004A 004A loc_316004A: 004A mov [rsp+20h], r9 004F mov [rsp+18h], r8 0054 mov [rsp+10h], rdx 0059 mov [rsp+8], rcx 005E push rbp 0060 mov rbp, rsp 0063 sub rsp, 10h 0067 push rdi 0069 push rsi 006B push rbx 006D sub rsp, 38h 0071 xor eax, eax 0073 mov [rbp-8], rax
34h. .
0034 nop dword ptr [rax] 0037 nop dword ptr [rax+00h] 003B xchg ax, ax
NOP-, .
, .
— CFG Windows 8.1. [8].
, JIT- , , , , , — , .
, CFG, , — rax , . : JIT ROP, rsp , , rsp , , rax — , ( vtable!), add byte [rax], al
. .
mov rax, [rdi] ; this->vtable mov rbx, [rax+208h] ; ptr = vtable[idx] mov rcx, rbx ; _QWORD call cs:__guard_check_icall_fptr mov rcx, rdi ; this
JIT- JS. WARP Edge, JIT- JS [9].
– IE Edge — , . , — . , , . , , .
Source: https://habr.com/ru/post/311616/
All Articles