Introduction
GDB (the GNU Debugger) is the most important tool in your reverse engineering and binary exploitation kit. It lets you pause a running program at any point, inspect every register and memory address, modify values on the fly, and step through assembly one instruction at a time. If you can use GDB, you can understand any Linux binary, no matter how obfuscated the source.
This guide walks through GDB from first principles, with a focus on the patterns that appear in picoCTF reverse engineering and binary exploitation challenges. By the end you will be comfortable with the GDB baby step series, GDB Test Drive, and the setup needed for format string and heap challenges.
Installing GDB
GDB ships with most Linux distributions. On Ubuntu or Debian:
sudo apt install gdb
To verify the install and check the version:
gdb --version
Running a binary in GDB
Start GDB with a binary as its argument. GDB opens an interactive prompt but does not run the program yet.
gdb ./challenge_binary
From the GDB prompt, use run (or r) to start the program:
(gdb) run(gdb) run arg1 arg2 # pass arguments
If the binary needs input piped from a file:
(gdb) run < /tmp/input.txt
To quit GDB at any time:
(gdb) quit
Breakpoints
A breakpoint pauses execution at a specific location. The most common targets are function names and line numbers. Use break (or b) to set one:
(gdb) break main # break at the start of main(gdb) break *0x401234 # break at a specific address(gdb) break check_password # break at a named function
After setting a breakpoint, run the program. GDB pauses when the breakpoint is hit. Then use continue (or c) to resume, next (or n) to execute the next source line without entering function calls, or step (or s) to step into function calls.
(gdb) continue # run until next breakpoint or end(gdb) next # next line (step over calls)(gdb) step # next line (step into calls)(gdb) nexti # next instruction (assembly level)(gdb) stepi # step one instruction
To list all breakpoints, delete one, or disable one:
(gdb) info breakpoints(gdb) delete 1 # delete breakpoint #1(gdb) disable 1 # disable without deleting
Reading registers
Registers are the CPU's fast, small storage slots. On x86-64, the general-purpose registers are rax, rbx, rcx, rdx, rsi, rdi, rsp, rbp, and r8 through r15. The instruction pointer is rip.
The GDB baby step 1 and GDB baby step 2 challenges are built specifically to practice reading register values. In those challenges, the flag is simply the decimal value stored in eax when a specific function returns.
To read all registers at once:
(gdb) info registers(gdb) info registers eax # just one register
To print the value of a single register in different formats:
(gdb) print $eax # decimal(gdb) print/x $eax # hexadecimal(gdb) print/t $eax # binary(gdb) print/c $eax # as ASCII character
print command (abbreviated p) can evaluate any C expression, not just register names. p $eax + 1, p (int) $rax, and p &variable_name all work.Inspecting memory
The x command (examine) lets you read arbitrary memory. The format is x/NFU addr where N is the count, F is the format, and U is the unit size:
# Format letters:# x = hex, d = decimal, s = string, i = instruction, c = char# Unit letters:# b = byte, h = halfword (2), w = word (4), g = giant (8)(gdb) x/20x $rsp # 20 hex words starting at stack pointer(gdb) x/s 0x402000 # string starting at address(gdb) x/10i $rip # next 10 instructions(gdb) x/4wx $rbp-0x10 # 4 words at rbp minus 16
The disassemble command shows the assembly for a function:
(gdb) disassemble main(gdb) disassemble /m main # interleave with source if available
The backtrace (or bt) command shows the call stack, which tells you how the program arrived at the current point:
(gdb) backtrace
GEF and pwndbg
Vanilla GDB displays minimal context when you step through assembly. Two popular plugins greatly improve the experience by printing registers, the stack, and nearby code automatically after every step:
GEF (GDB Enhanced Features) is the most widely used choice for CTF work. Install it with:
bash -c "$(curl -fsSL https://gef.blah.cat/sh)"
pwndbg is an alternative plugin with a similar philosophy. Both are excellent; GEF tends to be preferred for its built-in heap analysis. After installing either one, simply run gdb ./binary as before and you will see the enhanced context panel automatically.
checksec (shows security mitigations on a binary), vmmap (shows memory layout), and pattern create / pattern search (generates De Bruijn patterns for finding buffer overflow offsets).Common CTF patterns
Most beginner CTF reverse engineering challenges fall into a small set of patterns:
1. Read a register value after a function returns
Used in the GDB baby step series. Set a breakpoint on the instruction after the function call so that the function has fully executed and placed its return value in eax.
gdb ./chal(gdb) break *0x401142 # address of instruction after the call(gdb) run(gdb) print/d $eax # read the return value
2. Bypass a password check
Set a breakpoint at the comparison instruction. Inspect the values being compared with print, or use set to force the comparison to succeed:
(gdb) break *0x4012ab # break at the cmp instruction(gdb) run(gdb) print $eax # actual input(gdb) print $edx # expected value(gdb) set $eax = $edx # force them equal to bypass the check(gdb) continue
3. Find a return address for PIE bypass
Used in PIE TIME. With ASLR enabled, the base address of a PIE binary changes every run. GDB lets you leak it to compute the real address of target functions:
(gdb) break main(gdb) run(gdb) info proc mappings # shows base address of the binary# Add the function offset (from readelf -s ./binary) to get the runtime address
Quick reference
| Command | Description |
|---|---|
| run / r | Start the program |
| run < file | Run with stdin from file |
| continue / c | Resume after a breakpoint |
| next / n | Step over one source line |
| step / s | Step into a function call |
| nexti / ni | Step one assembly instruction (no call entry) |
| stepi / si | Step one instruction (enters calls) |
| break main | Breakpoint at main |
| break *0xADDR | Breakpoint at an address |
| info breakpoints | List all breakpoints |
| delete N | Delete breakpoint N |
| print/x $rax | Print register in hex |
| info registers | Print all registers |
| x/Ns ADDR | Examine N strings at ADDR |
| x/Ni $rip | Disassemble N instructions from rip |
| disassemble func | Show assembly for a function |
| backtrace / bt | Show the call stack |
| set $reg = val | Write a value into a register |
| quit / q | Exit GDB |
Relevant challenges to practice with: GDB baby step 1, GDB baby step 2, GDB Test Drive, PIE TIME.