HeapBaby

Description

I honestly didn't copy the description and they always don't open the challenges after the CTF, so that's that.

I solved this challenge after the CTF ended, so it isn't tested on the remote server.

Also, this challenge have no right to include 'baby' in the name...

Hint: House of Spirit

16KB
Open
2MB
Open

Solution

TL;DR

  • Create the 11th note to overflow the note_list global variable into note_cnt

  • Perform tcache house of spirit attack by creating a fake chunk on heap

  • Delete notes to decrement ptr stored in note_cnt. Decrement ptr till it points to the fake chunk

  • Read index 12 (gift) to get stack leak, use stack leak to find username buffer (or any buffer in main)

  • Get libc leak by freeing 8 chunks (UAF)

  • Free any index < 11, the free index 11 (note_cnt) to free the fake chunk, then free index where you wrote the fake chunk

  • Perform tcache poisoning with the freed fake chunk (need get key for ptr since glibc 2.35 is used). Overwrite fwd pointer of freed fake chunk with address of username buffer

  • Now perform BOF in username buffer, then perform simple ROP

  • Win

Initial Analysis

Decompiling the binary with ghidra, there are 3 main functions of the binary: create note, print note, delete note.

Looking at note_list in gdb, we can see it can store 10 address. The 11th address will be stored in note_cnt.

viewing note_list

We also see that gift is right after note_cnt.

viewing gift

If we create 11 notes, we can see that note_cnt indeed got overwritten with a malloced ptr.

overwritten note_cnt

Stack Leak

Since the note_cnt is very high (because it is an address), we can now read index 12, which contains our gift. This allows for a stack leak and we can calculate the address of username.

Libc Leak

Looking at the delete function, we note that notes stored in index >= 11 will have a Use After Free (UAF) vulnerability, so we can leak the fwd of a chunk that is freed into unsorted bin, which would give us a libc leak.

To free a chunk into unsorted bin, we can allocate 8 chunks of the same size, then free all 8 chunks. The first 7 chunks that are freed should go into tcache bin, while the last would go into unsorted bin :)

leaking libc code
libc address leaked

House of Spirit Attack

Now we most importantly need an arbitrary write. To do this, we can utilize Tcache House of Spirit since there is no heap overflow or anything else we can leverage on.

Tcache House of Spirit attack: "Frees a fake chunk to get malloc to return a nearly-arbitrary pointer." https://github.com/shellphish/how2heap/blob/master/glibc_2.35/tcache_house_of_spirit.c

So we can create a fake chunk on the heap. I created a fake chunk in index 10, with size of 0x140 containing DDDD.

create fake chunk code

We can see our fake chunk in the note of index 10:

fake chunk creation

Now that we have a fake chunk, we need to free it. Notice how note_cnt is always incrementing and decrementing whenever a note is created and deleted? We can utilize the pointer at the location by calling the delete function with a note index that contains 0 as an address, so it does free(0) which does nothing, but it also decrements our pointer at note_cnt by 1.

The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation. If ptr is a null pointer, no action occurs. https://pubs.opengroup.org/onlinepubs/7908799/xsh/free.html

manipulating note_cnt ptr

We can free the fake chunk now, but we first need to free another chunk so that our freed fake chunk's fwd ptr points to another freed chunk. This fwd ptr is the one we will be overwriting to point to any address to get arbitrary write.

We will also need to leak this fwd ptr to get the obfuscation key needed for our own ptr to work.

free fake chunk code

The deobfuscate function for deobfuscating the fwd ptr is referred from https://ctftime.org/writeup/34804

freed fake chunk with fwd ptr

Now we can create a note with size of 300 bytes, then overwrite the fwd ptr of the fake chunk with our own. Remember that the ptr needs to be obfuscated, so we need to xor the key with the address we want to write to, then overwrite the fwd ptr with this obfuscated address (basically tcache poisoning).

Afterwards, we create a note of size 300 bytes to clear the first tcache ptr, then the next note of size 300 bytes we create will write to our address!

We can try to write a bunch of Z's to note_list to check if it works.

arbitrary write to note_list example code
overwritten note_list

One easy way to get RCE is to point our address to the username buffer and overflow it, then we can perform a simple ret2libc through ROP.

Solve Script

solve.py

Last updated

Was this helpful?