< BACK TO TERMINAL

Owning Memory: Stack Buffer Overflow fundamentals

Introduction

This article demonstrates a classic stack buffer overflow and shows how a small, controllable overwrite of adjacent variables can change program behavior.

Prerequisites

Where it started

It all started in 1972, when a U.S. Air Force–commissioned Computer Security Technology Planning Study recorded one of the earliest descriptions of a buffer overflow attack.

References

James P. Anderson, Computer Security Technology Planning Study (October 1972)

Learn more about the author: James P. Anderson: An Information Security Pioneer

Proof of Concept

We begin by demonstrating how unchecked input can overflow a buffer and corrupt adjacent memory, allowing us to modify a variable that should be inaccessible through normal program flow.

The Vulnerable Code

#include <stdio.h>

int main(void)
{
    char private = 0;           // Flag we should not be able to set via input
    char buf[8] = {0};          // Only 8 bytes available

    printf("Input: ");
    fgets(buf, 20, stdin);      // Vulnerability: reads up to 19 bytes (+ NUL) into an 8-byte buffer

    if (private == 1) {
        printf("stack buffer overflow success\n");
    } else {
        printf("stack buffer overflow failure\n");
    }

    return 0;
}

Compilation

gcc demo.c -o demo.bin -no-pie -fno-stack-protector -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wl,-z,norelro -z execstack -Wno-stringop-overflow -Wno-stringop-overread -O0 -g

Memory Layout and Exploitation

We'll focus on the section of the stack where buf and private are located, demonstrating how overflow occurs in this region.

Normal Execution Flow

Before any input is provided, both variables are initialized to zero in memory:

With a safe input that respects the buffer boundary:

printf "AAAAAAA" | ./demo.bin # 8 bytes input including the null terminator (0x00)

The buffer is filled correctly, and private remains untouched. But what happens if we write more bytes than the buffer can hold?

Buffer Overflow Execution Flow

By providing input larger than the allocated buffer:

printf "AAAAAAAA\x01" | ./demo.bin # 10 bytes input including the null terminator (0x00)

If the compiler places private immediately after buf on the stack, the first byte past the end of buf will overwrite private, changing its value from 0x00 to 0x01.

Debugging with GDB

gdb ./demo.bin  # Launch gdb
(gdb) break main  # Break at program entry
(gdb) run  # Run the program
(gdb) print &buf  # Print the buf variable address
$1 = (char (*)[8]) 0x7fffffffdbe0
(gdb) print &private  # Print the private variable address
$2 = (char *) 0x7fffffffdbe8
(gdb) x/8bx &buf  # Inspect buf and the bytes immediately after it
0x7fffffffdbe0: 0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00  0x00

At this point, buf is all zeros (we explicitly initialized it), and private is also 0x00.

After the buffer is populated by our payload, we now see the character 'A' represented by 0x41 and the byte value 0x01 immediately after eight 'A' bytes. The overflow worked: the first byte written beyond buf[7] overwrote the next byte in memory, which is private. As a result, private changed from 0x00 to 0x01 and the program prints "stack buffer overflow success".

(gdb) next 2  # Step over fgets to populate the buffer
(gdb) x/8bx &buf  # Inspect buf and adjacent bytes
0x7fffffffdbe0: 0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x41  0x01
(gdb) x/bx &private  # Inspect the byte at private
0x7fffffffdbe8: 0x01

Redirecting payload

Entering a non-printable byte like 0x01 interactively is inconvenient. A simple, reliable workflow is to write the exact bytes to a file and then redirect that file into the program's stdin from within GDB.

Create a 9-byte payload:

python3 -c "open('payload.bin','wb').write(b'A'*8 + b'\\x01')"

You can then run the program under GDB with stdin redirected:

(gdb) run < payload.bin

Next steps

Now that we've seen how to overwrite adjacent memory, what else lies beyond the buffer? Can we overwrite more critical data structures, such as the saved return address (saved RIP/EIP)? The answer is yes, and this opens the door to complete control over program execution.

See Owning Memory: Stack-Based Code Injection

← Previous articleNext article →