int main(int argv,char **argc) {
char *pbuf1=(char*)malloc(256);
char *pbuf2=(char*)malloc(256);
gets(pbuf1);
free(pbuf2);
free(pbuf1);
}
In my particular case I changed gets() for strpy() using the call parameters as input vector to ease data input and exploitment, which doesn't alter the bug in any way. This level is textbook example of a Heap Buffer Overflow situation in which we fool the implementation and the unlink() macro to overwrite arbitrary bytes in memory.As explained in our last post, we need to create a fake chunk header in one of our buffers and fool free() to unlink() it. For this purpose we will overwrite the chunk header of buf2 so that calculations will lead the implementation to our fake chunk header. We will overwrite the prev_size field so that when _int_free() calculates the address of the previous chunk, it gets to our fake chunk header. Instead of making a step-by-step debugging session, I will explain the key instructions where data flow gets manipulated. Remember you can grab the disassembly here.
First let's begin by showing how the call will be so that we can identify the data we're analizing. Note in this call that the sub-sequence in blue is where our overflow begins and this 8 bytes will eventually fill the chunk header for buf2:
[infi@localhost insecure]$ ./abo9 `python -c 'print "\xeb\x0e"+"A"*14+"\xeb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"+"A"*188+"\xff\xff\xff\xff"+"A"*8+"\xf8\xff\xff\xff"+"\xf0\xff\xff\xff"+"\xff\xff\xff\xff"*2+"\x9c\x95\x04\x08"+"\x08\x96\x04\x08"'`
sh-2.05b$
In 0x4207446b the address of \xf8\xff\xff\xff gets loaded into ecx and right after that \xf0\xff\xff\xff is copied into eax. At address 0x42074477 eax is copied into esi. One of the critical points comes in 0x420744ad because esi=0xfffffff0 and ecx=0x08049708 get added and the result is stored in eax, which now contains 0x08049718; the theoretical start address of our fake chunk where the theoretical fd and bk pointers are (in red in the call sequence).
Anyway all of that is there just to success some checks, the real magic begins now. At 0x420744c8 edi-8 (the address of buf2) will be stored in eax, thus saving the prev_size field of buf2's chunk header in it. In the next instruction and remembering that ecx holds the address of the prev_size of buf2, ecx = ecx - eax is executed. Now remember that we manipulated the prev_size of buf2 to read 0xfffffff8 (-8), so this will effectively makes ecx point to an address inside buf2, which we can manipulate due to the overflow. ecx now points into 0x08049710, the chunk header of our fake chunk. Since the first 8 bytes of this "unused" buffer are fd and bk pointers our job is kindly completed by unlink() which using offsets 0x8 and 0xc will write 0x08049608 (address of our shellcode, stored in buf1 itself) in 0x0804959c+12=0x080495a8 (address of free@GOT).
That's basically it but a couple of notes here. First of all, shellcode+8 will get clobbered by 0x0804959c , thats how unlink() works. To make the shellcode work properly, we place a unconditional jump in the beginning and jump over the clobbered area, thats why we include "\xeb\x0e"+"A"*14 in the beginning of the shellcode (eb is the opcode for "jmp" and 0x0e=14 is the offset). Also, we need to make sure we overwrite the GOT entry for free() because right after we trick free() into doing this, it gets called once again over buf1, and after manipulating headers this way, the program crashes with a segment violation signal.
I hope this little walktrough enlightened somebody as just did with me. For any suggestion, question or whatever don't hesitate to drop a comment or write an email. See you next time and keep adding NOPs!