Chakra: Type Confusion Vulnerability – CVE-2016-7201

Last year in the month of September, the Project Zero team from Google disclosed vulnerabilities in the Microsoft JavaScript engine Chakra. CVE-2016-7200 and CVE-2016-7201 are two such bugs that caught the limelight. Even though it’s an old bug it is worth discussing their specifics. Both of these vulnerabilities went from PoC of vulnerability to PoC of exploitation to being fully integrated into well known exploit kits like Sundown, Neutrino and Kaixin. This makes it one of the first Microsoft Edge specific exploits to be integrated into an exploit kit. In this article we will be looking into the mechanics of CVE-2016-7201- Type Confusion vulnerability in FillFromPrototypes. For CVE-2016-7200 please refer our ThreatProtect article.

The vulnerability is classified as a type confusion bug. Meaning the code failed to verify the type of object that is passed to it. Type confusion can be very dangerous because sending wrong pointers or data to into the wrong piece of code can lead to code execution in some circumstances.

In our case the entry point of the vulnerability is via the FillFromPrototypes method. As the name suggests it is used to set the native elements of an array based on its prototype. FillFromPrototypes internally calls ForEachOwnMissingArrayIndexOfObject with the prototype of the target as its argument. If the prototype is an array the function assumes its a var array and fills the target array with entries corresponding to a var array. So if we manage to send a prototype that looks like an array but is actually native int array, It will treat the entries of the int array as pointers.

As per MDN the Proxy object can be used to define custom behavior for fundamental operations. Proxy object creation requires two things, target, this specifies the object we are proxying for and handler, a placeholder to define actions when certain events occur on the proxy object. One such event is getPrototypeOf, and we can have it return any prototype that we like for eg Native int. So to sum it up when ForEachOwnMissingArrayIndexOfObject tries to find the prototype of proxy object it will return Native int and perform var array operations on it. This allows an integer to be treated as an absolute pointer. A walk through of the vulnerability will paint a much clearer picture of what the bug does.

For our analysis we will use a PoC to replicate the issue. The code targets FillFromPrototypes via Array.prototype.shift. Shift removes the first element from the array and shifts the remaining elements and recalculates the array length. So [1,2,3,4] becomes [2,3,4]. Here the call sequence EntryShift->FillFromPrototypes->ForEachOwnMissingArrayIndexOfObject.

Project Zero PoC
Project Zero PoC

Our target array here is ‘b’. After the shift() operation, the print statement will crash the JavaScript engine.

Crash log on Windbg
Crash log on Windbg

The crash log is clearly shows the ChakraCore trying to load a quad word from an invalid address. Interestingly the value in register rdx = 0000777712345670, is the last two entries from array ‘a’. The application crashes out.

Chakra crash message
Chakra crash message

Lets check the contents of array ‘b’ before the crash.

Array 'b' before Shift
Array ‘b’ before Shift

The contents of the array are as expected. After the shift operation ‘0000777712345670’ has been added as the last element of the array so[2]) will result in an invalid address lookup. This shows that it is reading 8 bytes from the array assuming each entry in the array is a pointer. The expected behavior is for the last entry of array ‘b’ to be 0x44444444.

Array 'b' after Shift
Array ‘b’ after Shift

As per the ChakraCore code FillFromPrototypes calls ForEachOwnMissingArrayIndexOfObject on a loop till the prototype of said element is null. So on one of the iteration the array prototype check fails to detect NativeInt and reads values from array ‘a’ assuming it’s a var array.

FillFromPrototypes calling ForEachOwnMissingArrayIndexOfObject
FillFromPrototypes calling ForEachOwnMissingArrayIndexOfObject

In the code snippet above, prototype is updated via prototype = prototype->GetPrototype. Below is a code snippet of ForEachOwnMissingArrayIndexOfObject to determine the prototype from argument ‘obj’ and based on the result it will initialize array ‘arr’.

Checking object Type
Checking object Type

