A CTF and exploit-development framework that streamlines binary interaction, remote connections, shellcode, and payload construction into a concise scripting API.
pwntools (from pwn import *) collapses the repetitive plumbing of exploit development — connecting to a service, packing addresses, building cyclic patterns, assembling shellcode, spawning processes — into terse, readable helpers. It is the standard toolkit for binary-exploitation CTF challenges and for prototyping proof-of-concept exploits against software you are authorized to test. Its tube abstraction gives a uniform interface over local processes, remote sockets, and SSH.
pip install pwntoolsfrom pwn import context, process, remote
context.log_level = "info"
context.arch = "amd64"
# Local process
io = process("./vuln")
io.recvuntil(b"name: ")
io.sendline(b"A" * 32)
print(io.recvall(timeout=2))
io.close()
# Or a remote service on an authorized lab target
# io = remote("127.0.0.1", 9999)process and remote both return a tube with the same interface, so an exploit developed locally works unchanged against a lab service.
| API | Purpose |
|---|---|
process(argv) |
Spawn a local process as a tube |
remote(host, port) |
Connect to a network service as a tube |
io.send(data) / io.sendline(data) |
Write bytes |
io.recv(n) / io.recvline() / io.recvuntil(delim) |
Read bytes |
io.interactive() |
Hand the tube to your terminal |
io.close() |
Release the tube |
context.arch/os/log_level |
Global settings that affect packing and assembly |
p32/p64(value) / u32/u64(data) |
Pack and unpack integers |
cyclic(n) / cyclic_find(value) |
Generate and locate an offset pattern |
ELF(path) |
Parse a binary — .symbols, .got, .plt, .address |
ROP(elf) |
Build ROP chains |
asm(code) / disasm(data) |
Assemble and disassemble |
shellcraft |
Shellcode templates |
Tubes work as context managers: with remote(host, port) as io:.
Connect to a remote service and interact (authorized CTF/lab target):
from pwn import remote
io = remote("challenge.lab.local", 1337)
io.recvuntil(b"name: ")
io.sendline(b"tester")
print(io.recvline().decode().strip())
io.close()Hello, tester!
Packing, cyclic patterns, and offset discovery for a buffer overflow:
from pwn import p64, cyclic, cyclic_find
# Build a De Bruijn pattern to locate the overflow offset.
pattern = cyclic(200)
# Suppose the crash shows RSP contained b"kaaa"; find its offset.
offset = cyclic_find(b"kaaa")
print("offset:", offset)
# Craft a payload: padding + return address.
payload = b"A" * offset + p64(0x401156)
print("payload len:", len(payload))offset: 40
payload len: 48
Assemble shellcode and inspect an ELF with the bundled helpers:
from pwn import asm, context, ELF
context.arch = "amd64"
shellcode = asm(shellcraft.sh()) if False else asm("xor rdi, rdi; mov rax, 60; syscall")
print("shellcode bytes:", shellcode.hex())
elf = ELF("/bin/ls")
print("PIE:", elf.pie, " NX:", elf.nx)shellcode bytes: 4831ff48c7c03c000000 0f05
PIE: True NX: True
- Exploit development — script buffer-overflow, ROP, and format-string PoCs with clean address packing and I/O.
- CTF binary exploitation — the standard framework for pwn challenges;
remote()/process()tubes andcyclicpatterns speed the whole workflow. - Shellcode assembly — build and test architecture-specific shellcode via
asm()andshellcraft. - Binary triage — inspect ELF protections (NX, PIE, RELRO, canary) with the
ELFandchecksechelpers. - Service interaction — automate protocol exchanges with network services during authorized testing.
Warning
Use only against binaries and services you own or are explicitly authorized to exploit (CTF, personal labs, sanctioned engagements).
- Sending
strinstead ofbytes— pwntools works in bytes throughout; useb"...". - Forgetting to set
context.arch, sop64()andasm()produce output for the wrong architecture. - Hardcoding offsets or addresses copied from a write-up — they are specific to one binary, libc, and environment. Derive them with
cyclic/cyclic_findin your own lab. - Ignoring ASLR, PIE, NX, and stack canaries — check with
checksecbefore assuming an approach will work. - Omitting
timeout=on receive calls, hanging the exploit. - Using
recv()when you needrecvuntil(), producing race-dependent behaviour. - Leaving
io.interactive()in an automated script.
[!warning] Lab and CTF use only pwntools is exploit-development tooling. Use it only against binaries and services you own, intentionally vulnerable targets, or CTF challenges you are authorized to solve.
- Never point an exploit at a system you do not own or lack written authorization to test. Successful exploitation is unauthorized access, which is a criminal offence in most jurisdictions regardless of intent.
- Offsets and addresses are environment-specific. Any address in a tutorial — including in this course — is illustrative. Derive your own with
cyclic,ELF, and a debugger against your own target. - Work in an isolated VM. Exploit development crashes processes and can corrupt state; keep it off any machine that matters and off any network with production systems.
- Disable protections only inside your lab (
setarch -R,ulimit -c), never on a shared or production host. - Exploit code is dual-use. Handle it, and any target binary, as sensitive material; do not publish working exploits against unpatched third-party software.
- The educational goal is understanding memory-safety failures so they can be found and fixed — not deployment.
- Set
context.arch/context.os(orcontext.binary = ELF(...)) early so packing and shellcode match the target. - Develop against a local
process()first, then swap toremote()— the tube API is identical. - Use
context.log_level = "debug"to see raw bytes on the wire while debugging. - Prefer
cyclic()/cyclic_find()over manual offset counting. - Keep exploit scripts, target binaries, and authorization notes together for reproducibility.
- [[Scapy]] — lower-level packet control for network-facing exploits
- [[Paramiko]] — SSH automation alongside pwntools'
sshtubes - [[Readme|Python for Security Professionals]] — course home