Skip to content

Debugging with GDB

GDB (GNU Debugger) is the central tool for dynamic analysis on Linux: it lets you run a program step by step, stop it at specific points (breakpoints), inspect and modify registers and memory, and see exactly what it does at each moment. For pwn and reversing it’s essential: with GDB you confirm offsets, see the stack/heap state, debug your exploits, and understand code that’s unreadable statically. Powered with pwndbg or GEF, it becomes an exploitation swiss-army knife.

# bare GDB is awkward; pwndbg/GEF add exploit views
pwndbg github.com/pwndbg/pwndbg # register/stack/heap views, pwn commands
GEF github.com/hugsy/gef # a very complete alternative
# after installing, GDB automatically shows useful context at each stop
# start
gdb ./binary ; gdb -p <pid> # open binary / attach to process
run (r) [args] ; starti # run / stop at the 1st instruction
# breakpoints
break main (b main) ; b *0x401234 # stop at function / address
b *main+42 # offset inside a function
info breakpoints ; delete <n>
# execution
continue (c) ; next (n) ; step (s) # continue / next line / step into
nexti (ni) ; stepi (si) # next/step into at INSTRUCTION level (key in reversing)
finish # run until the current function returns
# inspection
info registers (i r) ; p $rip # registers
x/20gx $rsp # examine memory (20 qwords in hex from RSP)
x/s 0x404050 ; x/i $rip # as string / as instruction
disassemble (disas) func # disassemble
x/<count><format><size> address
# format: x hex, d decimal, s string, i instruction, c char
# size: b byte, h 2, w 4, g 8 (giant)
x/20gx $rsp # 20 qwords in hex from the stack pointer
x/s $rdi # the string pointed to by RDI (e.g. a function argument)
x/5i $rip # the next 5 instructions
# confirm an overflow offset
b *vuln+X ; run < <(cyclic 200) # on crash: cyclic -l $rsp
# see the state before a ret
b *func+ret_offset ; x/20gx $rsp
# debug an exploit with pwntools
from pwn import *
p = gdb.debug('./vuln', 'b *main\nc') # launch with GDB attached and breakpoints
# or: p = process('./vuln') ; gdb.attach(p, 'b *0x...')

pwntools’ gdb.debug/gdb.attach integrate GDB with your exploit script: you develop the exploit seeing memory at each step.

# follow logic that's hard to read statically
b *check_function
# step with ni/si watching registers and memory
# see what it compares: the correct password appears in a register/memory before the cmp
# modify live: set $rax = 1 (force a result), set {int}0x... = value
# skip a check: change the flow with set $rip = ...

GDB lets you modify the state live: change a register, a memory value, or RIP to skip checks and see what happens — very useful in crackmes.

# watchpoints: stop when a variable/memory changes
watch *0x404050
# conditions on breakpoints
b func if $rdi == 0x1234
# backtrace of the call stack
bt
# TUI (text interface) or just pwndbg/GEF's context

Malware and protected software detect GDB/debuggers (ptrace, timing, breakpoints) to hinder dynamic analysis — covered in rev-antidebug. The analyst bypasses them (patch the check, ptrace LD_PRELOAD, etc.).

  • Install pwndbg/GEF and understand the context they show
  • Breakpoints by function, address, and offset
  • Step-by-step execution (ni/si/finish/continue)
  • Examine memory with x (formats and sizes)
  • View and modify registers/memory/RIP live
  • Confirm overflow offsets (cyclic)
  • Integrate GDB with pwntools (gdb.debug/attach)
  • Use watchpoints and conditional breakpoints