tl;dr 2 element overflow in Array when jit compiled
Created by: sherl0ck
About a couple of weeks ago, teambi0s conducted InCTF, an International level CTF and I had the opportunity to create a couple of challenges -
ateles . Now let’s delve into
There were 2 files provided -
ateles_handout_large contains a non-debug js shell built with the provided patch, and also the environment the challenge is running on, in the remote server.
On the remote server sandbox was disabled. Also,
docker_stuff/firefox/vulnProfile/prefs.js file. The built firefox browser runs with the
vulnProfile profile. (
ateles_handout.zip contains the patch file that is to be applied.
Analyzing the patch
The patch file is pretty short
diff -r 3d02a4c69a81 js/src/jit/CodeGenerator.cpp
The function’s in
CodeGenerator.cpp are called when Ion tries to JIT compile code and wants to look up how a specific piece/segment of code is to be compiled.
In this case the patched code is a part of the function
visitInitializedLength. This is called when trying to JIT compile a segment of code that accesses the initialized length of an Array. For example while compiling the following code snippet :
arr[idx] = 12;
While trying to compile this code, first the index (idx) is checked against the initialized length to verify that it is smaller than it. The code for this is emitted by the function
visitInitializedLength. As we can see, initially all it did was to load the initialised length into a register. In the patch the initialized length is loaded into the register and then
2 is added to the register. Thus, now when the JIT’ed code is executed, the index will be checked against the initialized length plus 2, thus giving us access to 2 more elements than what the
initializedLength field of the Array specifies.
Ok, that was straightforward, but how can we trigger a crash with this? Well, the easiest (and only one I could think of :)) way I found was to use this 2 element overflow to overwrite the
shape field of the
JSObject that follows the vulnerable
Array. Here is a crashing sample
blah = new Array()
Here we first create a Array that holds 2 objects, namely another Array and also a
Uint32Array. These are allocated one after other in the nursery heap (firefox’s primary heap for short lived objects).
As is obvious, both the elements lie just one after another. Thus we can use the 2 element overflow in the
blah array to change the group and the shape of the following object, which is the
Uint32Array. In this crashing sample, we overwrite the
group pointer of the
Uint32Array with a float value 1.337 which leads to it becoming an invalid pointer and thus crashing when we try to access this object (When we try to access an object the group and the shape pointers are read which leads to spidermonkey trying to de-reference an invalid pointer)
Right so now that we are clear with what the bug is, we can go ahead and try to achieve arbitrary read-write. With this overflow, we can change the group and shape pointer of any object to that of any other object. So basically we can hijack the identity of an object to make spidermonkey think that it’s of a different type, which gives us the power to create arbitrary type-confusions in the memory.
So now the question is which object should we confuse with which other object to make our job of exploitation easier? After pondering over it for some time I used kind of the same technique that I used while trying to exploit CVE-2019-11707. Here I initially create a
Uint8Array and overwrite the
shape to that of a
Uint32Array. Now let’s assume that the size of the
Uint8Array is 16. That means that the size of the buffer will be
16*1=4. Now we overwritet the group and shape to that of
Uint32Array. But here each element is of 4 bytes size. Thus the size of the buffer becomes
16*4=64, giving us a sufficiently large overflow. Thus we converted our 2 element overflow to a larger, more controlled overflow in a typed array!
From here the exploitation can proceed in pretty much the same way as the one in my previous post on CVE-2019-11707. We create an array of array buffer’s and set the underlying buffer of the
Uint8Array (which we will corrupt) to any one of those array buffers. Then with this overflow, we can change the size field of the following array buffer to a larger one which will lead to us getting arbitrary read-write.
After this, the exploitation method is ditto same as the previous post. Please refer the above mentioned post for more details. Let me give a gist of the exploit here -
- Let’s say that we have some consecutive array buffers in the memory - A-B-C-D
- We use the overflow in ‘A’ to overwrite the length field of ‘B’ array buffer and make it to a large size.
- We use this to leak the address contained in the metadata of ‘C’.
- The leaks contain - The address of an TypedArray created on this array buffer and the address of this array buffer.
- Now we again use the overflow in ‘B’ to overwrite the address of the DataBuffer of ‘C’ to the address of the data buffer of a
- Now we create a
Uint8Arrayview on the
CArrayBuffer. Thus it’s buffer is actually the address of the data buffer of the
- Thus we control the DataBuffer of the leaker array and can point it to any address to achieve arbitrary read-write.
Now for getting code execution (This is also the same as the previous post but here’s a gist anyway) -
- Our aim will be to overwrite the the
addPropertyfunction pointer in the
- We will overwrite it with a stager shellcode that will mprotect a second region with rwx permission. This second region will be where we have our actual shellcode.
- The stager shellcode will be JIT compiled. Thus we place it in a function and call it over and over to jit compile it.
- We leak the address of this using our arbitrary read primitive.
- We also write a shellcode for execve(‘/usr/bin/xcalc’,0,0) and also leak it’s address.
- We then overwrite the
addPropertyfunction pointer with the address of the shellcode.
- To trigger the chain, we add a property with the value of the property as the address of our actual shellcode buffer.
- When this happens, the address of the shellcode buffer goes into the
rcxregister and our stager shellcode is called.
- The stager shellcode mprotects the page pointed to by
rcxto make it rwx and then jumps to that region.
- In the now rwx region, we execute the shellcode for execve(‘/usr/bin/xcalc’,NULL,NULL);
I just copy pasted the code for this whole part directly from my previous exploit. There was only a small change - The previous exploit was written for firefox 66 and the one given in the challenge was the latest version, pulled just a couple of day’s prior to the CTF. Thus there was a slight change in the structure of
ArrayBuffer. Earlier the right shifted value of the data pointer of the ArrayBuffer was in the memory. Now, the data pointer is stored as it is. So only a slight modification is required in the previous exploit.
The full exploit can be found on github
After finishing the exploit, we have to host the whole thing (wrapped in a html file) on a server, and send the link to the remote service.
And the flag was