StarCTF 2019: girlfriend
I long for love, but also like single life. But it's a little difficult to find a girlfriend by playing CTF.
TL;DR
Classic LIBC leak via Unsorted Bins' read primitive into Fastbins' Double Free write primitive to achieve code execution by overwriting
__free_hook.
Challenge Overview
The binary presents a classic heap challenge menu with options to allocate, free, and view chunk contents.

Option 3 isn’t implemented, so there’s no direct way to modify the content of an allocated chunk.
The interesting twist is that the allocation size is controllable, and hence there is more flexibility as to what kinds of free lists we want our chunk to end up in after free-ing.
When Option 4 is called, the name field of the struct Girl is freed, but the pointer to the actual structure itself stays intact, which means we have a Use-After Free scenario here.
The provided LIBC version is 2.29, so there are double-free protections built in that we need to consider.

Stage 1: LIBC Leak via Unsorted Bins
A common technique to get a libc leak is to free a big chunk (>0x400 bytes) so it gets put into the unsorted bin. The unsorted bin is a doubly linked list, and its head is stored in the libc's data section.
When we free a chunk into the unsorted bin for the first time, its forward pointer then points into the libc, at a known offset. If we can read this pointer, we leak the base address of libc.
Conveniently, we are able to control the allocation size, which means we can force the chunk to end up in unsorted bin after free-ing.
In GDB, we can confirm that there is a LIBC pointer of main_arena+96 in the unsorted bin.

We can then calculate the offset using main_area and it's delta value of 0x96.
Stage 2: Double Free
After leaking the LIBC, we want to fill up the tcache entirely so that we can perform double-free on fastbins instead to avoid the double-free protections.
In GDB, the fastbin free list now shows the same address appearing twice, confirming that the double-free succeeded.

Before performing the arbitrary write, we need to first use the allocator's first-fit behaviour to re-allocate chunks from tcache.
Stage 3: Code Execution
According to this article, there are a couple of interesting targets that we can consider overwriting, one of which is __free_hook, which only works on GLIBC <= 2.33.
When __free_hook is executed, the argument to free() will be passed to system(). If we can allocate a chunk and place the string /bin/sh in it, calling free on that chunk will effectively call system("/bin/sh") to get a shell.
Solution

Last updated