Lesson 7 of 9 · 24m
Debuggers, Breakpoints, and Watchpoints
Static analysis shows every path a program can take; the debugger shows the one it does take. Four commands and one mindset dissect almost anything.
Static analysis shows you every path a program can take; the debugger shows you the one it does take. When the listing and your model disagree — and they will — thirty seconds in a debugger settles the argument that an hour of staring couldn't. Learn four commands well and you can dissect almost anything.
The gdb minimum
$ gdb -q ./crackme
(gdb) set disassembly-flavor intel
(gdb) starti # stop at the very first instruction
(gdb) break strcmp # break on an imported function
(gdb) c
(gdb) info registers rdi rsi # args live in rdi/rsi — read them
(gdb) x/s $rdi # examine as a C string
(gdb) x/32bx $rsi # 32 bytes, hex
(gdb) si / ni # step into / step over one instructionbreak strcmp alone solves half of hobbyist crackmes: run to the call, x/s $rdi, x/s $rsi, and the expected input is sitting in the registers. Real work needs more finesse, but the reflex is identical — stop where the decision happens, read the operands.
Watchpoints: ask "who writes this"
The killer feature isn't breakpoints — it's watchpoints, "stop when this memory changes":
(gdb) watch *(int*)0x555555558040 # break on any write
(gdb) rwatch *(int*)0x555555558040 # break on any readFound the flag byte that decides licensed-versus-not? Don't trace code forward hoping to find its writer — watchpoint the byte, run, and the debugger hands you every function that touches it, in order, with the call stack at each touch. It's a dynamic xref, and for "where does this value come from" it's often faster than static analysis.
Cut the noise
A breakpoint inside a hot loop that fires 40,000 times is worse than useless. condition 1 $rdi == 0x41414141 makes it selective; ignore 1 39999 skips the warm-up; commands blocks that print and continue turn breakpoints into instrumentation. On Windows, x64dbg does the same job with a GUI — breakpoint-by-API-name, a memory map you can set breakpoints on by region, and "run to user code" to skip loader transitions.
PIE, ASLR, and why your breakpoint never hit
Modern binaries are position-independent: the listing's 0x1180 lives at base + 0x1180 at runtime. info proc mappings gives you the load base; gdb also sets disable-randomization on by default, so at least addresses stay put between your runs. Rebased addresses are the number-one cause of "my breakpoint never fires" in new reversers — the second is breaking on a file offset instead of a virtual address.
Field notes
catch signal SIGSEGVandcatch syscallare underused — crash analysis starts at the signal, not wherever you happened to stop.- One run is one path. Inputs, environment, and timing change control flow; a conclusion drawn from a single execution is a hypothesis, not a finding.
- Some programs watch back:
ptrace(PTRACE_TRACEME)self-checks, timing loops, debugger-detection APIs. When a binary behaves differently under the debugger, that difference is the check — patch it (next lesson) and continue.