Stack based overflow

Posted by Preacher on

The stack is a memory segment just like the heap, or bss. It's used as a temporary frame during function calls, and for local variables inside a function. Let's see how it works.

First, below is an illustration of the memory segmentation:

It's named stack because of how it works. It follows the LIFO principle, which is a data structure where we can push and pop objects to/from. The last pushed object will be the first one to be popped off the stack. The first pushed object will be the last one to be popped off.
The stack grows towards low addresses, while the heap grows towards high ones.

For a simple C function call like strcmp(str1, str2);, the compiler would produce something like this:

Now some explanations. The Extended Base Pointer (EBP) register points to the stack base while the Extended Stack Pointer (ESP) points to its top. Function arguments are pushed onto the stack using the simple push instruction.
So when you push an arg onto the stack, ESP is decremented by 4. In this case, str1 will be at the top of the stack, then we'll find str2.

Then, we find the actual function call. The call instruction, amongst other things, pushes the return address on the stack. This is the address the callee will jump to when its job is done.
After that, we jump straight to the called function. Finally, we clear the stack from the arguments we previously pushed. To do that, we need to add 4*2 to esp (since the stack grows towards lower addresses).

Now let's have a quick overview of what happens inside a function. Say int add(int a, int b){return a+b;}.

The two first instructions create a stack frame. We're now able to access arguments using ebp, since esp is always moving as we push/pop stuff from the stack.

The first argument is at ebp+8, the second at ebp+12... Why ? Because we saved ebp which is at the top of the stack. Right before it, we have the return address, and then we find the function parameters.

At the end of the function, ebp is restored, and the ret instruction pops the return address from the stack and jumps to it.

This last step is where the fun part starts. By overflowing a local variable, we gain control over the return address. Let's see how with the following vulnerable code:

This program really does nothing, except passing argv[1] to a function that will call strcpy. That's enough for us.

Our goal here is to call the unreachable function.
Also, since this paper uses very basic exploitation techniques which are now mostly outdated, we'll need to turn some features off (ASLR, stack canary) for our exploit to work. Therefore compile this example program as follows: gcc sbof.c -o sbof -m32 -no-pie -fno-stack-protector

The strcpy(char *dest, const char *src) function copies the string src to the buffer dest. It does not check for overflow. The buffer in the regname function is 16 bytes, what if we enter a name longer than 16 characters ?

./sbof lol
Name 'lol' successfully registered ./sbof AAAAAAAAAAAAAAAAAAAA
Name 'AAAAAAAAAAAAAAAAAAAA' successfully registered

Oh well it looks ok. Let's try with a name way longer.

./sbof AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[1] 4309 segmentation fault (core dumped) ./sbof AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

It crashed. We've just smashed important stuff from the stack, like the return address, so the program jumps to an invalid location.
Let's see what happens in the stack when ran with ./sbof XYZ

stack

We can see the argument we entered on top of the stack, right before the call to regname. Let's follow this call until the vuln.

stack

The call to strcpy is about to be executed. The stack contains both the destination and the source addresses. We learn strcpy will start copying from 0xffe22a20, which, if you look at the stack, has been zero'ed.

If you get back to the previous screenshot, you notice the next address after the call to regname is 0x08048569. This is where we'll be jumping after execution of this function.
Now look at the stack, you can see this address. Our goal is to smash everything before it and then write our new return address, that is the address of unreachable: 0x08048605.
There are 28 bytes we need to smash before writing our return address.
Finally, our payload is: 'A'*28 + '\x05\x86\x04\x08'.

Let's try:
./sbof `perl -e 'print "A"x28 . "\x05\x86\x04\x08"' `
Name 'AAAAAAAAAAAAAAAAAAAAAAAAAAAA' successfully registered
WIN !
[1] 10745 segmentation fault (core dumped) ./sbof `perl -e 'print "A"x28 . "\x05\x86\x04\x08"' `


Got it! We managed to overwrite the return address. Of course this is a basic example, but it shows that we can achieve way more using this technique, like injecting and running our own code for instance.