Chapter 2 - Stack Overflows

C and C++ Lack Bounds-Checking

#include <stdio.h>
int main()
{
    int array[5] = {1, 2, 3, 4, 5};
    printf("%d\n", array[5]);
}

In this program, the programmer created an five elements long array in C. The programmer made a mistake by forgetting that an array of size five begins with element zero, array[0], and ends with element four, array[4]. So, he tries to print what he thought to be the fifth element of the array, but in reality the program will read beyond it, into the "sixth" element.

We can compile this program using gcc. Since it will be compiled on a 64-bit processor and the program in the book was compiled on a 32-bit one, we need to use the flag -m32 to compile it as 32-bits. Also, to enable source code debugging using gdb we will set the -g flag:

We can notice that the gcc compiler doesn't throw any errors, but once we execute the program the output is not what it was expected.

This shows us how easy it is to read past the end of a buffer. In a real world scenario, this would be considered as an information disclosure vulnerability.

"A buffer is a limited, contiguously allocated set of memory, usually represented as an array in C."

We can now use gdb (GNU Debugger) to debug the program.

gdb commands:

  • list show source code

  • run execute program

  • break insert breakpoint

  • x examine memory

  • disassemble show assembly code

  • continue resume execution

  • info registers see registers

  • info proc mapping see memory map

In the image above we can see that the numbers 1 through 5 were pushed to the stack plus a random value that represents the sixth element of the array.

Writing Past End of Array

#include <stdio.h>
int main()
{
    int array[5];
    array[10000] = 1;
}

In this example, the programmer created a five elements long array, and then sets 1 as the 10000th element, which writes way past the end of the array. By executing the program we can see a "Segmentation fault" error. This means the program crashed and its normally the desired outcome of a Denial of Service attack.

g666isildur@linux:~/cnit127/ch2$ ./ch2b 
Segmentation fault

The Stack

  • LIFO (Last-In, First-Out)

  • ESP (Extended Stack Pointer) register points to the top of the stack

  • EBP is typically used for calculated addresses on the stack (mov eax, [ebp+10] copies the data 16 bytes down the stack into the EAX register)

  • PUSH puts items on the stack

  • POP takes items off the stack

  • The stack's purpose is to make the use of functions more efficient

  • When a function is called, these things occur:

    • Calling routine stops processing its instructions

    • Saves its current state

    • Transfers control to the function

    • Function processes its instructions

    • Function exits

    • State of the calling function is restored

    • Calling routine's execution resumes

  • In other words...

    • Push function's arguments onto the stack

    • Call function, which pushes the return address RET onto the stack, which is the EIP at the time the function is called

    • Before function starts, a prolog executes, pushing EBP onto the stack

    • It then copies ESP into EBP

    • Calculates size of local variables

    • Reserves that space on the stack, by subtracting the size from ESP

    • Pushes local variables onto stack

Function Example

void function(int a, int b)
{
    int array[5] = {1, 2, 3, 4, 5};
}
int main()
{
    function(1, 2);
    printf("Returned from function\n");
}

This function does nothing else besides printing a message to the screen. The purpose of it is to understand the stack frame.

We debug the program with gdb and set a breakpoint and the call to the funtion and at the function ret like shown in the screenshot, then run the program.

The we can see that the program stops at the first breakpoint, before jumping to the function. Now we can type info registers to see where the stack frame starts and ends in the main function.

Now we know that the stack frame starts at 0xffffd618 and ends at 0xffffd610 (the stack frame goes from EBP to ESP and grows towards lower addresses).

We can now use x to examine the first 20 addresses of the memory in the stack frame of main().

The stack fram of main() can be seen by the highlighted region.

Then we can analyze function() by typing continue and making the program run until the next break at the return address.

We can see that the stack frame goes from 0xffffd600 to 0xffffd5e0. By examining ESP we can have a better look at function()'s stack frame, highlighted in the screenshot below.

To know which part of the addresses of ESP belong to the stack frame, we should realize that it is built on little endianess and that each address has its values reversed. So, we know that the function()'s stack frame ends at the address 0xffffd618 by counting from two in two bits from right to the left (xff = 600, xff = 5ff, xd6 = 5fe, x18 = 5fc)

By disassembling main() we can see how a function is called:

  1. push arguments onto the stack

  2. call the function

We can also disassemble function() to see how the prolog is executed:

  1. push EBP onto the stack

  2. mov ESP into EBP, starting a new stack frame

  3. sub from ESP, reserving room for local variables

If we take a close look at the next word after function()'s stack frame, we can notice that it has the address of the next instruction to be executed in main().

Stack Buffer Overflow Vulnerability

#include <stdio.h>
void user_input(void)
{
    char buf[30];
    gets(buf);
    printf("%s\n", buf);
}
int main()
{
    user_input();
    return 0;
}

This function allows the user to put as many elements into buf as the user wants. By compiling this program we can see that it throws a warning.

The compiler says that the 'gets' function is dangerous and should not be used. We can see the man page of gets to learn more about this function.

Now we know why we should never use gets. It will continue to store characters past the end of the buffer making it impossible to tell how many characters it will read.

By executing the program we can see that it takes user input and returns that same input, but by inputting a bunch of A's we get the segmentation fault error, indicated an illegal operation.

Running the program on gdb and setting a breakpoint after gets will let us examine what is happening in the stack.

In the screenshot above we can see outlined in yellow the current stack frame that goes from the address 0xffffd648 at EBP to the address 0xffffd620 at ESP. Then, we can see the current stack highlighted, and in it outlined in red the ASCII values for HELLO (stored in reverse, remember that this is little-endian) as well as the return value outside of the stack outlined in green. Now since the ASCII values appear in hexadecimal, when using a bunch of A's to overwrite the stack we can expect to see the ASCII value of A that is 0x41. Below is the ASCII table that confirms that.

Now running the program again, this time with a bunch of A's, we can see that we filled the stack frame with these many A characters and that the return value is overwritten with 0x41414141.

By typping continue we can examine the crash and check the EIP value.

The EIP value is now 0x41414141 , which means that it was controlled by user input.

pageLinux Buffer Overflow With Command InjectionpageLinux Buffer Overflow Without Shellcode

Last updated