This is the subset of pwndbg I use without thinking. It isn't the manual — it's the muscle memory, the commands that survive contact with a real target. Install is one line into a fresh gdb:
|
pre-flight#
Three commands before anything else, every time:
-
checksec— what am I up against (NX, PIE, RELRO, canary) -
start— break at the entry/mainand get a live process + mappings -
vmmap— where is everything actually mapped right now
the three I type most#
telescope- dereference a chain of pointers and label what each one points at —
telescope $rsp 30is how I read a stack.telescope -r $raxwalks backwards. It'sx/gxthat does the deref work for you. vmmap- the live memory map with permissions.
vmmap libcfilters to one module;vmmap $rsptells you which region an address lives in. This is your leak sanity-check — does that pointer land in libc, the heap, or the stack? context(aliasctx)- the register / code / stack / backtrace dashboard pwndbg prints on every stop.
contextreprints it;ctx code,ctx stackshow one pane when the full one scrolls off.
navigation & breakpoints#
| command | what it does |
|---|---|
b *0x401234 |
break at an absolute address |
b main / b file:42 |
break at a symbol or source line |
breakrva 0x1234 |
break at an offset — the right way under PIE |
b *$rebase(0x1234) |
same idea inline: $rebase() adds the PIE load base |
starti |
stop at the very first instruction (_start) |
ni / si |
step over / into one instruction |
nextcall / nextret |
run until the next call / ret — skip the boilerplate |
finish (fin) |
run until the current function returns |
set follow-fork-mode child |
follow the fork (forking servers) |
PIE addresses change, offsets don't
Never hard-code a PIE address from a previous run — ASLR moved it. Set
breakpoints with breakrva/$rebase against the file offset you saw in the
disassembler, and they survive every restart.
inspecting memory#
| command | what it does |
|---|---|
x/20gx $rsp |
20 giant (8-byte) words in hex from $rsp |
x/8i $pc |
next 8 instructions |
x/s 0x4040a0 |
read a C string |
hexdump $rax 0x40 |
pwndbg's annotated hexdump |
p/x $rdi / p $eax |
print a register, hex or decimal |
xinfo 0x7fff... |
"what is this address" — module, section, offset |
set {int}0x404060 = 1 |
poke a value into memory |
set $rip = 0x401200 |
redirect execution |
watch *0x404060 |
break when that memory changes (rwatch on read) |
the heap#
The reason pwndbg exists. All of these read glibc's live allocator state:
| command | what it shows |
|---|---|
vis (vis_heap_chunks) |
the heap drawn chunk-by-chunk, freelist links coloured |
heap |
every chunk in the current arena, top to top chunk |
bins |
all bins at once — tcache, fast, unsorted, small, large |
tcachebins |
just the tcache, per size class, with the next chain |
fastbins |
the fastbin freelist |
malloc_chunk 0x.. |
decode one chunk header (size, prev_size, flags) |
top_chunk |
the wilderness chunk and its size |
find_fake_fast 0x.. |
scan for a size field that lets you allocate onto an address |
arena / arenas |
the malloc_state for this / every arena |
- chunk
- glibc's allocation unit: an 8/16-byte header (
prev_size,sizewith its low flag bits) followed by user data.size & ~0x7is the real size. - tcache
- per-thread, single-linked cache of recently freed chunks; the first stop on
freeand the first served onmalloc. Poisoning itsnextis the modern "near-arbitrary write" — see tcache poisoning. - safe-linking
- glibc ≥ 2.32 stores
nextasptr ^ (chunk_addr >> 12), so overwriting a freelist pointer needs a heap leak first.visshows the demangled value.
search#
search is the swiss-army primitive — find a value, a string, or a pointer:
advanced — the one-liners I keep in a snippet file
Find a pop rdi; ret without leaving gdb:
|
Dump the GOT and PLT, see what libc calls are reachable:
Cyclic pattern in, offset out — no pen-and-paper:
Turn ASLR off for a deterministic session (then turn it back on before you trust an exploit):
|
don't ship an exploit you only tested with aslr off
A chain that works at a fixed base and breaks under ASLR means your leak is
wrong, not your gadgets. Test the final version with aslr on and from a
fresh run each time.