Pages

About us

H4xOrin' T3h WOrLd

Sunny Kumar is a computer geek and technology blogger. He is a founder and editor of H4xOrin’ T3h WOrLd web-site. Always passionate about Ethical Hacking, Penetration Testing of Web applications, security, gadgets and ev-erything to go with it.His goal of life is to raise the awareness of Information Security, which is nowadays is the key to a successful business.

Introduction

This tutorial includes all the code you'll need to carry out these exercises. If you have the above tools you'll be able to compile all the code and test it out on your own (Linux) box. Note that you may need root privileges to make some of the modifications specified.

Buffer overflow vulnerabilities are some of the most prolific and dangerous types of attacks in computer security. The problem essentially boils down to two main factors. The first is that C doesn't enforce type checking and therefore if a programmer isn't careful to handle exceptions unexpected behavior may occur. The second problem is that many process programs written in C run with escalated privileges. This means that an exploit of such a program yields effective control at the level of the exploited process. Since many of these processes run as root, or SYSTEM, successfully exploiting them allows a malicious user a privilege escalation that amount to total control over the target machine.

Buffer overflow exploits are accomplished by mangling the way that C handles memory allocation. When a program in C begins, or starts a function, it allocates a stack of memory for that particular piece of the program. This stack consists of space for variables and data, as well as pointers to return flow control to the proper place in the stack. This allows stacks to grow dynamically as programs fork and carry out subroutines and other processes. This is efficient because the stack doesn't have to be initialized at the start of the program with room for every possible execution path of the program. Instead, as the program runs, memory is allocated on a per needed bases.

Programs don't run in a vacuum, however, and one process can't be allowed to own the stack entirely until it's completion. For this reason the return pointer on these individual pieces of the stack (called stack frames) is critical, so that at the end of the frame execution the processor can return to the original programmatic instructions and continue the program.

Because these frames are allocated dynamically and because they are of a fixed size, if a programmer is not careful it becomes possible to pass in more variable data than is reserved on the stack. For instance, if the following represents a frame:

You can see that there are 5 'slots' for data in the frame, the sixth slot is for the return pointer. What happens if the program tries to write 6 'slots' of data into the frame? An exception probably, but if the attacker is careful they could arbitrarily send the pointer to a different location in memory, perhaps a location that contains malicious code.

Turning Off Stack Randomization

Before we get too far into this tutorial lets make sure to create an 'easier' environment for our work. Check to make sure the Linux VA patch is disabled as follows. First check to see if randomize_va_space is set off (to zero):

justin@madirish$ cat /proc/sys/kernel/randomize_va_space
0

