Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
GDB (the GNU Debugger) is the standard Linux debugger for stepping through execution, inspecting registers and memory, and setting breakpoints — but its stock command-line interface is painfully low-level for exploit development: examining a stack frame or heap chunk means manually computing addresses and issuing raw x/ memory-examine commands. pwndbg is a GDB plugin, written by and for exploit developers, that replaces GDB’s default UI with a context-aware one: every stop shows registers, disassembly, and stack contents at a glance, and it adds purpose-built commands for heap analysis, ASLR/PIE-aware address resolution, mitigation checks, and cyclic-pattern offset finding.
The combination is the de facto standard for CTF pwn challenges and real vulnerability research on Linux binaries. Where plain GDB tells you a program crashed at some address, pwndbg tells you *why* — which mitigations are active, what the heap chunk metadata looks like at the point of corruption, whether a leaked pointer looks like a valid libc or heap address, and how far into a cyclic pattern the crash occurred. That turns hours of manual memory arithmetic into a handful of commands, mattering equally for attackers writing exploits and defenders validating a patched binary actually resists the technique it was patched against.
Attack Prerequisites
Using GDB/pwndbg for exploit development assumes local analysis capability against the target binary or a faithful reproduction of it:
- A runnable copy of the target binary (or a remote target reachable via
gdbserver/target remote), ideally matching the exact libc and loader the production target uses — pwntools’pwninit/patchelf workflow is the common way to pin this. - pwndbg installed against a compatible GDB —
git clone+ itssetup.sh, or a distro package; it hooks GDB’s Python API, which needs GDB built with Python scripting support (virtually all modern distro builds are). - Symbols or a good guess at the binary’s structure — stripped binaries still work, but function boundaries, the heap allocator in use (glibc ptmalloc2 vs. musl vs. custom), and calling convention affect readability.
- Explicit authorization / an owned target — debugging production or third-party systems without permission is out of scope; this workflow is for local binaries, labs, and CTF targets.
How It Works
GDB works by using the OS’s ptrace(2) facility to attach to (or launch and control) a process, intercepting signals and reading/writing its memory and register state. Breakpoints are implemented by temporarily overwriting the target instruction byte with an int3 (0xCC on x86) trap opcode; when the CPU hits it, control returns to the debugger, which restores the original byte, reports the stop, and lets the operator inspect state before continuing. Everything pwndbg adds — register panel, disassembly context, heap commands — is built on this same ptrace/ELF-parsing foundation via GDB’s Python scripting API (gdb.Value, gdb.Breakpoint, and friends).
pwndbg’s most exploit-relevant feature is heap introspection: it parses the glibc malloc_state (main_arena) structure and chunk headers directly out of the debuggee’s memory, so heap, bins, and chunk <addr> commands show chunk size, PREV_INUSE/IS_MMAPPED flags, and free-list linkage without the operator computing any of it by hand. That’s what makes UAF and heap-overflow primitives tractable to develop: you can watch a tcache bin’s forward pointer (fd) get corrupted in real time and confirm a poisoning primitive actually redirects malloc() before running it against the real chain.
The other core capability is mitigation-aware analysis. checksec reads the binary’s ELF headers to report Canary, NX, PIE, RELRO, and Fortify status; vmmap reads /proc/<pid>/maps to show the live memory layout (and, with ASLR enabled, each region’s actual randomized base on this run); and address classification (pwndbg tags pointers as stack, heap, or a specific mapped library) lets an operator instantly tell whether a leaked value is a usable pointer or garbage — critical before building on an info-leak.
Practical Example / Configuration
A real pwndbg session against a locally built vulnerable binary (./vuln), from launch through mitigation check, breakpointing, and offset discovery. First, check what protections are compiled in — this decides the whole exploit strategy (no canary means a straight stack smash is viable; PIE means an info leak is needed first):
$ gdb -q ./vuln
pwndbg> checksec
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Stripped: NoBashSet a breakpoint at main and run, so we can inspect the fresh process image before any input is consumed — pwndbg’s context panel (registers, disassembly, stack) prints automatically on every stop:
pwndbg> b main
Breakpoint 1 at 0x1234: file vuln.c, line 12.
pwndbg> run
* REGISTERS * RAX 0x0 RBX 0x0 RDI 0x1 ...
* DISASM * 0x5555555551a9 <main+8> mov edi, 0x0
* STACK * 00:0000│ rsp 0x7fffffffe420 ...
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x555555554000 0x555555555000 r--p /home/user/vuln
0x555555555000 0x555555556000 r-xp /home/user/vuln
0x7ffff7dc0000 0x7ffff7de3000 r--p /usr/lib/libc.so.6
0x7ffffffde000 0x7ffffffff000 rw-p [stack]Bashtelescope walks a memory region printing each pointer-sized value with automatic dereferencing and type annotation — invaluable for reading a stack frame or a chain of heap pointers at a glance instead of issuing x/8gx and mentally resolving each address:
pwndbg> telescope $rsp 10
00:0000│ rsp 0x7fffffffe420 ◂— 0x0
01:0008│ 0x7fffffffe428 ◂— 0x1
02:0010│ 0x7fffffffe430 —▸ 0x7fffffffe4c8 ◂— 'AAAABBBBCCCC'
03:0018│ 0x7fffffffe438 —▸ 0x555555555189 (main+0x1c) ◂— mov eax, 0
...
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5555555592a0
Size: 0x21
pwndbg> bins
tcachebins
0x20 [ 1]: 0x5555555592c0 —▸ 0x0BashFor a stack-overflow, generate a De Bruijn cyclic pattern, feed it to the target, and once it crashes let pwndbg compute the exact offset to instruction-pointer control automatically from the crashing register value — no manual pattern math:
pwndbg> cyclic 200
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaas...
pwndbg> run <<< $(python3 -c "import sys;print('aaaabaaacaaadaaaeaaafaaagaaahaaai...')")
Program received signal SIGSEGV
pwndbg> cyclic -l $rsp
Finding cyclic pattern of 8 bytes: b'haaaiaaa' (offset 8 in rsp)
Found at offset 40BashWalkthrough / Exploitation
Putting the primitives together into an exploit-dev loop: confirm the crash is controllable, locate the exact write offset, then build and validate the payload incrementally rather than guessing the whole chain in one shot.
# 1. Confirm RIP control at the offset found above
pwndbg> run <<< $(python3 -c "print('A'*40 + 'BBBBBBBB')")
pwndbg> print $rip
$1 = (void (*)()) 0x4242424242424242 # confirmed: offset 40 -> RIP
# 2. PIE is enabled, so leak a libc pointer via the target's own output,
# then compute the base:
pwndbg> vmmap libc
0x7ffff7dc0000 0x7ffff7de3000 r--p /usr/lib/libc.so.6
pwndbg> p/x leaked_addr - 0x1d0000 # offset of leaked symbol within libc
# 3. Resolve a ROP gadget against the recovered base, then re-verify under
# the debugger before sending it to the real target:
pwndbg> ropper --search "pop rdi; ret"
pwndbg> b *0x555555555230
pwndbg> cBashEach iteration re-runs under GDB so telescope/heap/vmmap confirm the exploit does what the offline calculation predicted before it fires against a real target with no debugger attached.
Note:
cyclic/cyclic -land pwntools’cyclic()/cyclic_find()use the same De Bruijn sequence generator, so an offset computed in pwndbg matches one computed in a pwntools exploit script — this is why the two tools are almost always used together, pwndbg for interactive discovery and pwntools for the scripted final exploit.
Opsec:
checksecdescribes the binary on disk, not necessarily the environment it runs in — ASLR can be disabled system-wide (/proc/sys/kernel/randomize_va_space), and containerseccompfilters can block syscalls an exploit relies on. Validate assumptions against the actual target environment, not just the local debug session.
Detection and Defense
GDB/pwndbg activity is local, offline analysis and is not itself a network detection surface — the relevant controls are about hardening the binaries and environments this tooling is used to attack or validate:
- Compile with full mitigations — stack canaries (
-fstack-protector-strong), full RELRO (-Wl,-z,relro,-z,now), PIE, and NX remove the easiest primitives pwndbg makes visible. - Harden the allocator — glibc’s tcache double-free/safe-linking checks and
_FORTIFY_SOURCEwrappers close off many classic heap primitives. - Fuzz and sanitize before release — AFL++/libFuzzer plus ASan/UBSan catch the same overflow/UAF classes far earlier and at far greater scale.
- Restrict
ptracein production — Yama’sptrace_scopesysctl, seccomp profiles, andCAP_SYS_PTRACEdenial stop interactive dynamic analysis against a running production process. - Monitor build/CI hosts for anomalous debugger activity — unexpected
gdbserver,ptraceattach syscalls, or GDB spawned by unusual parents.
Real-World Impact
GDB has been the standard Linux debugger since 1986 and remains the foundation nearly every Linux exploit-dev and CTF workflow builds on; pwndbg (alongside similar projects GEF and peda) is maintained specifically for offensive security work and is a default component of pwn-focused CTF environments and vulnerability research labs. The same heap and mitigation introspection that speeds an attacker’s exploit-dev loop is equally used by defenders to confirm a patch or compiler mitigation actually closes the primitive it was meant to, rather than assuming it from the source diff.
Conclusion
GDB provides the raw process-control primitives, and pwndbg turns them into an exploit-development cockpit — automatic mitigation checks, heap-chunk visualization, address classification, and cyclic-pattern offset discovery replace pages of manual x/ commands and mental arithmetic. The workflow of checksec, breakpoint, telescope/heap/vmmap, and cyclic pattern-matching is the same whether the session builds an exploit or proves one no longer works against a hardened target.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments