[bkpCTF-2015] quincy-adams (kspace) write-up

This is a write-up for quincy-adams challenge, which is the second part of 3-chained pwnable challenge from Boston Key Party CTF last weekend. You can read about the other parts here: quincy-center, braintree.

The binaries were packaged into a tar ball.

The MBTA wrote a cool system. It’s pretty bad though, sometimes the commands work, sometimes they don’t…
Exploit it. (kspace flag) 8899

The goal is to get “kspace” flag by exploiting the kernel space process.


Before we dive in, let’s look at what is really going on.

We can think of each process (tz, kspace, uspace) as a separate privilege ring, as their name suggest.

  • tz (TrustZone?) – a “hypervisor” layer that implements the “hypercalls” (we haven’t really reversed this too much).
  • kspace – a kernel layer that implements the “syscalls”; it maintains the files array and performs the actual tasks such as listing files, removing files, sleeping, and cat’ing (open & read).
  • uspace – a user layer that implements the interface that the user interacts with; it parses the commands and calls the appropriate “syscalls”.

We would start from the uspace to get an arbitrary code execution on uspace process (as we did in previous write-up), then exploit the kspace to allow us to perform an attack against tz. Shared memory is arranged by tz, such that each layer will get its own memory “space” to pass the arguments to {sys, hyper}calls.

kspace syscall_handler (sub_401180)


We can see that the syscall numbers, which is stored on user_space[0], match with what we saw on uspace.

  • do_ls loops through the file_array list and prints out the name of the file.
  • do_rm finds the file in file_array with a given filename, and deletes it.
  • do_create adds a file into file_array up to 16 files. It looks for the spot/bin in the array by checking if the filename is null.
    This also initializes the file structure:

    • open_file, read_file, and delete_file are the function pointers.

      file struct

  • Note that syscall 95 and 99 are sleep and exit, respectively, which are not that interesting :p
  • do_read finds the file in file_array with a given filename, and if the file is “open”, its content is copied to the output buffer (2nd argument).
  • do_open finds the file in file_array with a given filename, and changes its open state.
    • Only up to 9 files can be open simultaneously.

There is a bug with file creation and deletion, where the number of files in the list gets incremented when creating a file, but it does not get decremented when being deleted. Thus, we can only create up to 16 files and we can’t create any more file even if we delete some. This didn’t really affect the exploitation, however :)

If you followed carefully, you’d have noticed that we didn’t go over the mysterious syscall 92 (sub_402380).

This operation, unlike other ones, does not process the user input/arguments here. Instead, it forwards these arguments to tz via the “hypercall”.

syscall 92 (kspace)


To be more precise, the 3 arguments are stored starting at kernel_space + 48, and the hypercall takes 3 arguments of the hypercall number (92), 0, and the pointer to the arguments (kernel_space + 48).

So what does this hypercall do? Let’s take a look at tz binary now.

hypercall handler (tz)

As we can see above, sub_401560 is the hypercall 92 handler.
It doesn’t really do anything too fancy. The function performs a very simple “encryption” of data.

hypercall 92 handler


It basically xor‘s the bytes at src with the key “charlestown isn’t that skeytchy.” and stores the result to dest.

There is no restriction on the memory address for dest (other than checking if it’s 0x100000000), which allows us to do arbitrary (xor) write. By preparing the src buffer with already xor’d values, we can write any value we want to any memory location.

So, we can use this capability(?) to write useful data structure (such as a function pointer) to gain arbitrary code execution!

Just like uspace, kspace has NX disabled, so we can put our shellcode somewhere and jump to it.


The attack plan is as follows:

  1. Create a file, with the content being the shellcode we want to run as kernel.
  2. Overwrite a function pointer (open_file) for the first file (which is the one that we just created in step 1) with the pointer to our shellcode.
    • The file_array is located at kernel_space + 0x3E800 == 0x90003e800.
    • According to our struc_files struct, file_array + 0x8 points to the files array.
    • Thus, the first file structure will be located at 0x90003e808 and according to our struc_file, its content is located at the offset +0x9.
    • The first file’s content == shellcode == 0x90003e800 + 0x8 + 0x9 == 0x90003e811.
    • The first file’s open_file function pointer is at +0x118 from the file structure, which makes its location 0x90003e920.
  3. Invoke a syscall #93, which opens a file.
    • At this point, we will tell it to open our file which has corrupted open_file function pointer — thus, calling into our shellcode.
Note that since we are using syscal #92 (encrypt) to perform an arbitrary write to memory, we have to “encrypt” the value we want to write beforehand such that it will get “decrypted” when it writes. The filename we used is “lol”.
Our exploit code looks fairly similar, but we now create a file called ‘lol’ first with the shellcode. (The shellcode is the same as what we used for uspace.)

Then, we trigger the bug with our stage1 code.


So far, we have triggered a uspace bug to call a syscall (92) in kspace, which does a hypercall (92) in tz, which allowed us to perform an arbitrary memory write in kspace memory.

Amusingly, we will be using the same primitive to get a shell under tz context in the next series.



You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *