Lots of good articles have been already written in this subject (Which I'll link at the end of this post for anyone that's interested) but with the versions I was using in my VMs as lab I had more than one headache(In fact, vulnerable versions of glibc seem to be already patched in SuSE versions 9.X. In my case switching to Red Hat 9.0 did the job). None of the popular papers seemed to work in my case, hence, I decided to dig myself in the inner workings of this dynamic memory allocation system. Instead of explaining a C implementation I went for a Reverse Code Engineering approach which turned as follows. In this post I won't explain the inner workings of dlmalloc, that's already been nicely documented in other papers such as Vudo - An object superstitiously believed to embody magical powers by Michel "MaXX" Kaempf, hence I'll avoid any reference to that.
The function in question is free() which takes care of freeing a used chunk once we have finished our work with it. free() is actually a wrapper around _int_free() which does the real job. _int_free() makes some checks and then makes use of the unlink() macro to unlink two adjacent chunks and join them to create a larger one. You can get the interesting part of the disassembly of _int_free() here.
At address 0x42074464 it loads the pointer to the chunk we want to free into the edi register. In 0x4207446b loads the address of the prev_size field of the buf2 buffer into ecx. Right in the next intruction the contents of size field of buf2 are copied into eax. After check int the arena struct we jump to 0x420744a0 at 0x42074481. In the next two instructions it checks wether or not the 2nd least significant bit of the size field is enabled and if it is(which is not our case) it jumps to another region within _int_free().
Now is where the fun part for exploitation begins. At 0x420744ad, we calculate the address of the next chunk using our address + the prev_size field of buf2 (which we can control overflowing the first buf1) and store it in eax, which can now contain the address of the prev_size of a fake chunk we can create. In the next instruction we load the size field of our fake chunk into edx. After some saving into local variables, some 8 byte boundary roundings and a PREV_INUSE checks, we arrive to 0x420744c8. Here we load in eax the prev_size of buf2 and in the next instruction we store in ecx the address of buf2's prev_size minus the prev_size of buf2 itself. This hypothetically would get us the previous chunk's prev_size address but since we manipulated buf2's prev_size we can make it go wherever we want.
So, since ecx now points to the prev_size of our fake chunk, let's talk a little bit about the unlink() macro. This macro basically swaps the fd and bk fields of unused chunks to make the use of free space in memory more efficient and to avoid fragmentation. Being a macro as it is, is compiled inline with the rest of the code and, since doesn't provide any sanity checking it'll swap some addresses we control with information we control allowing us to overwrite 4 bytes wherever we want in memory with the information we want. You can see unlink() in action in address 0x420744cd, 0x420744d2, 0x420744d5 and 0x420744d8. From then on the function continues his normal path as the damage has already been done.
Well that's it. I'll talk about exploitability and it's techniques in the next post. If anybody felt this was quite messy to follow I apologize; I understand it's a difficult matter (at least it was for me :P) but I hope somebody else learned something from this. I also hope I can bring a final solution for abo9 in the upcoming days. As promised I include some references to previous works in the topic here:
- Once upon a free() by Anonymous
- Vudo - An object superstitiously believed to embody magical powers bt Michel "MaXX" Kaempf
- w00w00 on Heap Overflows by Matt Conover and the w00w00 Security Team
- Remedial Heap Overflow (Video from DEFCON 15) by atlas
No comments:
Post a Comment