# Challenge description
```Fullchain
Do you have what it takes to pwn all the layers?```
* Category: Pwn* Points: 326* Solves: 15
## Introduction
Last weekend it was GoogleCTF weekend again :). I didn't have much time tospend on it this year; but I took a quick look at `Fullchain`. This challengecomes with two patches for chrome and a linux kernel module (with source).Together these introduce three vulnerabilities.
1. An out of bounds write in a TypedArray2. Mojo out of bounds/read write3. Linux kernel UAF
Reaching the flag required a full chain of three exploits.
## Exploiting the renderer to activate Mojo bindings
The bug is introduced in `v8_bug.patch`:
In `TypedArrayPrototypeSetTypedArray` an important bounds check is removed:
```CheckIntegerIndexAdditionOverflow(srcLength, targetOffset, targetLength)    otherwise IfOffsetOutOfBounds;// CheckIntegerIndexAdditionOverflow(srcLength, targetOffset, targetLength)//     otherwise IfOffsetOutOfBounds;```
This removes the bounds check when calling `set` on a TypedArray. The followingwill then crash a debug build of `d8`:
```d8> var a = new Uint8Array([0,0])d8> a.set(a, 1)abort: CSA_ASSERT failed: Torque assert 'countBytes <= target.byte_length -startOffset' failed [src/builtins/typed-array-set.tq:270][../../src/builtins/typed-array-set.tq:92]```
Since we can write out of bounds of the data buffer we immediately becomeinterested to discover what lies right after the data buffer of a TypedArray:
```var a = new Uint8Array(new Array(4).fill(0xab));%DebugPrint(a);
// Lots of output, but the important part is:- map: 0x24fb082c24d9 <Map(UINT8ELEMENTS)> [FastProperties]- data_ptr: 0x24fb08109c34
gef➤  x/xg 0x24fb08109c340x24fb08109c34:	0x082c24d9abababab```
We see that right after the data buffer is the [compressed](https://blog.infosectcbr.com.au/2020/02/pointer-compression-in-v8.html)`map` pointer. Among other things, this `map` holds information about thedatatype of the array.  Let's see what happens if we were to change this toanother type.  (In a non-debug build of `d8` otherwise we will trigger debugassertions)
```var b = new Uint32Array(new Array(4).fill(0xdeadbeef));%DebugPrint(b) - map: 0x24fb082c3181 <Map(UINT32ELEMENTS)> [FastProperties]
var c = new Uint8Array([0x81, 0x31]);  // Lower two bytes of b's mapa.set(c, 4)                            // Overwrite a's map
d8> a2880154539,136327553,134226477,134518561```
Now `a` is still the same length, but all the elements are 32 bit. Thus thedata buffer is now assumed to be 16 bytes. This allows an OOB read/write onthree 32 bit values after `a`.
An interesting memory layout happens if we allocate an ArrayBuffer right afteran TypedArray:
```var a = new Array(64).fill(0xab);var b = new Uint8Array(a)var c = new Arraybuffer(4);
%DebugPrint(b) - data_ptr: 0x31ef08109958
%DebugPrint(c);DebugPrint: 0x31ef081099e1: [JSArrayBuffer] - backing_store: 0x55555563c410
// The distance between the data buffer and the JSArrayBuffergef➤  p/x 0x31ef081099e1 - 0x31ef08109958$2 = 0x89gef➤  p 'v8::internal::TorqueGeneratedJSArrayBuffer<v8::internal::JSArrayBuffer, v8::internal::JSObject>::kBackingStoreOffset'$3 = 0x1cgef➤  p/d (0x88+0x1c)/4$4 = 41```
Thus we can modify the backing store of the ArrayBuffer by writing to fields41 and 42 of the corrupted TypedArray.
However, so far we only encountered compressed pointers but the top bits of thispointer are also in the memory right after the corrupted TypedArray:
```gef➤  x/xw 0x31ef08109958 + 27*40x31ef081099c4:	0x000031ef```
And in a real chrome process (this is where we leave the `d8` shell) we can thenfind a chrome pointer at `0x000031ef00000040` to break ASLR
Following along with [ProjectZero](https://googleprojectzero.blogspot.com/2019/04/virtually-unlimited-memory-escaping.html)it is now possible to enable the Mojo bindings and reload the page.
Follow the same offsets as the blog from the `g_frame_map`, the magic offset to`RenderFrameImpl::enabled_bindings_` can be found by disassembling the functionwhich checks if Mojo bindings should be enabled:
```gef➤  disassemble 'content::RenderFrameImpl::DidCreateScriptContext(v8::Local<v8::Context>, int)'0x000056049f6e2369 <+89>:	test   BYTE PTR [rbx+0x444],0x2```
All that's left for this stage is to write the `BINDINGS_POLICY_WEB_UI` bit tothe address we found and reload the page. We then have the `Mojo` object in ourjavascript context, which we need for the next stage.
## Code execution outside of the sandboxThe bug is introduced in `sbx_bug.patch`, which adds a `CtfInterface` withthree functions:
```cppvoid CtfInterfaceImpl::ResizeVector(uint32_t size,                                    ResizeVectorCallback callback) {  numbers_.resize(size);  std::move(callback).Run();}
void CtfInterfaceImpl::Read(uint32_t offset, ReadCallback callback) {  std::move(callback).Run(numbers_[offset]);}
void CtfInterfaceImpl::Write(double value,                             uint32_t offset,                             WriteCallback callback) {  numbers_[offset] = value;  std::move(callback).Run();}```
There are no bounds check on anything here. Again we can read/write out ofbounds, this time to everything after the `numbers_` vector.
This `CtfInterface` consists of three pointers:
1. vtable (Note: the 12 bottom bits are fixed)2. vector data\_start3. vector data\_end
If we were to allocate a `CtfInterface` with a vector of size 4, we get inmemory:
1. `0x...4e0`2. `v`3. `v + 0x20`
A possible way gain code execution is thus to allocate a `CtfInterface` and scansome memory following the vector for this pattern. This immediately gives usthe address of our vector (breaking heap ASLR) and a vtable pointer (breakingchrome ASLR and allowing RIP control).
Ofcourse the vector may not always be located in memory before the`CtfInterface`, but we can just keep allocating `CtfInterfaces` in a loop untilwe have the desired layout.
To pop the shell I then fake a `CtfInterface` vtable (of which I modify the`resizeVector` function pointer). And then write a small ropchain to `execve``/bin/sh`. Since `rax` points at the vtable the `xchg rsp, rax` gadget isuseful to pivot the stack.
I had some issues with my ropchain getting overwritten so in my exploit you'llsee that I first scan for some unused (zeroed) memory to write thevtable+ropchain at.
Now that we have a shell as a normal user we only need to get root.
## The home stretch: Getting root
We get the source to a kernel module which is inserted into the running kernel.
The module adds a device: `/dev/ctf` with which we can interact fromuserspace to:
* open
Allocates a zero initialized `ctf_data` struct and saves it in the files struct`private_data`
* read/write
Read/Write an user specified amount of bytes from `ctf_data->mem` touser/kernel space
* ioctl
Allocate/free a buffer of user controlled size in kernel space. Saves theallocated buffer in `ctf_data->mem`
* close
Free the `ctf_data->mem` buffer
However on freeing `ctf_data->mem` the pointer is not zeroed out. Allowing aUAF on this allocation.
If we allocate and free a buffer of size `0x10`, we can reallocate this memorywith another `ctf_data` struct. We can use this to create the following memorylayout (which is kind of hard to describe in words so I drew a picture):
![memory](pic.png)
I've drawn the `file` structs as red boxes and put a letter representing apointer in each `private_data`.
Now two things are interesting:
1. Reading from fd1 we get a pointer. (`C`) Because of the reallocation trick   this is the same pointer as is stored in fd3's `private_data`2. By writing to fd1 we can control fd2's `ctf_data`. This gives us arbitrary   read/write in the kernel.
Because we have a leaked heap pointer and arbitrary read/write we can scanmemory for fd3's `private_data`, since this should be at offset `0xc8` from the`file` struct we can filter on this to eliminate most false positives.Sometimes this still hits a false positive but it's a CTF so it's good enough(and it wouldn't be hard to perform more validation or abort)
After locating fd3's `file` struct we can break ASLR by reading a kernelpointer from this struct. We gain RIP control by overwriting the `f_ops`. Ioverwrite the `ctf_write` function with `set_memory_x`, then:
```cwrite(fd3, (char *)1, 1);```Becomes:```cctf_write(struct file *f, const char __user *data, size_t size, loff_t *off)```Which becomes (because of the function pointer hijack)```cset_memory_x(f, 1)```Which means we now have an RWX page in the kernel at fd3's `file` to drop ourshellcode. We can then obtain root with the classic`commit_creds(prepare_kernel_creds(NULL))` shellcode.
Use the arbitrary read/write to cleanly restore fd3's `file` and then `execve`into `/bin/sh`, finish with `cat /dev/vdb` for the flag!
Note that the target did not have network connectivity, so I dropped my kernelexploit the low-tech way by pasting it into the shell as base64 :)
See also the exploits:* [Chrome](exploit.html)* [Linux](exploit.c)
```CTF{next_stop_p2o_fda81a139a70c6d4}```