pwntools: A Python Library for Pwn Challenges

Tool · Pwn

pwntools is a Python library purpose-built for CTF pwn challenges. It doesn't find bugs for you, but it eliminates the boilerplate around exploiting one — opening a socket, packing an address into little-endian bytes, building a cyclic pattern to find an offset — so your exploit script reads as the logic of the exploit itself, not the plumbing around it.

Install and target selection

pip install pwntools

A pwntools script's first job is opening a connection to whatever it's attacking, and it uses the same API whether that's a local binary or a remote service:

from pwn import *

# Local binary, for testing
io = process('./vuln')

# Remote CTF service
io = remote('ctf.example.com', 1337)

Talking to the target

The io object exposes send/receive helpers that handle the parts of socket programming that get tedious fast: reading until a specific delimiter, sending a line with the newline already appended, waiting for a prompt before continuing.

io.recvuntil(b'Enter your name: ')
io.sendline(b'admin')
response = io.recvline()
print(response)

Packing and unpacking addresses

Binary exploitation means constantly converting between Python integers and the raw little-endian bytes a target expects — a stack address, a libc offset, a return address. pwntools' p32/p64 (pack) and u32/u64 (unpack) handle this in one call instead of manual struct.pack juggling:

from pwn import *

leaked_addr = u64(io.recv(6).ljust(8, b'\x00'))
payload = b'A' * 40 + p64(leaked_addr + 0x1234)
io.sendline(payload)

Finding a buffer offset with cyclic patterns

Before you can overwrite a return address, you need to know exactly how many bytes sit between the start of a buffer and the value you're overwriting. pwntools' cyclic() generates a non-repeating pattern; feed the address it crashed on into cyclic_find() and it hands back the offset directly, instead of counting bytes by hand:

payload = cyclic(200)
io.sendline(payload)
# ... target crashes, reading a return address of 0x6161616c from the core dump ...
offset = cyclic_find(0x6161616c)
print(offset)  # -> the exact byte offset to the saved return address

Why it's worth learning

None of this replaces understanding the underlying vulnerability — pwntools has no idea what a stack buffer overflow or a format string bug is. What it removes is the friction between finding the bug and weaponizing it: once you know what payload you need, pwntools gets you from "I know the exploit" to "the exploit is running against the remote service" in a handful of lines instead of a page of socket and struct code.

Back to Blog