It was a while since heap exploitation part 1, in which I detailed fastbin attack, double frees, and UAF. Today, I will delve a much deeper into some slightly more difficult techniques... make sure you know how glibc malloc and free works at a detailed level before continuing! Additionally, these are more summaries and ideas of what is going on rather than full on write ups of specific problems. Let us begin!
1. House of Force
This technique is quite old, but still works on many CTF challenges. Basically, if you can overflow the heap and control allocation sizes, you can achieve arbitrary write and probably pop a shell. You can also find examples of such exploits from HitConCTF and BackdoorCTF... I am currently writing a challenge related to this for another CTF.
Generally, if you can overflow and drastically change the size of the top chunk; most typical examples show exploits overwriting the top chunk with -1, which becomes a very large value in memory. By doing this, the size field of the top chunk changes from the usual 0x21000 (or something smaller after a few allocations) to 0xFFFFFFFFFFFFFFFF, which can basically cover the entire program's memory (so no more mmap calls!). Now let's say you want to get address X to mess with... allocating the difference between the address of the target and the address of the top chunk (in addition to the 16 bytes of metadata on 64 bit computers and perhaps an extra few bytes to correct malloc rounding), you will get that region to overwrite! Here's the way I perform my calculations in these problems (64 bit):
request = c_ulong(target).value - 8*5 - c_ulong(topchunk).value
request = c_ulong(request).value
request = c_ulong(request).value
Very cool technique indeed!
2. House of Spirit
I honestly just figured this out from this old CTF challenge: 0x00 2017 Spiritual Memos. If you can get a stack leak or a heap leak, you can then attempt to overwrite a pointer to point to a location near the stack or heap region (or other places too) you have control over (remember to take into consideration 0x10 bytes of metadata on 64 bit machines). Make sure to forge your chunk correctly though and make sure that the data around it will allow it to pass the next size check. Then, you can free that pointer (I prefer fastbin sized chunks), and once you get that back, you can start overwriting! In that old CTF challenge I mentioned (I won't discuss it in too much detail here), there was basically an overflow bug over a buffer and the pointer right next to it (including a canary also); thankfully, the usage of read (which lacks the attachment of a null byte) allowed us to see a lot of data, including the canary as well as another pointer that was always at a constant offset from the stack (probably something leftover from libc initialization or some other startup function). The same concept was used for the libc leak. Then, with all these leaks gathered nicely, I used the same overflow capabilities to help me with the creation of a fake chunk while setting the pointer equal to it. This time, I made sure that the size was large enough to help me overwrite the return address of the function. Hence, when I freed and malloc again, I get back this fake chunk, and achieved arbitrary write, using a one gadget to pop a shell.
3. Unsorted Bin Attack
This technique isn't often used for RCE or popping shells. Rather, it usually is useful to overwrite another variable elsewhere and further the path for exploitation. How2Heap does a great job explaining this attack. Basically, you need to be able to malloc multiple chunks bigger than fastbin size (you should never free the last one because of top chunk consolidation); if you overwrite the bk pointer of the freed victim chunk (take into account the 16 bytes of metadata!) in unsorted bin, upon freeing it, the victim address will get updated to the address of the unsorted bin, due to how malloc/glibc works.
4. Family of Off by Ones. I consider this a pretty important, if not one of the most important, categories. There are many types here, but they are all very similar: House of Einherjar, Poison Null Byte, etc.
Basically, if you get a one byte overflow (from issues like strlen() vs read(), adding 1 to the amount to reading... there's a ton of examples out there) and have some degree of control over allocation sizes, you can actually get arbitrary write. The 0x00sec article on the poison null byte actually documents this very well in my opinion. What is the end goal? To overlap a newly allocated chunk with an already in use chunk to overwrite the latter's metadata pointers. For example, if you can make a chunk with the last line (in heap memory of gdb) with a fake but slightly smaller chunk prev size data (for beating a check in the unlink macro later on), and have a chunk above, and two chunks below (the last one for preventing top consolidation), you can start attacking! By freeing the second chunk, the set foot macro will not affect the fake prev_size we created above (make sure these chunks are all not fastbin sized). The third chunk's prev in use bit will be unset. Then, overflow from chunk #1 to shrink the second chunk to match the fake prev size value. Then we can allocate another chunk there (a few chunks usually... one to prepare for back coalescing and the others are fastbins for the final part of the attack). Now, when you free newly created non-fastbin chunk and then the third chunk (and most likely the other ones to help with the attack later when performing arbitrary write), back coalescing will occur and you will get access to a huge region of overlapped chunks. You can now overwrite parts of the fastbin carefully and get arbitrary write! Target wherever you desire... I usually go for the BSS section of the binary if it is possible, as it usually stores the array that stores the pointers to the chunks... I can overwrite those and then manipulate them with the alloc, edit, delete, etc. functions many CTF problems provide.
5. Poison Null Byte
Basically the same idea as off by ones. Just be more careful about the way you craft everything because you can only overwrite with a null byte.
Happy pwning guys! Come back soon for part 3!