Points: 267 Solves: 9 Category: Exploitation Description:
Solved by kileak & uafio
Wow… while writing this I had to pause here for a while. I didn’t realize the challenge I’ve been pwning all day is only this small! This is awesome! Where do you even begin…
Truth is Kileak had already done half of the work and I just wanted to document our method just because of the sheer amount of exploitation methods involved.
In short, we begin with getting an info leak by creating a bunch of different sized fastbin chunks (fastbin chunk <= 0x80 bytes with the metadata) followed by a request for a chunk of at least MIN_LARGE_SIZE+1 (0x400 bytes). By doing so we force the fastbin chunks to coalesce and eventually provide us with a leak. Then we create a new mmaped segment for our heap along with it’s new main_arena, we do this by requesting an enormous sized chunk.
By abusing the
long size; as a pointer and
char* buf as an index (always 0) we can write a NULL byte anywhere. In this case we overwrite the 2nd byte of new main_arena->top_ptr. Since the new main_arena and the new heap are on the same segment this will point the top_ptr right over the smallbins.
After we have gained control of the smallbins we create 2 fake buffers using the smallbins. First with size of a fastbin that will get put into it’s fastbin index. Second of size smallbin so when it gets freed right away it will get put into the unsorted bin. After that we use the fastbin to overwrite the unsorted bin’s BK ptr and perform a unsorted bin attack (needless to say the address of the fastbin and unsorted bin need to overlap).
With unsorted bin attack we can attack the _IO_list_all and the vtable of the _IO_FILE stderr just as in House of Orange.
With the application running in a loop with
malloc() followed by
free() first it seems like impossible to get an info leak. But by abusing 2 facts we can achieve our goal.
- We take advantage of that fastbin sized chunks don’t merge with the top chunk but instead they are being put into their appropriate fastbin.
- An allocation bigger than MIN_LARGE_SIZE (0x3ff bytes on 64bit system) skips the allocation for fastbins and smallbins and does a check if fastbins exist in the current main_arena, consolidate them to avoid heap fragmentation.
Next, we create a new heap by requesting a huge chunk that would fail even mmap. Requesting a chunk bigger than the current top chunk and no free list can satisfy, malloc will call upon
sysmalloc for extending the current heap.
Sysmalloc will do a check if the request is bigger or equal than
mp_.mmap_threshold (0x20000 bytes on my 64bit machine)
sysmalloc will then call
mmap. But if the size is too big even for mmap, the mmap will fail and brk syscall will be tried, and if this fails,
_int_malloc will return 0. Then
__libc_malloc will call
arena_get_retry which it will call
arena_get2 to fetch another arena (av) in it’s arena list. However since no arena’s but main_arena are currently present,
arena_get2 will create a new arena with
_int_new_arena => new_heap so
__libc_malloc can try the allocation again with
_int_malloc and the newly created arena. This of course will fail but it will result 2 things we are interested in.
- A new arena will be used for any sequential allocations.
- The mstate (main_arena structure) for the new heap and the new heap itself will share the same segment (how convenient) ! At the segment’s base there will be a pointer to the mstate (which is usually just 0x20 bytes away from the segment’s base) followed by the new heap (meaning new_heap->top points right after the mstate).
Going back to the source now let’s exploit
buf[size - 1] = 0;. How ? By requesting a size to match a target address which will result in the
long size used as a
char * and
buf as our index. This will write 0 at arbitrary address (in the new heap, the new heap because we can’t use the old heap anymore since it will not be used after the first time we use this vulnerability).
What is our write NULL byte target ? Well, thanks to Kileak’s ingenious thinking, we can overwrite the 2nd byte of the
new arena->top which will cause the top chunk to point right over the smallbins of the mstate of the new arena (remember they share the same segment now).
After that, we can create a fake smallbin which points right on top of the mstate’s smallbins (almost on top of itself) so we can control all the metadata of it, but it’s actual size is of fastbin ! So after it’s allocated it will be freed and put into the fastbins. This way we create a fastbin for later allocations, which is very important because fastbin allocations are the fastest and will not mess with the mstate or any of the other bins.
Creating a fake smallbin
Let’s start with the
struct malloc_state *mstate structure.
When malloc keep track of chunks in the malloc state, malloc only handles the actual base of chunks it deals with, not the user returned buffer, and the sizes are always the actual full size of the chunk including the metadata. For example let’s see the unsorted bin. The chunk starts at
0x7fa17c000078 with it’s FD and BK pointers both pointing to it. And the numbers on the side are the size of the chunks being linked in that index. So, requesting a chunk of 0x100 bytes malloc will change the size to include metadata making it 0x110. The index for 0x110 is at
0x7fa17c000188 and 0x7fa17c000190,
0x7fa17c000188 being the address of it’s FD ptr and
0x7fa17c000190 being the address for it’s BK ptr. To check if a free chunk is present at that index malloc takes the BK ptr and checks if its not equal to the base address of that index’s base smallbin
As you can see we have already corrupted everything… the 2nd byte of the top chunk to make it point to the smallbins. We have also made our fake smallbin chunk for size 0x110 (user request 0x100). The chunk’s BK pointer at
0x7fa17c000190 making the
victim 0x7fa17c0001b0. The only check that prevents malloc from returning
0x7fa17c0001c0 (user buffer) is that
0x7fa17c0001b0->BK->FD != 0x7fa17c0001b0.
As you can see we have satisfied all conditions for the a requested 0x100 bytes to return
0x7fa17c0001c0 as our fake smallbin chunk. It’s size is
0xc0 which is going to place it in the unsorted bin as soon as it gets freed after the allocation.
Creating a fake fastbin
We will do the same fake chunk but user request of size
0xf0 bytes. Looking at the corrupted data from the screenshot, can you tell what’s going to be the user returned buffer for that size ?
This chunk will be placed in the fastbins when freed because its size is of largest fastbin.
If you notice we have also set the
PREV_IN_USE bit for both of our fake chunks so when freed there won’t be any backward consolidations. The forward consolidations are also taken care of thanks to all the array of
Unsorted bin attack
We are at a state with two free chunks, one in the fastbins and one in the unsorted bins. Our goal is to corrupt the unsorted bin’s BK pointer so when an allocation is made the address of the unsorted bin will be written at the now corrupted
BK + 0x10. In our exploit we already corrupted the unsorted bin’s BK ptr via the buffer of the fake fastbin. When we allocated the fastbin, the chunk’s buffer overlays right on top of the fake unsorted bin so we could easily just overwrite the BK ptr. As you have probably figured out, the second fake chunk was of fastbin size so when freed it won’t interfere with our chunk in the unsorted bin.
Here is the state of the heap after corruption (sorry for the different addresses, had to restart).
0x7fc4fc0001b0 in the unsorted bin and
unsorted bin->BK. We have also modified the unsorted bin’s size to
0x90 so it can match exactly a request of
0x80 bytes. And we have also put
0x7fc4fc0000f8 address at
0x7fc4fc000110 so when requesting
0x80 it will not try to return a smallbin chunk but use the unsorted bin instead.
0x7fc503f5b510 address you ask? It’s
&_IO_list_all-0x10 which will cause the address of the address of the unsorted bin
0x7fc4fc000078 to be written exactly at
_IO_list_all which currently holds a pointer to the
stderr _IO_FILE structure.
Let’s go ahead and verify the conditions for this write.
First off is the check if there is a chunk in the unsorted bin. Verifying if
unsorted chunk->BK != unsorted chunk basically saying
0x7fc4fc0001b0 != 0x7fc4fc000078 from the screenshot above and also setting victim to
Next we need to skip the if branch for using the
We know that the second check fails… Now we can proceed and exploit the unsorted bin attack.
We are successfully out of malloc with our write objective completed. However a crash happens in
_int_free right away because of the BK we corrupted.
But as described in the House of Orange we can still get a shell out of a crash. After all this is the reason we corrupted _IO_list_all.
To figure out how exactly, we will have to trace the call stack. When malloc/free fails with an error it will call on
malloc_printerr => __libc_message => abort => fflush / _IO_flush_all_lockp => _IO_OVERFLOW (if requirements are satisfied)
Our goal is to reach the
_IO_OVERFLOW which is a call to a pointer from the
fp->_IO_jump_t->__overflow. You decide which OR condition you can satisfy.
Here are the important offsets
Some interesting _IO_FILE related structures.
P.S. I’m actually not sure what to call House of Orange. Is it the abuse of _int_free in sysmalloc or is it the abuse of the _IO_list_all.