Introduction

This summer I took CS161 at UC Berkeley, an introductory course in computer security. One of the topics covered in the course was buffer overflows, which are a common type of vulnerability that can be used to execute arbitrary code on a system.

With nearly 60% of all cyber attacks involving some form of buffer overflow, it is crucial to understand how they work and how to prevent them. In this post, I will explain what a buffer overflow is, how it works, and how to prevent it.

What is a Buffer Overflow ?

A buffer overflow is a type of vulnerability that occurs when a program tries to write data to a buffer that is too small to hold the data. This can cause the data to be written to memory locations that are outside the buffer, which can cause the program to crash or behave in unexpected ways.

Buffer Overflow

Here buffer A has a length of 8 bytes, and we can see it has been filled and therefor the next byte will be written to buffer B. This is an example of a buffer overflow. This technique can be used to overwrite some data in memory, such as the return address of a function, which can be used to execute arbitrary code.

What is the call stack ?

Before we can understand how a buffer overflow works, we need to understand how a program is executed. When a program is executed, it is loaded into memory and the operating system creates a stack for the program. The stack is a region of memory that is used to store local variables and function parameters. When a function is called, the parameters are pushed onto the stack, and when the function returns, the parameters are popped off the stack. The stack grows downwards, so the parameters are pushed onto the stack from the top, and popped off the stack from the bottom.

Stack execution

Here we can see the stack layout for a function call, all you need to understand is that the return address of a function is just after its local variables (in memory). Therefor if we can write past the end of the buffer (local variable), we can overwrite the return address of the function and redirect the execution flow of the program.

How does it work ?

Let’s take a look at a simple C program that is vulnerable to a buffer overflow.

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    char buffer[8];
    strcpy(buffer, argv[1]);
    printf("Buffer: %s\n", buffer);
    return 0;
}

This program takes a string as an argument and copies it into a buffer. The buffer is 8 bytes long, but the program does not check the length of the string before copying it into the buffer. This means that if the string is longer than 8 bytes, it will overflow the buffer and overwrite the return address of the main function.

But what if we provide a strategically crafted string that will overwrite the return address of the main function with the address of some code that we want to execute? Well, it turns out that this is exactly what we can do with a buffer overflow.

Mitigations and Prevention

Stack Canaries

A stack canary is a value that is placed on the stack between the local variables and the return address. The value of the canary is checked before the function returns, and if it has been modified, the program will crash. This prevents buffer overflows from overwriting the return address, but it does not prevent them from overwriting other data on the stack.

Stack Canary

Before the function returns, the value of the canary is checked. If it has been modified, the program will crash.

Address Space Layout Randomization (ASLR)

Address Space Layout Randomization (ASLR) is a technique that randomizes the location of the stack, heap, and libraries in memory. This makes it more difficult for an attacker to predict the location of the stack, heap, and libraries in memory, which makes it more difficult to exploit a buffer overflow.

Non-executable Stack

A non-executable stack is a stack that is marked as non-executable in the page table. This prevents the stack from being executed, which prevents an attacker from executing arbitrary code on the stack.

Of course, all modern operating systems have these mitigations enabled by default, but it is still important to understand how they work and to write code that is not vulnerable to buffer overflows.

Conclusion

Buffer overflows are a common type of vulnerability that can be used to execute arbitrary code on a system. They are caused by programs that do not check the length of a string before copying it into a buffer. This can be prevented by using stack canaries, address space layout randomization, and non-executable stacks.

Sources