Thursday, October 9, 2014

Buffer Overrun Vulnerabilities and Exploits, Part 1

Hello,

Since we will be delving deeper into the Vortex wargames in the near future, I figured this would be a good time to bring everyone up to speed on buffer overruns. So this will be the first posting in a multi-part series. In this first part, we will cover basic buffer overrun vulnerabilities, as well as simple exploits. Subsequent posts will cover how to execute the actual attacks, as well as return-to-libc theory, which comes into play when the stack is NX (not-executable).

I will cover everything you need to know to have a basic understanding of buffer overruns here. If you want to delve more deeply, here is some suggested reading:
http://www.phrack.com/issues/49/14.html#article
https://www.ethicalhacker.net/columns/heffner/smashing-the-modern-stack-for-fun-and-profit

To learn buffer overruns, and to play Vortex or any other wargame server, you must know how to use gdb to examine memory. I will show some tips/tricks here.

Okay, buffer overruns. What's wrong with this code snippet?

vulnerable1.c                                                                                                                                             

#include <stdio.h>
#include <string.h>
int main(){
  char buffer[80];
  strcpy(buffer,argv[1]);
  return 1;
}

buffer is a char array of size 80. We then use strcpy to take argv[1] (whatever input the user supplies at the command line) and copy it into the buffer variable. No bounds checking is performed here, so the user can write an arbitrary length of data into the buffer variable. This will generally lead to a seg fault, and will crash the program. Unless the attacker is clever...

Let's look at how the stack is laid out for this particular program:

Usually, the stack grows from higher memory (return address) to lower memory (buffer). The function stack layout is:
higher memory -------------------------------------------------------------------------------lower memory
[arguments to the function][return address][saved framepointer(%ebp)][local variables]

A new stack is generated every time we call a function. Further, the memory address of the next instruction to be executed by the cpu after we return from the called function is pushed onto the stack. So if we had code like:
int x = 1;
<call to function>
x += 1;
Then the address of the instruction x+= 1; would be pushed onto the stack when we call <call to function>. This is so that when we return from <call to function>, the cpu knows where to resume execution.
Now, if you were somehow able to gain control of that return address, then you could point it to any accessible address in memory. Basically, you control the program at that point. But, how do we gain control of the return address?
In this case, since no bounds checking is enforced on our input, it is trivial. If you want to follow along, compile vulnerable1.c with the following options:
gcc -ggdb -fno-stack-protector -mpreferred-stack-boundary=2 -o vulnerable1 vulnerable1.c

This will align the stack and turn of stack-smashing detection, necessary if you want these examples to work on a modern Linux system. You will also need to disable stack randomization (ASLR). Do this by running the following command:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
This will not persist through a reboot.

Now, let's run it:
root@bt: ./vulnerable1 $(python -c 'print "A"*80+"B"*4+"C"*4')

We get a seg fault, predictably.
Let's fire up gdb:
root@bt: gdb ./vulnerable1
gdb: break main
gdb: run AAAA...ABBBBCCCC  (that's 80 A's, 4 B's, and 4 C's)
gdb: x/90xw $esp  (look at the top 90 words in hex of the stack ($esp))
gdb: s  (single step past the strcpy)
gdb: x/90xw $esp (look at the top of the stack again, should see our input)
gdb: c (continue)
seg fault, 0x43434343 in ?

Note, 0x41 is "A" in hex, 0x42 is "B" in hex, and 0x43 is "C" in hex, so our 4 "C"s overwrote the return address! The "B"s overwrote ebp, and the 80 "A"s filled in the buffer. Thus, we have successfully overwritten the return address with 0x43434343 by writing past the buffer (supplying 88 pieces of data instead of only 80 or less).

That's all for this round. We've seen that the entire goal in a buffer overrun attack is to gain control of the return address on the stack. Once we have that, we can take care to point it to something else (like, our own function that spawns a shell). We will see in the next posting just how to do that.

No comments:

Post a Comment