Dissecting stack based buffer overflow

  • Post author:
  • Reading time:7 mins read

what is a buffer?

In general, the term buffer is a temporary storage, a space in the memory used to store the data.

Memory Organization:

Memory Allocation
Memory Allocation

Stack: Contains arguments which are passed to the function and local variables.
Heap: Contains the dynamically allocated memory (malloc()).
Data:
– Initialized data segment: Contains the global, static and constant data.
– Uninitialized data segment (BSS): Contains uninitialized data.
Code or Text : Contains actual executable code or executable instructions.

More on Stack:

Stack is LIFO data structure, that is Last in First Out. Anything which is put into the stack last is the first which will have to be removed. Stack always grows down the memory address, from higher to lower and ESP points to the top of the stack.

Stack grow down
Stack grow down

Stack supports two operations:

PUSH:  Pushing data into the stack. (After push fix ESP to top of the stack)
POP: Remove the data from the stack.(After remove fix ESP to top of the stack)

What is buffer overflow?

Buffer overflow occurs when the larger data is written to the buffer without checking the actual size of the buffer. It is due to an improper bound checking and results in overwriting the adjacent memory locations.

Stack overflow :
– Overwriting a local variable.
– Overwriting the return address.
– Overwriting a function pointer.
– Overwriting a parameter of a different stack frame or a nonlocal address.

Heap overflow :
Generally, heap overflow will happen in the heap area when allocating the memory dynamically using runtime memory allocation techniques.

More on Stack Based overflow:

Let’s consider some sample code which is vulnerable to stack based buffer overflow.

#include<stdio.h>

Input()
{
char buf[8];

gets(buf);    /gets() itself is dangerous function because  it does not do the bound check./
puts(buf);

}

main()
{
Input();

return 0;
}

In the above code gets(); is used which is going to get input from the user and writes into the variable ‘buf’ without checking the size of the variable ‘buf’.  Suppose, if user supplies more than 8 bytes of data and gets() will still happily go ahead and write that onto the memory.

Run the Program :
Lets go ahead and run the the program with normal size of the buffer.

Normal execution of the program
Normal execution of the program

Here, we had given the input string “SecPod” which is less than the size of the buffer, so the program will print the string “SecPod” and exits normally.

Now, go ahead run the program with large data input.

Abnormal execution of the program
Abnormal execution of the program

Observe that the given string is “SecPod SecPod”, which is more than the size of the buffer available. So when executed, the program will throw “Segmentation fault (core dumped)”.

Let’s analyse the source code to understand further.

Vulnerable program stack layout:

Vulnerable program stack layout
Vulnerable program stack layout

Observations:
– Input() functions is not taking any argument, thus there are no arguments which are pushed into the stack.
– When main() program transfers control to the input() function, it will push the next instruction ie ‘return 0’ on to the stack.
– Once the execution goes to the input(), old value of the EBP will be stored and then local variables will be pushed into the stack.
– 8 bytes of buf variable is stored in the stack and 4 bytes of the old EBP is stored.

Now lets try it wirh GDB to get a better understanding.

Run the program with GDB: 

run with gdb

  •  Compile the program with gdb.

gcc -ggdb -fno-stack-protector -mpreferred-stack-boundary=2 -o stack_bof.o stack_bof.c

  •  Run the  program with gdb.

gdb ./stack_bof.o

  • List the source code in gdb.
    – (gdb) list
  •  Set the the two break points , first one at line 13 and second one at line 7
  •  (gdb) break 13
    Breakpoint 1 at 0x8048425: file stack_bof.c, line 13.
    (gdb) break 7
    Breakpoint 2 at 0x804840a: file stack_bof.c, line 7.
    (gdb)

Disassembled code:

dissassemble the code
disassemble the code

Stack observation:

Stack observetion
Stack observetion

Overwritten return address:

return address is overwritten with input string
return address is overwritten with input string

Execute some crafted code:

Let’s slightly modify the original program code by adding a new function called shouldNotExecute() and compile the program again and run.

#include<stdio.h>

shouldNotExecute()
{

printf(“should not execute\n”);

}

Input()
{
char buf[8];

gets(buf); /gets() itself is dangerous function because it does not do the bound check./
puts(buf);
}

main()
{
Input();
return 0;
}

Run the above program

gcc -ggdb -fno-stack-protector -mpreferred-stack-boundary=2 -o stack_bof.o stack_bof.c

Load into the gdb at get the address of the function shouldNotExecute()

gdb ./stack_bof.o

Execution of shouldNotExecute() function
Execution of shouldNotExecute() function

Notice that shouldNotExecute() function is not called from anywhere. With special crafted inputs it is possible to execute the shouldNotExecute().

-AnTu