Points: 384 Solves: 25 Category: Exploitation
Intro
This program is based off the CaNaKMgF
pwnable challenge, except that CaNaKMgF_remastered
has full RELRO enabled and PIE enabled. Interestingly, during the CTF, I had already solved CaNaKMgF
using a technique that bypassed both mitigations, so I re-used the same exploit for CaNaKMgF_remastered
and was able to get the flag.
Reversing
When we run the program, we are presented with the following menu.
The program is very simple. It allows us to allocate heap chunks of different sizes, which it places in a global array in the .BSS called alloc_list[]
.
We can write data of our choosing into these chunks and the program ensures that the length of our data fits inside the chunk we’ve allocated for it.
We can specify chunks from this list that we would like to print out the contents of.
Similarly, we can also free any chunks in this list.
There was also some functionality to read limited files off the remote server when one selected the “Pray for Allah” option in the CaNaKMgF
binary, but this function no longer worked in CaNaKMgF_remastered
, and I didn’t use it in my exploit for CaNaKMgF
, anyway.
Exploit
The main vulnerability in this program is that when a chunk is freed, the associated pointer to the chunk is not removed from alloc_list[]
. This allows us to perform use-after-frees and double-frees which we can abuse to corrupt the heap and gain code execution.
To get a libc leak, we can exploit the UAF to allocate 2 small chunks, free the first one, and then print its contents out, since we know the FD
and BK
pointers of our free’d small chunk will be populated with a pointer to an offset from main_arena
in libc. (The purpose of allocating a 2nd small chunk, is to prevent top chunk consolidation.)
To get control of RIP
, we can perform a fastbin attack to get malloc()
to return an almost arbitrary pointer, overwrite __malloc_hook
, and then call our overwritten __malloc_hook
function pointer by triggering a double free memory corruption error.
To perform the fastbin attack, we will allocate 2 fast chunks of size 0x68
bytes, and free the 2nd one, then the 1st one, and then the 2nd one again, abusing the fact that we can double-free fast chunks, so long as the head of the freelist that their fast chunk size is associated with, is not the same as the chunk that is being free’d.
from malloc.c
:
So, right now our fastbin looks like this:
[HEAD]->D->C->D->NULL
Now, we will allocate another fast chunk of the same size as D, so that D is popped off this freelist and used to service our malloc()
request.
Since we can also specify the contents of chunks we allocate, we will overwrite the first qword of this chunk with the address of our target that we would like malloc()
to return.
This corrupts the FD
pointer of chunk D, a pointer to which, still exists in the singly linked freelist!
We can select any address to overwrite the FD
pointer with, subject to certain constraints.
from malloc.c
:
To satisfy these constraints, we will abuse the fact that we can make FD
point to misaligned addresses as long as they satisfy the valid size metadata field constraint. In our case, since we are targeting fast chunks of size 0x68
, the following is a valid address should do the trick.
At this point, our freelist should now look like this:
[HEAD]->C->D->{target addr}
After two more allocations, our target address should now be at the head of this freelist:
[HEAD]->{target addr}
Then, the next memory allocation of size 0x68
should return a pointer to our target address+0x10
and since we can control the contents of chunks we allocate, we will simply overwrite __malloc_hook
with a “magic” one gadget RCE address.
Once we trigger an actual double free corruption error, the program should now spawn a shell.