pwntools is the standard library for exploitation: it turns "parse this, pack
that, connect here, attach a debugger" into a handful of calls. Install is
pip install pwntools. This is the slice I use without looking anything up.
the boilerplate#
Don't hand-write it — generate it and fill in the blanks:
|
That scaffolds the pattern every exploit starts from:
|
Setting context.binary once configures p64, asm, ROP, and the loglevel to
match the target. args.REMOTE reads from the command line, so one script runs
locally, under gdb, or against the server.
tubes — talk to the target#
process, remote, gdb.debug, and ssh all return the same tube interface,
so the I/O code never changes between local and remote:
| call | use |
|---|---|
io = process("./chal") |
run locally |
io = remote("host", 1337) |
connect to the server |
io = gdb.debug("./chal", GS) |
launch under gdb with a script |
gdb.attach(io, GS) |
attach to an already-running process |
io.recvuntil(b"name: ") |
read up to a delimiter |
io.recvline() / io.recvn(8) |
one line / exactly n bytes |
io.sendline(payload) |
send + newline |
io.sendlineafter(b"> ", payload) |
wait for prompt, then send (the workhorse) |
io.interactive() |
hand the shell to your keyboard |
make gdb.attach actually pop a window
Set the terminal once and gdb.attach/gdb.debug open a real split:
context.terminal = ["tmux", "splitw", "-h"] (inside tmux). On a fresh box
that one line saves a lot of swearing.
packing & offsets#
Find the overflow offset without counting — send a De Bruijn pattern, read back the value that landed in the saved RIP:
ELF & libc helpers#
The ELF object is a symbol/address oracle, and it stays correct after you slide
its base:
|
format strings#
fmtstr_payload computes the %hn/%n writes for you — give it the argument
offset and an {address: value} map:
That overwrites exit's GOT entry with win, so the next exit() calls your
function — the canonical format-string-to-control-flow move.
shellcode without writing assembly#
ROP#
ROP assembles chains and finds simple gadgets; the full treatment — find_gadget,
SROP, ret2dlresolve — is its own post: finding gadgets.
the CLI is half the value#
pwn is also a toolbox of subcommands that replace one-off scripts:
|
the loop I actually run
python3 solve.py GDB to develop against a breakpoint, python3 solve.py to
sanity-check locally, python3 solve.py REMOTE to fire. Same file, three
modes, because the template wired args in from the start. Set
context.log_level = "debug" when the I/O desyncs — it prints every byte sent
and received, which is almost always where the bug is.