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
and navarint
. ateles
. Now let’s delve into ateles
!
Attachments
There were 2 files provided - ateles_handout.zip
and ateles_handout_large.zip
.
The 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, javascript.options.ion.offthread_compilation
is set to false, to increase the realibility of exploits. These settings can be tweaked in the docker_stuff/firefox/vulnProfile/prefs.js
file. The built firefox browser runs with the vulnProfile
profile. (docker_stuff/firefox/vulnProfile
)
The ateles_handout.zip
contains the patch file that is to be applied.
Vulnerability
Analyzing the patch
The patch file is pretty short
1 | 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 :
1 | 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.
PoC
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 group
and shape
field of the JSObject
that follows the vulnerable Array
. Here is a crashing sample
1 | 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).
1 |
|
As is obvious, both the elements lie just one after another. Thus we can use the 2 element overflow in the blah[0]
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)
Exploitation
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 group
and 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
Uint8Arry
(sayleaker
) - Now we create a
Uint8Array
view on theC
ArrayBuffer. Thus it’s buffer is actually the address of the data buffer of theleaker
array. - 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
addProperty
function pointer in theClassOops
structure. - 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
addProperty
function 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
rcx
register and our stager shellcode is called. - The stager shellcode mprotects the page pointed to by
rcx
to 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
inctf{r3sh4ping_th3_ateles_for_sh4ping_up_a_pr0t3le$}