If you happen to find that it is on (set to one) then disable it using the following (you'll need root to do this):

This patch randomizes the stack pointer, making it more difficult to find our jump address to kick off the exploit. It's possible to carry out the exploit with this patch enabled, just much more difficult.

The most effective way to do this is to pass in malicious bytecode as part of the 'data' and then overwrite the return pointer with the location of the malicious bytecode. Even this process is tricky though, because the return pointer must point to the exact location of the exploit code or the code will fail. For instance, if the pointer lands in the middle of the exploit code it won't execute properly. A neat trick is to pad the start of the exploit shellcode with NOP (no operation) instructions. When the machine encounters a NOP it simply moves to the next instruction. If there are a series of NOP instructions preceding the malicious shell code then the pointer merely has to hit one of them, and then the instructions will cascade down the NOP's to the shellcode. This technique is called a NOP sled.

Other Fine Tuning

You may notice that a lot of tutorials online have a bunch of documentation that points to examining the core dumps of programs that throw segmentation faults. When utilizing your own modern Linux box you might find that your buffer overflow attempts are causing a segmentation fault, but not a core dump. Core dumps can be controlled (if you have sufficient privileges) at the command line as part of your user profile. To check if you have core dump enabled try:

justin@madirish$ ulimit -c
0

If you see the above output (a zero) it means your don't have the ability to view core dumps. Go ahead and change that using:

justin@madirish$ ulimit -c unlimited

This will enable you to view the core dump of your files using GDB. The syntax is:

justin@madirish$ gdb

Where is the name of the program that just caused the segmentation fault and is the name of the core dump file (note that this won't always be 'core').

A Further Look at Stack

When you read about buffer overflows you'll read a lot about stacks, heaps, frames and the buffer. It's all a little confusing, even if you understand some of the topics, so it's worth examining more closely. As programs are executed they are assigned blocks of memory. Ultimately these are just places in RAM. The processor runs through blocks of memory in order's supplied to them by the 'register'. The register keeps track of what instructions are to be passed to the processor and where they are located.

When a program starts it is assigned a block of memory that looks something like this:

Ok, so that looks a little weird, but it easily demonstrates how the stack can grow down and the heap can grow up. Those are the two areas where dynamic memory is utilized.

The stack is reserved for dynamic input and function variables. You need this to be dynamic because at run time a program has no idea what sort of input it will get or need to assign to a variable. Some variables might get their value from user input, some from the system, some from reading files, and so on. You can see how it would be impossible for the program to calculate what sort of input it would get (and how that might cause the program to branch) at run time. So the program lines up the instructions in the 'Low Addresses' (the Program Data) part of the diagram. As it runs into blocks of code it allocates space on the stack to hold the variables.

So lets say the program starts with main(). The computer allocates a frame on the stack for the main() function that holds it's variables, etc. So the stack looks something like:

I've drawn this upside down because it's easier to understand as a stack in this orientation. now the register points at the top of the frame and begins to feed instructions into the processor. Let's say the program calls a function called foo() in main() though. What happens then? Well, a new frame is added to the stack, with the return pointer at the end showing the register where to move next once it's done with the particular frame. Now the stack might look something like:

With the variables for foo() on top of the stack. The return pointer is at the end showing the register where to move in the stack to pick back up in main() at the point after the function foo() is called. It is important to note that these pointers show the register where to return to execute program instructions, values that are usually held in the bottom of the stack as program data. Knowing this you can see why a return pointer is necessary, rather than having the program just chew down the stack.

A Look at the Victim

Let's examine the following code for the 'blame' program. The code actually makes a rudimentary attempt to prevent a buffer overflow exploit, but one which doesn't work.

Looking at the code you can see that the main() function sets up the char variable scapegoat with a size set to the constant INPUT_BUFFER (which is 256). A pointer to this variable is now passed to the getline() function which copies the program's input using getchar() into the variable scapegoat. The problem with this function is even after the getline() function finishes and control returns to main() the buffer has been overflown, so the check for length that occurs as the next instruction:

happens too late (the chicken has already flown the coop). Let's begin to explore how this particular buffer overflow works.

Overflowing the Buffer

First let's ensure that we actually can overflow the buffer. We'll use a little Perl at the command line to create some input then check out what is going on using GDB. First I'll demonstrate blame working correctly, however:

Now the important piece of this output is the EIP. The EIP is the return pointer that we want to overwrite with our malicious address. Our input was passed in as a bunch of "A"'s (41 in hex), but the EIP seems to have a regular memory value. Let's try with only 256 A's instead and see what happens.

Ah, that's more like it. You see how the EIP is overwritten with the value of the string A. You might wonder why 256 bytes will overflow the buffer but a large value won't. The key here is actually in the blame code. The code does proper bounds checking to make sure the input can only be 256 bytes long, but the extra byte is actually introduced by the code itself. Inside the loop:

while ((c=getchar()) != EOF)
*s++ = c;
*s = '\0';

There is a critical error. The final declaration adds one extra byte to the sting, the null byte. This results in an error when a legal input of 256 bytes is passed into blame, the getline() function actually adds one extra byte and causes the buffer overflow.

A Simple Fix

A simple change in the function will prevent this behavior, but you can easily see how a vulnerability such as this could be overlooked. If getline is rewritten as:

The program functions safely regardless of the input size. This modification also prevents the gnarly segfault errors from showing up, effectively handling exceptions in a cleaner manner.

Back to Our Regularly Scheduled Exploit

Ok, so now back to our exploit. We know now that an input size of 256 bytes will cause a buffer overflow and overwrite the EIP. If we can exploit this weakness we can cause the program to execute some arbitrary commands.

Let's begin with what is arguably the most difficult part of this process, our shellcode. While there are shellcode generators out there online, it's a lot easier to be able to build your own, especially if you want to be able to craft very specific behavior out of your buffer overflow.

Generating Shellcode

Let's create some shellcode that makes blame print out the output "Now I p0wn your computer" instead of it's normal function. Usually you'll want a buffer overflow to spawn a shell or perhaps open a backdoor listening port on the target computer, but we'll keep it simple for now.

Shellcode, often referred to as bytecode, is basically just assembly language. Now, don't worry if you don't know a whole lot of assembly at this point, we're going to leverage some tools to help make it easier. The first thing we want to do is create a program to test our bytecode. Using the following:

We can substitute our shellcode for the "substitute shellcode here" portion. Go ahead and create the file shellcode.c by cutting and pasting the above. Next compile this program so we can use it (I'm assuming you know how to compile raw C code but I'll go ahead and be explicit here just in case):

justin@madirish$ gcc -o shellcodetest shellcodetest.c

This creates the executable shellcodetest in the current working directory. Now, it isn't going to work at this point since we don't actually have any shellcode assigned to the shellcode[] variable. Let's go ahead and tackle that challenge now.

For this task we're totally going to gank the hello.asm code fromhttp://www.vividmachines.com/shellcode/shellcode.html (see citations below) and modify it to suit our purposes. You can use C to generate your shellcode as well, but there are some problems that crop up along the way. For instance, you cannot have any null bytes (\x00) in your shellcode or it is interpreted as the end of text input (as the getchar() or other input function in the C program is reading input it stops as soon as it encounters a null, thus your shellcode won't be loaded into memory entirely). For now we'll gloss over how to modify your assembly code using 'xor' to get rid of these null bytes and keep things simple. The code we're going to use is as follows:

What we're doing here is using the programs nasm, ld, and objdump. The important values (the good stuff) are contained in the second column of the output (the part on the second line that reads 'eb 19'). If you copy all of these out and preface them with "\x" then you have valid shellcode. So copying out the above example gives us:

Lets test out the above code to make sure it works. Save the modified file as shellcodetest.c and compile it using:

justin@madirish$ gcc -o shellcodetest shellcodetest.c

Then test the shellcode to see if it works:

justin@madirish$ ./shellcode
now I p0wn your computer

Injecting the Shellcode

Ok, the next part of the process is to actually inject the shellcode into a running process with a buffer overflow exploit. First let's examine our overflow of the blame program. We know that with 256 bytes of A's that the EIP is overwritten with four bytes worth of A's 0x41414141. If we examine this behavior more closely we'll find that the A's that overwrite the EIP aren't in fact the last four A's of the payload.

You'll notice that it was actually the D character's (44 in hex) that overwrote the EIP. This is interesting to note since the D's occur between the 76th and the 80th characters of our input. Now, let's add our shellcode to the end, substituting it for the C's and prefacing it with a NOP sled:

[Note that if you cut and paste the above you may have to reformat on account of line breaks] Now lets test out our payload in gdb. What we're specifically going to look for is a memory address somewhere near the middle of our NOP sled. We're going to use this value instead of "DDDD" in our final payload. I'm going to double check the size of the malinput2 file as well, just to make sure it's 256 bytes.

Ok, so looking at the above example we can see our NOP (0x90) sled starting at 0xbffff000 and ending at 0xbffff077. Lets choose 0xbffff010 as our target address. Now, we have to rewrite this address in little endian format (if you overlook this then your address won't work), so it becomes:

\x10\xf0\xff\xbf

Now, lets plug this value in for the "DDDD" part in our payload and test it out:

And there you have it. Now, at this point the payload will only work in the gdb environment. In order to get it working in the wild we'll have to be a little more creative.

Release the 'Sploit

Ok, so now we've seen that the buffer overflow can be accomplished, but doing this outside of gdb is a little more tricky. At this point we're going to switch to the classic buffer overflow technique rather than leveraging the nuanced behavior of the "blame" code with a 256 byte input. We're going to go whole hog and shift our input to 420 bytes long. This is going to completely clobber the stack, overwriting the return pointer (and probably a lot more too). Of course, to do this we're going to have to come up with a tightly crafted payload and a good guess as to where the return address needs to return to.

Since stack space initializes with the same memory addresses regardless of program the easiest way to get a sense of where our payload will reside in memory is to create a new program that initializes a couple of variables and prints out their memory addresses. We can use this as a guide to put is in the right neighborhood for our exploit. Because our buffer is so small we're actually going to have to be fairly precise in our calculations. First let's look at the code that'll show us some addresses:

By listing the variable name with an ampersand preceding it the output will contain the address of the start of the variable rather than the contents of the variable (its value). Using these two values we can gauge where in memory we will need to point our exploit payload towards.

Next we have to craft a program that will put together our payload. This program will take two arguments, the size of the payload and an offset. The payload size has to be big enough to clobber the return pointer, but not so big that it completely wipes out the stack. In our case, using around 420 bytes will do the trick (using over 9 Megs will certainly clobber the stack and cause 'very bad things' to happen). Next we have to play with the offset to make sure it lands somewhere in the neighborhood supplied by our show_sp program. Here's the final program we'll use:

Now, putting this into action we'll first have to check out where we need our address, then we'll have to play with the numbers a little:

justin@madirish$ ./show_sp
First var: 0xbfffeff4
Next var: 0xbfffefee

Ok, so we're looking to hit an address somewhere around 0xbfffefee. Let's try a couple of addresses:

justin@madirish$ ./exploit 420 -400
Using address: 0xbfffeff4
Payload is now in file './payload''

This is close, although it's right at the edge. Let's give it a shot anyway:

justin@madirish$ cat payload | blame
Segmentation fault

No cigar, and what's worse is that there won't be any hints as to how close or far off you are. Let's just start walking through address space starting from an offset of -400 and see what happens when we hit -404:

Bingo! It worked. We got a valid return address in just the right spot at an offset of -404. Now, this program was pretty benign, but if we were to change the shellcode to do something more malicious, or if the program had been a suid program (set to run as another user, typically root) then we could have leveraged the privilege escalation to do all sorts of things.