Points: 500 Solves: 21 Category: Exploitation Description: Do you know Huang Xudong? Let me show you his power.
nc 18.104.22.168 6969
poisonous_milk Solved by uafio and kileak
Today I’m going to explain one of my favorite heap exploitation techniques.
I will try to keep the description of each of these functions short, although they can be really confusing. Let’s first start with the 10,000 ft view. It’s a C++ program that allows us to create milk objects. Each milk object has two properties, a
const char * to a color constant and a
char * to a input buffer called “flag” that we control. A max of 100 milk objects can be created and a pointer to each is stored in a dynamic array, a pointer to this dynamic array is stored on the .bss
To better illustrate the relations of these structures, here is a pretty diagram made by rh0gue.
Now to drill down to each of the options, individually.
[p]- Checks if
(arrayEnd - arrayStart) < 800(hence the 100 objects limit). Next, it allocates a buffer of max 0x56 bytes for the
milk->flagreads input into it (without overflow), it reads input for the color choice and compares it with a list of constants. If a match is found it populates the
milk->color_ptrwith the pointer to the constant color (not with the pointer of our color input). If a match is not found it leaves the
milk->color_ptruninitialized, a fact that we are going to take advantage of.
[v]- Iterate through all of the milkArr and
printf("[%d] [%s] %s\n", i, milkArr[i]->color, milkArr[i]->flag)
[r]- Without any out of bounds it simply does
delete milkArr[idx]->flag ; delete milkArr[idx]and since the milkArr is a dynamic array it pops the index out of the array and adjusts the size, however, neither of the two pointers are nulled essentially creating a UAF.
[d]- Delete all the milk objects from the milkArr, the associated flag properties, the milkArr and the milkTable. Essentially freeing all of the dynamically allocated pointers the program knows about in its current state. Except it won’t NULL the
milkTable_ptrlocated on the bss creating another UAF.
[q]- returns from main
Leaking the heap
First step of our exploit is to get not one but two info leaks. The binary is PIE enabled and we need to know where the heap is allocated and where libc is loaded. I’m going to be thorough here and going to show you how the memory layout looks like compared to the diagram above.
NULL this is not because it’s being zeroed but instead it’s because it’s uninitialized which we are going to exploit. If we free the only milk structure in the milkArr the program will do
delete(milkArr->flag) followed by
delete(milkArr) followed by adjusting the values in the milkTable indicating there’s no more elements in the milkArr. We know both of the elements we are going to free are of size 0x20, which means they are of fastbin size. The first element we free is going to be placed on top of the fastbin free list for size 0x20 with
buffer->FD = NULL because the fastbin free lists are singly linked lists and
FD == NULL represents the end of the list, this means
free() is going to remove our
0x4141414141414141 out of
0x555555769c40 address. However, the next
delete(milkArr) will free chunk
0x555555769c60 and because we already have a node in the fastbin list for this size, it will replace the node on top of the fastbin list and put the old fastbin top in
the current fastbin top -> FD. Which means it will place a heap address in that uninitialized milk->color property which lucky enough points to a pointer of the end of the dynamic array!
All that’s left now is to create a new milk object without providing a color so we keep that fastbin stored pointer. One thing to notice here is that the new milk object’s flag needs to be bigger than the size of the currently free fastbin. This way we will not serve the same chunks for the same structures/buffers. Instead allocating a bigger chunk for the
flag property will force malloc to serve a new buffer from the wilderness and serve the previous
flag buffer as a memory for the now current milk object, exactly with the heap pointer in place of the
color property, which we are going to keep “uninitialized” (or forcefully initialized by us :P).
To leak an address of libc we need to somehow place a libc address on the heap. But we can’t simply free a chunk and hope for a libc pointer to end up on the heap because all of the chunks are of fastbin size. So the plan is to free a fake chunk of size of a smallbin and then leak either the FD or the BK ptrs. To free a fake chunk, we are going to target the
arrayEnd pointers. By pointing those to a controlled heap address, we can essentially take control over the
milkArr. For that we are going to use the
[d]rink option which is going to free the
milkTable and then we create a new milk object and the buffer for the
flag property will end up getting the
milkTable’s old buffer, essentially giving us control over the
arrayStart and arrayEnd ptrs. Here a lot of coordination was required because the “fake”
milkArr had to be pre-setup with not just the “fake smallbin chunk” but also with fake pointers to chunks we are going to need for later.
Our fake smallbin chunk here is
milkArr->flag which points to
0x0000555555769d10 with size
0xd1. So, we free
milkArr and then
[v]iew and we got ourselves a libc info leak :).
On the next part I decided to take control over the
milkArr pointers. This way I can control each individual milk object without worrying for the size of the
milkTable. Well, this has already been taken care of :). On the last part where I said some coordination was needed, if you notice our “fake” smallbin chunk is located on top of the
milkArr, now I just need to request a chunk of size which does not have a corresponding free fastbin. This way malloc will serve us a chunk from the currently free smallbin at
0x555555769d10 and placing whatever is the remainder in the unsorted bin (which gave me a lot of trouble later :P). Next with some convolution, I arrange everything so the next allocation of a “flag” buffer for a new milk is going to be allocated on top of already free chunk and I can overwrite its
FD ptr and do a fastbin attack.
However ! I can’t simply place a ptr of
&__malloc_hook - 0x23 in a fastbin like I did here because the “fake” size needed to pass the fastbin size allocation will be
0x7f and the largest heap chunk I can allocate is 0x56 bytes + 0x10 for metadata making it total 0x60 (rounded). So, what to do next ?
Impossible fastbin attack ?
This is my favorite trick in the book :). Instead of placing a pointer of
&__mallok_hook - 0x23 we are going to place a pointer of
&main_arena + 0x25 which will point right on top of the fastbinsY array.
What would this do, you ask ? Well, on the next allocation of the appropriate size it will give us the
0x00007ffff7a4fb55 address and the target is the
ptr to the top chunk. Overwriting the pointer to the top chunk will point the
wilderness/top to address we want and allocations after that will be served from that memory area ! The only requirement is for the new
top chunk to have enough data so choosing an address with
new_top_chunk->size != NULL is required. What about the fastbin size check you might ask again :) ? Well that’s why there’s an already intentionally free chunk in
0x7ffff7a4fb48 slot. So the MSB
0x55 is going to serve as the size of the free fake fastbin.
OK, we control the new
top chunk, where do we point it ? Haha, at
&__malloc_hook - 0x28 of-course :).
As you can see if
new top starts at
0x7ffff7dd1ae8 the pointer at
0x7ffff7dd1af0 is going to serve as
new_top->size and we are all good, yes even with the LSB(it) being 0.
My biggest frustration with this tactic is that the unsorted bin did not point to itself meaning there is a free chunk in the unsorted bin, meaning new allocations will not be served from the top chunk. So with the overwrite of the
top chunk I also had to NULL the
last_remainder ptr (which is located at
0x7ffff7dd1b80) and restore the unsorted bin ptrs to the address of
Another hiccup is the “fake size” from the MSB(yte) of one of the pointers in the fastbins. with ASLR the heap has a chance to start with either
0x56 address. Without ASLR it’s only
0x55. In my calculations this only worked with “fake size” of
0x55 fails the index of fastbin allocation check. I’m assuming “fake size” of
0x55 can pass the check if the “fake fastbin ptr” is placed 1 slot before where it’s currently located.
Full exploit script