Orange Deportation Simulator (pwn)

MAKE FLAGS GREAT AGAIN! HELP ME DEPORT THE ORANGES!

2MB
Open
2KB
Open
20KB
Open

checksec:

Arch:       amd64-64-little
RELRO:      Full RELRO
Stack:      No canary found
NX:         NX enabled
PIE:        No PIE (0x3ff000)
RUNPATH:    b'.'
Stripped:   No

Here's the code of the program:

Scanning through the program, we realise that it allows us to:

  1. Malloc a chunk of any size we want only once

  2. Check any bit of our malloc'd chunk to see if it is set

  3. Perform an OR operation on any bit of the malloc'd chunk

Essentially, the seat number we provide is represented as:

(byte offset * 8) + bit_index

Where the byte offset is the byte we want to read/OR relative to the start address of the chunk, and bit_index is the bit within that byte that we want to read/OR.

The key vulnerability lies in the fact that we can provide an arbitrarily large size value, so large that when we do malloc(size), it returns a null pointer (0) due to it being unable to fulfill the request. This results in us having bus = 0. Inputting a negative number like -8 will achieve this.

When bus = 0 , and size is extremely large, notice that the read and OR functionality provided by the program essentially allows us to read and OR any bit we want in the program.

Now the only address we know is the binary's address since PIE is disabled. Therefore our goal is to call win using the binary as a starting point. Remember that we do not have an arbitrary write primitive, but we have arbitrary read and arbitrary OR.

We can use our arbitrary read to read any entry in the binary's .got.plt section in order to get a libc leak.

How can we execute the win function?

I initially considered overwriting the exit functions, but this won't work since the program calls _exit() instead of exit(), so the exit functions aren't called.

Upon reading this article https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md it seems that printf custom conversion specifiers are the way to go, because the two things it relies on, namely:

  1. __printf_function_table and

  2. __printf_arginfo_table

initially have all their bits set to 0, so an arbitrary OR primitive will allow us to redirect execution to the win function.

How do we do this?

On reading the article, we learn that __printf_function_table must have a non-null value, and __printf_arginfo_table must have our function's address written in one of its slots corresponding to the conversion specifier we are using. (in this case we are using %lld . The l type modifier doesn't matter, and d has a hex value of 0x64. So we want to write the address of win to __printf_arginfo_table + 0x64 * 8 ). The 8 is because each address is 64 bits / 8 bytes.

But how do we find the offsets of __printf_function_table and __printf_arginfo_table in libc? When I tried to find them by doing p & __printf_function_table in GDB it cannot find the symbol. To find the offset, we have to look at the code and assembly of the register_printf_specifier function in libc:

And note that we actually have the symbol for the register_printf_specifier function:

With this we can look at the assembly of register_printf_specifier, and try to infer the offsets of __printf_function_table and __printf_arginfo_table.

Here's a small snippet of it:

With this we can find that the offset of __printf_function_table is 0x1d4980 and the offset of __printf_arginfo_table is 0x1d3890.

Now let's summarise the exploit:

  1. Enter a negative number as size to obtain bus = 0

  2. Obtain a libc leak by using the arbitrary read primitive on the binary's .got.plt entry

  3. Calculate the addresses of __printf_function_table and __printf_arginfo_table

  4. Use arbitrary write to make __printf_function_table a non-null value and set the content in __printf_arginfo_table + 0x64 * 8 to be the address of the win function.

  5. Trigger a printf again and execute the win function

Below is my script:

Last updated