Reverse engineering can feel overwhelming when you’re staring at raw binaries and cryptic assembly. That’s where GDB comes in — but only if you know how to use it effectively. To make the process less intimidating, a one‑page cheat sheet that distills the essentials: launching GDB cleanly, separating compiler “noise” from your own functions, switching between Intel and AT&T syntax, and setting surgical breakpoints to inspect registers and memory. Think of it as your quick‑reference map from binary → assembly → logic. Keep this infographic open while debugging, and you’ll always know where you are in the workflow.
Step 1
Interpreting the command
The command “gdb --nx calc” is a way to start GDB (GNU Debugger) without loading any initialization files, and then launch the program “calc.”
Why it matters
It allows to debug code in a cleaner environment—without any preconfigured settings that could interfere with their process.
Launching gdb
- Command:
gdb --nx calc - What it does: Starts GDB and loads the binary named
calc, - Why use
--nx: Ensures a “clean” debugging environment with no custom aliases, macros, or scripts that could change behavior—important for reproducibility in a reverse engineering class. - What you’re looking for: Confirm GDB can parse the binary, see basic metadata (architecture, PIE/ASLR hints, symbols), and ensure no hidden helpers are affecting output.
What should we notice right after this
- Binary loaded: GDB recognizes
calcand shows its architecture (e.g., x86-64), whether it’s stripped (no symbols), and if it’s dynamically linked. - No auto-run scripts: The prompt won’t be modified; commands behave as documented.
- Starting point: You’re at the GDB prompt, ready to inspect the program before running it—this is the moment to gather intel.
Why this matters in reverse engineering
- Controlled environment: Eliminates external config noise, so you learn “vanilla” GDB behavior.
- Baseline reconnaissance: Before execution, understanding binary layout, symbols, and protections shapes the strategy (breakpoints on main, libc calls, or syscalls).
Step 2
What the command did
- Command:
info functions - Purpose: Lists all functions GDB can identify in the binary.
- Result: You see both system‑generated functions and your program’s own functions.
Interpreting the Output
1. Runtime/system functions
_init/_fini→ Special functions automatically added by the compiler for initialization and cleanup._start→ The true entry point of the program beforemain. It sets up the runtime environment (stack, registers, libc startup) and then callsmain.printf@plt→ This is the Procedure Linkage Table (PLT) entry forprintf. It’s how dynamically linked functions (likeprintffrom libc) are called.__cxa_finalize@plt→ Another PLT entry, used for C++ destructors and cleanup.deregister_tm_clones/register_tm_clones/__do_global_dtors_aux/frame_dummy→ Compiler‑inserted housekeeping functions for things like thread‑local storage and destructors. These aren’t part of your logic, but they show how much “extra” the compiler adds.
2. Your program’s functions
add_numbers(0x1139) → This is a user‑defined function. The name suggests it performs addition.main(0x114d) → The main entry point of your program logic. This is where execution begins after_startsets things up.
What should we learn here
- Not all functions are yours. Compilers and linkers add many helper routines. Reverse engineering means separating “noise” (runtime scaffolding) from the functions you care about.
- PLT entries matter. They show which external library functions are used. Seeing
printf@plttells you the program prints something. - Addresses are clues. Each function has a memory address. You’ll use these addresses to set breakpoints (
break *0x114d) or disassemble (disassemble 0x1139). - Focus on
mainand custom functions. For reverse engineering, the most interesting targets aremainand any user‑defined functions likeadd_numbers.
Step 3
Got it — let’s focus only on the difference between the two disassembly flavors you used in GDB:
Intel vs. AT&T Syntax in GDB
When you run set disassembly-flavor intel or set disassembly-flavor att, you’re telling GDB how to print assembly instructions. The underlying machine code is the same — only the notation changes.
Intel Syntax
- Operand order:
destination ← source(like high‑level languages).
mov eax, [rbp-0x4]
→ Move the value at [rbp-0x4] into eax.
- Immediate values: Written plainly (
mov eax, 5). - Memory references: Use brackets
[ ]. - Registers: Written without
%(justeax,rbp, etc.). - Style: Often considered more readable for beginners because it resembles C assignment.
AT&T Syntax
- Operand order:
source → destination.
movl $0x5, -0x4(%rbp)
→ Move the immediate value 5 into memory at rbp-0x4.
- Immediate values: Prefixed with
$(e.g.,$0x5). - Memory references: Use
()with base registers (e.g.,-0x4(%rbp)). - Registers: Always prefixed with
%(e.g.,%eax,%rbp). - Style: Traditional GNU assembler format; less intuitive at first glance.
Why Should you Care
- Same instructions, different “dialects.” Switching flavors doesn’t change what the CPU executes — only how GDB prints it.
- Intel syntax is easier to read if you’re coming from C/C++ because it looks like
variable = value. - AT&T syntax is common in Unix/Linux tooling (GNU assembler, objdump), so you’ll encounter it in real reverse engineering work.
- Learning both builds flexibility. You’ll be able to read disassembly regardless of the environment or tool.
Step 4
Step 1: disassemble /r main
- Command meaning: Disassembles
mainand shows both assembly instructions and raw machine code bytes (/roption). - Why useful: Students see the actual opcodes (
55,48 89 e5, etc.) alongside mnemonics (push %rbp,mov %rsp,%rbp). - This connects the human‑readable assembly to the raw bytes the CPU executes. It’s how you bridge binary → assembly → source.
Step 2: break *main+45
- Command meaning: Sets a breakpoint at offset
+45insidemain. - Looking at the disassembly:
0x000000000000117a <+45>: lea 0xe83(%rip),%rax # 0x2004- Interpretation: You stopped execution just before the program loads the format string for
printf. - Why here: This is the moment right after the addition result has been computed and stored, but before it’s passed to
printf. - Strategic breakpoints let you pause execution at critical points (before function calls, after calculations) to inspect registers and memory.
Step 3: run
- Command meaning: Starts the program under GDB control.
- Result: Execution halts at your breakpoint (
main+45). - Now you can inspect the state of the program at this exact moment.
What to look at now
At the breakpoint:
- Registers:
eax→ holds the result ofadd_numbers(sum of 5 and 3 = 8).esi→ will be set toeax(the integer argument forprintf).rip→ points to the next instruction (lea …).- Memory:
rbp-0x4→ contains 5.rbp-0x8→ contains 3.rbp-0xc→ contains the result (8).
- Next instruction:
lea 0xe83(%rip),%rax→ loads the address of the format string intorax.
Why this matters
- Breakpoints are surgical tools. You don’t just stop at the start of
main; you stop at the interesting moment (before a library call, after a calculation). - Registers tell the story. By checking
info registers, you can confirm how arguments are passed (edi,esi) and how return values are stored (eax). - Reverse engineering workflow:
- Disassemble → find key instructions.
- Break → pause at those instructions.
- Inspect → registers/memory to reconstruct logic.
