It’s been a while since I’ve posted anything here. Honestly I’ve been waiting for a nice heap exploitation challenge to come around and I think this is it.
No need to dig into the disassembly since everything is pretty straight forward.
Allocate - lets us choose the size of a chunk to be allocated, size is restricted to <= 0x1000 and allocation is done via calloc, which means any allocated chunk’s memory is zeroed
Fill - lets us put data in a chunk. This is where the vulnerability is since we specify the amount of data to input and there’s no bounds checking
Free - just frees a chunk
Dump - prints the content of a chunk. The trick with this function is that the size of the allocated chunk is saved to a separate mmapped region and Dump does write(1, chunk, initial_sizeof(chunk)). So this means we can’t print more then the original size requested for the specific chunk
Before jumping into the exploit, let’s go over the constraints. We can overflow any chunk in memory but we can’t leak any data (at least not without fiddling with allocations first). Why is that ? Well, because to get a leak we first need to free a chunk so it’s FD or BK (depending on the size of the chunk) needs to get populated so they point to either the previous/next chunk in the heap or to the head of the list which they are part of, which is in main_arena structure inside libc.
So once we free a chunk, we can’t print the content of it because there’s no UAF vulnerability. Another method you are thinking is why we can’t leak the BK/FD ptrs of a free chunk from the previous chunk? This is because if we use Dump function on the previous chunk in memory, Dump will only output the amount of data we initially requested for that chunk. Now maybe you are thinking why we can’t free a few adjacent chunks and allocate a new big chunk that would contain all small freed chunks and leak their pointers from it’s content. This is because allocations are done via calloc and the content that would contain any pointers will be cleared with the allocation. So how do we get a leak with a FULL PIE (yes, I forgot to mention) binary?
To create a leak, we are going to force an allocation on top of already allocated chunk. After we have both chunks (a fastbin and smallbin sized chunks) overlaying each other we are going to free the smallbin sized chunk and then dump the content of the fastbin sized chunk which will be populated with the content of the smallbin’s FD and BK pointing to main_arena. To do that we are going to use partial-overwrite with fastbin attack
First step is to allocate a couple of same-sized fastbins and 1 smallbin which is going to be our overlay target that we want to allocate over. After that we free 2 of the fastbins, this will place the first freed fastbin in the fastbin list in main_arena for that index.
And if we look at the content of the heap and main_arena we can see this chunk’s been put in it’s corresponding fastbin.
As you can see, we freed a fastbin and it’s put in it’s fastbin list but it’s FD is not populated because it’s of fastbin size, and fastbin chunks are only singly-linked. Since we need to do fastbin attack, we need to free another fastbin sized chunk so it’s placed on top of the fastbin free list and the chunk that’s currently in the fastbin free list will be placed in it’s FD field so they are “linked”.
To explain the fastbin attack, we now have 2 free chunks in the fastbin free list. If we request an allocation of this size, malloc will serve us the chunk currently on top of the list (0x000055db5d67f060) and it will place the chunk pointed to by it’s FD pointer on top of the free list for the next allocation. If we request another chunk of this size, malloc will serve us 0x000055db5d67f030 since it’s already placed on top of the free list and awaiting to be allocated. However, this is where we trick malloc. If we do partial overwrite on the pointer at [0x55db5d67f070] and we overwrite it’s LSB with the location of the smallbin (0x55db5d67f0c0). After the first time we request 0x000055db5d67f060, 0x000055db5d67f060->FD which is now 0x55db5d67f0c0 will be placed on top of the fastbin free list. And on the next allocation malloc will serve us a chunk allocated right on top of the smallbin. The only requirement we need to pass with the final allocation is
Which basically checks if the victim->size (0x55db5d67f0c0->size) corresponds to the size of this fastbin request (which does not because victim->size is 0x91 and we are requesting fastbin 0x20), but we can easily fix that with the overflow we are given.
To show you how that looks like:
Now we have a smallbin and fastbin both allocated at 0x55db5d67f0c0, we still need a leak. To populate smallbin’s FD and BK we need to free it. But if we free it with it’s current size 0x31 it will be placed in a fastbin and we won’t get any FD BK populated, so we just need to restore its’ size to 0x91 and then free it (don’t over-think too much here :P).
Ok, now we have the address of libc and we know how fastbin attack works, what now ? Well, we use same same… but different :)
We can use fastbin attack to place a libc address in the fastbin free list, so malloc returns this libc address and we can overwrite libc stuff. Our target is __malloc_hook, which is a function pointer that malloc calls if this pointer is not NULL.
Do you remember what was the requirement with the fastbin attack ? If you scroll up, you can see that there’s an assertion for the size being allocated needs to match the index for this fastbin. So if we put 0x7ffff7dd1b00 in a fastbin so we can request it and write over the __malloc_hook, we are gonna crash on requesting this chunk because 0x7ffff7dd1b00->size is 0x00007ffff7a92e50 which is waaaaaaay past fastbin sizes. But we know that the fastbin sizes range from 0x20 to 0x80 inclusive (on a 64bit system, it’s size_t * 16), so what do we do ? Well, here we don’t have to be aligned ! Knowing this we can shift these addresses so we can get the MSB (0x7f) to overlay at our fake chunk’s ->size which will match with the index for 0x70 bytes sized chunks.
So placing 0x7ffff7dd1aed address in a fastbin free list and requesting it passes all the necessary checks of malloc and it’s right before our target __malloc_hook.
Of course we can simplify further exploitation if we can use the one shot / magic gadget and not worry about pivoting and system arguments. A few days ago I stumbled upon this amazing tool by david942j that finds the addresses and lists the requirements for using the one shot magic gadget :).