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
2
3
4
5
6
7
8
9
10
11
12
13
14
diff -r 3d02a4c69a81 js/src/jit/CodeGenerator.cpp
--- a/js/src/jit/CodeGenerator.cpp Thu Sep 19 06:59:14 2019 +0300
+++ b/js/src/jit/CodeGenerator.cpp Fri Sep 20 15:35:07 2019 +0530
@@ -9177,7 +9177,9 @@
void CodeGenerator::visitInitializedLength(LInitializedLength* lir) {
Address initLength(ToRegister(lir->elements()),
ObjectElements::offsetOfInitializedLength());
- masm.load32(initLength, ToRegister(lir->output()));
+ Register out = ToRegister(lir->output());
+ masm.load32(initLength, out);
+ masm.addq(Imm32(2), out);
}

void CodeGenerator::visitSetInitializedLength(LSetInitializedLength* lir) {

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
2
3
4
5
6
7
8
9
10
11
12
blah = new Array()
blah.push(new Array(1.1,1.1))
blah.push(new Uint32Array(0x10))

function trigger(a1,a2){
blah[0][a1]=1.337;
for (let i=0; i<100000; i++){}
}

for(var i=0;i<100;i++) trigger(0)
trigger(2)
blah[1]

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

gef➤ x/10xg 0x28a7da500550 <-- `blah` Array

0x28a7da500550: 0x00002d73b8b7e820 0x00002d73b8b8fba0
0x28a7da500560: 0x0000000000000000 0x000028a7da500580
0x28a7da500570: 0x0000000200000000 0x0000000200000006
0x28a7da500580: 0xfffe28a7da5005d8 <-+ 0xfffe28a7da500618 <------+
0x28a7da500590: 0x2f2f2f2f2f2f2f2f | 0x2f2f2f2f2f2f2f2f |
| |
blah[0] (new Array) | blah[1] (new Uint32Array) |
+----------+ |
gef➤ x/8xg 0x28a7da5005d8 | |
0x28a7da5005d8: 0x00002d73b8b7e850 0x00002d73b8b8fba0 |
0x28a7da5005e8: 0x0000000000000000 0x000028a7da500608 |
0x28a7da5005f8: 0x0000000200000000 0x0000000200000002 |
0x28a7da500608: 0x3ff199999999999a 0x3ff199999999999a |
gef➤ +--------------------------------------------------------+
|
0x28a7da500618: 0x00002d73b8b7e9d0 0x00002d73b8ba5330 |
0x28a7da500628: 0x0000000000000000 0x0000564e7c87ff98
0x28a7da500638: 0xfffa000000000000 0xfff8800000000010
0x28a7da500648: 0xfff8800000000000 0x000028a7da500658

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 (say leaker)
  • Now we create a Uint8Array view on the C ArrayBuffer. Thus it’s buffer is actually the address of the data buffer of the leaker 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 the ClassOops 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.

flag.png

And the flag was

inctf{r3sh4ping_th3_ateles_for_sh4ping_up_a_pr0t3le$}