PWN10: Heap exploitation for GlibC 2.32

Hello guys, this is another write-up for one of the amazing pwn challenges from Cyber security Rumble CTF.

The challenge name is howtoheap , and it's a heap challenge for GlibC 2.32, a little bit blindy challenge because we don't have the GlibC provided, all we have is the binary and the source code. if you don't have the binary downloaded you can find it in my GitHub repo here (The exploit too) :


File :

64 bits ELF binary, and it's dynamically linked (it needs to be linked to a GlibC in order to work).

Not stripped which means it may contain symbols that makes debugging easier.

Checksec :

All protection mechanisms are enabled except it's partial RELRO which gives the possibility to overwrite the GOT with our pointers (if we can of course, in my exploit I didn't touch the GOT)

Play with the binary :

When running the binary we get the usual menu for heap exploitation challenges.

You can malloc, free, read and write

Let's go and search for the bug in the source code.

Source code auditing :

Looking at the source code I started with the main which contain just a switch :

I started with malloc_one() function, and it looks normal, just giving it a size and it will allocate the providing size (no checks on the size), and it store the chunk pointer in an array of pointers of size 0x400.

The function free_one() just free the pointer we allocated by giving its index, and it get rid of the pointer by NULL it, no UAF.

But when looking at read_one() and write_one() the vulnerability is clear, you can determine how much bytes you want to read and how much you want to write! clear heap overflows and clear leaks!
Example : if you allocate a chunk of size 0x20 and you use the option 4 which is write_one(), you can give it a size of 0x100, the first 0x20 bytes will be written to the chunk, the rest will be overflowed!

same goes for read_one().

Plan :

Until now all we know is that we can allocate a maximum of  0x400 chunks, we have no UAF, but we can overflow into the next chunks if they exist and we can read their content from the upper chunk (this is usefull to leak heap and libc addresses when the chunks are free)

So my plan is this : 

- Leak heap it can be usefull later.

- Leak GlibC.
- Find the GlibC version somehow.

- Tcache poisoning attack to change __free_hook to system and free a chunk with "/bin/sh\x00" on it.

Leak Heap :

I guess from what we already know, you can leak a heap pointer.

To do this you need to allocate three tcache chunks.

You have to free the second, when freed it's bk will contain a pointer to the tcache_perthread_struct which is in the Heap.

So what you can do is this :

Free the second chunk.
Read the content of the second chunk from the first chunk.
The third chunk is here just to not consolidate the second chunk with the top chunk.

This is the block in my exploit that do what I said : 

I don't know why the image is not clear, but it show two 0x40 chunks freed and that what I want I will be using this to achieve tcache poisoning after getting the GlibC leak.

GlibC leak :

Same as the Heap leak here instead I will be freeing a bigger chunk to fit into unsorted bin, chunks higher than 0x408 are good for libc leak.

Here I'm gonna free a chunk of size 0x430 so it's fd and bk becomes main_arena+96, at least we know the address of main_arena+96 which means we know the address of main_arena remotely.

GlibC version determination :

 It wasn't enough for me to determine the GlibC version after getting the address of main_arena but I wanted to leak _IO_2_1_stderr_ which is a pointer that resides in _IO_list_all all the time, and _IO_list_all is at fixed offset from main_arena in all GlibC versions I guess.

_IO_list_all = main_arena + 96 + 0x9c0

So I used the heap overflow and you still remember that I have two freed chunks in the tcache ready to be overflowed.

I want a chunk here is _IO_list_all just to leak what's inside to get _IO_2_1_stderr_ address.

I'm overwriting from chunk 0 to chunk 1 (freed) to change it's fwd to _IO_list_all like this :

This should be enough to leak _IO_2_1_stderr_ right? let's test remotely :

Oh I got an error remotely, it looks like a malloc() error, let's google it :

It says glibc-2.32 aha here I got the hint that we're dealing with glibc 2.32 and I knew the source of the error.
tcache poisoning works good in glibc-2.32 too but you need to have a heap leak (we already did it), because some xoring and shifting happened to protect fwd and bk of a freed tcache chunk, you can't just fake you chunk easily as you did already in older versions of GlibC.

You can use this function to get the pointer you need to give : 

For more information of heap exploitation under GlibC 2.32 go here : Heap exploitation 2.32

So I'm gonna change a little bit my exploit and run it again hopefully this time I'm gonna get _IO_2_1_stderr_ address : 

And yeah I got the leak I want, now let's go to search and find the version in

Enter the symbol name along with the last three nibbles of the leaked address in my case it's 0x440.

Here are the results that match my leak, the remote GlibC can be anyone of these you just eliminate the ones that are not 2.32.

I started from bottom to up I downloaded the last one libc-2.32-2-x86_64, and I tested it (it was the right one btw).

After you download it go and make it executable by chmod a+x libc-2.32-2-x86_64

and run gdb ./libc-2.32-2-x86_64

I'm gonna use these offsets and assume that the glibc I have now is the right one, it's still an assumption it's now a matter of test and fail.

Here I'm calculating GlibC base address, system and __free_hook from the offset I got from gdb.

Now I'm gonna allocate again some chunks, free the middle ones, and do tcache poisoning attack again (just like we did before), just to get a a chunk in __free_hook and change it to system() then I'm gonna free a chunk with /bin/sh on it : 

Enjoy :

If you have any questions regarding anything you can ask me, here is my discord : Maher#7775