In this post we are going to trigger a FastBin consolidation which we are going to combine with a double free vulnerability (dup) in order to return a pointer to an already allocated chunk. By consolidation we refer to the process of merging adjacent free chunks and placing them to the unsorted bin for future allocations.
As always, please find below the references to my previous posts relative to heap exploitation:
Back to our topic, a consolidation is performed by the malloc_consolidate
which is a specialised version of the free() function[1]:
This function will iterate the FastBin list merging a free chunk to its next and/or previous ones (4766–4783) and the result chunk will be added to the unsorted bin list (4784). A consolidation can be triggered by the malloc
function, when special conditions occur:
It is also triggered by the malloc_init_state, malloc_trim, __libc_mallopt
as well as the free
function if the size of the freed chunk is ≥ 64KB:
We’ll stick to the malloc for this post as it is the most straightforward way to trigger the Fastbins consolidation.
We are going to use the following program:
Although the code above is pretty much self-explanatory, let’s take some time to highlight few points:
Lines 11–14
we fill the tcache list in order to force the FastBin usage.Lines 16–20
freeing p1 will add the corresponding chunk to the FastBin list since the tcache list is already full for this size (0x40).Line 22
) will trigger the consolidation of the chunks in the FastBin list. The p3 pointer though will point to the same address as p1 due to the fact that the p1 chunk has been merged.Line 22
) will add the chunk to the tcache even though it is still referenced by the p3.Line 33
we request a size which can be satisfied by the tcache’s last addition (0x400
). So p4 will now point to the same address as p3.Let’s load the program in gdb to see everything in action:
Set a breakpoint after the free(p1)
and after hitting it check the heap bins:
Now step into the malloc
up to the _int_malloc
function, where we notice a call to the checked_request2size
:
On a subsequent branch, _int_malloc will check if the requirement can be satisfied by the fastbins (using the get_max_fast()
):
As the we requested a size of 0x400 bytes, this check will fail thus we will end up to the malloc_consolidate
invocation:
Right after this call the chunk at 0x5555555598e0
has been merged:
Now malloc will return the 0x5555555598e0
as the new chunk address reference to satisfy the allocation (see last before the top chunk below):
The double free of p1 will move the chunk to the tcache:
Finally, the last call to malloc will assign the already allocated chunk to p4 (see $rax below) and this completes our proof of concept:
[1] https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;hb=45a8e05785a617683bbaf83f756cada7a4a425b9
[2] https://sourceware.org/glibc/wiki/MallocInternals#Thread_Local_Cache_.28tcache.29