A point to note ChakraCore defines all types as numbers. So the first time ForEachOwnMissingArrayIndexOfObject() is called it determines that the array element is an object and checks if it has a valid object array.

b[0] - TypeIds_Object
Array ‘b’  object type
19 represents ‘TypeIds_Proxy’ which is expected, as the PoC manually sets the prototype to proxy.

Changing b's protoype
Changing b’s prototype

Now continuing with the loop prototype = prototype->GetPrototype triggers the getPrototypeOf event for the proxy object which is handled by the custom handler getPrototypeOf defined in the PoC. Now ForEachOwnMissingArrayIndexOfObject is called with prototype pointing to address of array ‘a’.

Customer handler
Customer handler

array ‘a’ is sent as an argument to ForEachOwnMissingArrayIndexOfObject . We can verify this using a debugger, clearly showing the contents of NativeIntArray ‘a’.

array 'a' as function arguement
array ‘a’ as function argument

it tries to read element at index 3 from array ‘a’ and calls SetItem() as an inline function to write it to array ‘b’.

New entry in array 'b'
New entry in array ‘b’

After which EntryShift overwrites the first element in ‘b’ by shifting the remaining elements, it uses the inline function SparseArraySegment<void *>::RemoveElement to do this. Below is the result after shift.

Array 'b'
Array ‘b’  after shifting

Notice the the missing ‘000002247892d000’. The address space remains the same but points to different values.

What does this say about the vulnerability? It gives us a way to read from any memory address in the process space. From an exploitation point of this is can used to find the base address of a target module through vftable, more of this is discussed in the following section. With ASLR a module may be loaded at any address but it’s offsets remain the same. So finding this address is the starting point for code execution in the exploit chain.

On its own exploiting this vulnerability is not enough for code execution. A PoC is available that exploits CVE-2016-7200(Microsoft Edge JavaScript Information leak) and CVE-2016-7201 to achieve code execution. A detailed article about CVE-2016-7200 is available on ThreatProtect. The PoC consists of two main functions PutDataAndGetAddr(CVE-2016-7200) ,TriggerFillFromPrototypesBug(CVE-2016-7201), and a couple of helper functions to read values from memory.

PutDataAndGetAddr is used to obtain the address of array ‘x’ stored on the heap. Once this is obtained we will use the TriggerFillFromPrototypesBug to create a fake DataView object in the data slots of the array ‘x’. We use DataView object because it lets you read and write to a memory location without worrying about endianess of the data. Once it is created we can find the base address for chakra.dll. From here the code walks the stack to find the return address for ScriptEngine::ExecutePendingScripts.

Stack Walk
Stack Walk

After that it writes the shellcode to an array object and uses PutDataAndGetAddr to find its address on the heap.

Arranging Shellcode
Arranging Shellcode

Now it builds the ROP chain to set execute permission on the memory pages where the shellcode stored using VirtualProtect and then return to the stack where it is pointing to the address where the shellcode is stored. We overwrite the stack where the return address of ScriptEngine::ExecutePendingScripts is stored with the addresses of the ROP gadgets.

ROP chain and return address overwrite
ROP chain and return address overwrite

These are very interesting vulnerabilities for study. With the implementation of ASLR attackers have the additional task of finding the address of the target module. In this case there is no need for any heap manipulation by spraying, CVE-2016-7200 provides the exact location on the heap. Usually exploits try to find ROP gadgets dynamically, while in our case they are hard coded, though this reduces the code size and detection it has its demerits. An important point to note here is the exploit bypasses CFG an exploit mitigation technique focusing on indirect function calls . ThreatProtect will continue provide more coverage on these vulnerabilities. We highly recommend our clients to scan their network using QID 91300 to detect vulnerable versions of Microsoft Edge.

Microsoft Edge: Type Confusion in FillFromPrototypes
CVE-2016-7200 & CVE-2016-7201 (Edge) and Exploit Kits
PoC – Exploit

Leave a Reply

Your email address will not be published. Required fields are marked *