GDB Reverse Engineering Cheat Sheet

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.

Image:AI Generated 

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 calc and 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 before main. It sets up the runtime environment (stack, registers, libc startup) and then calls main.
  • printf@plt → This is the Procedure Linkage Table (PLT) entry for printf. It’s how dynamically linked functions (like printf from 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 _start sets things up.

What should we learn here

  1. 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.
  2. PLT entries matter. They show which external library functions are used. Seeing printf@plt tells you the program prints something.
  3. Addresses are clues. Each function has a memory address. You’ll use these addresses to set breakpoints (break *0x114d) or disassemble (disassemble 0x1139).
  4. Focus on main and custom functions. For reverse engineering, the most interesting targets are main and any user‑defined functions like add_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).

Example:

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 % (just eax, rbp, etc.).
  • Style: Often considered more readable for beginners because it resembles C assignment.

AT&T Syntax

  • Operand order: source → destination.
Example:

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 main and shows both assembly instructions and raw machine code bytes (/r option).
  • 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 +45 inside main.
  • 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 of add_numbers (sum of 5 and 3 = 8).
  • esi → will be set to eax (the integer argument for printf).
  • 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 into rax.

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.

Reverse engineering isn’t just about memorizing commands — it’s about building intuition. The more you practice with GDB, the more natural it becomes to spot where the compiler adds scaffolding, where your logic lives, and how registers tell the story of execution. Use this cheat sheet as a starting point, but don’t stop here: experiment with different breakpoints, explore disassembly in both Intel and AT&T syntax, and challenge yourself to reconstruct program behavior step by step. And if you discover clever tricks or insights along the way, share them in the comments — let’s turn this into a collaborative learning space for anyone diving into the world of binaries and assembly.