Project 5: Bomb Lab Workflow — Binary Reverse Engineering Playbook

Project 5: Bomb Lab Workflow — Binary Reverse Engineering Playbook

Build a repeatable methodology for defusing binary puzzles and document your solutions with assembly-level precision.

Quick Reference

Attribute Value
Difficulty Advanced
Time Estimate 1-2 weeks
Language C (analysis target), no coding required
Prerequisites Project 4 (Calling Convention Crash Cart) or equivalent
Key Topics Reverse engineering, x86-64 control flow, GDB debugging, disassembly
CS:APP Chapters 3

1. Learning Objectives

By completing this project, you will:

  1. Read x86-64 assembly fluently: Translate disassembly into high-level logic without relying on source code
  2. Extract constraints from machine code: Identify what inputs satisfy comparison and branch conditions
  3. Master GDB for binary analysis: Set breakpoints, examine registers, inspect memory, and trace execution
  4. Recognize common compiler patterns: Identify loops, conditionals, switch statements, and function calls
  5. Apply systematic reverse engineering methodology: Document your approach in a reproducible “defusal dossier”
  6. Handle indirect control flow: Understand jump tables, computed jumps, and lookup operations
  7. Develop defensive reasoning: Verify hypotheses before committing to potentially destructive inputs

2. Deep Theoretical Foundation

2.1 Reverse Engineering Methodology

Reverse engineering is the systematic process of understanding a program’s behavior without access to source code. For bomb labs, this means:

┌─────────────────────────────────────────────────────────────────────────────┐
│                    REVERSE ENGINEERING WORKFLOW                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  1. RECONNAISSANCE          2. STATIC ANALYSIS        3. DYNAMIC ANALYSIS   │
│  ┌─────────────────┐        ┌─────────────────┐       ┌─────────────────┐   │
│  │ • File type     │   ──►  │ • Disassemble   │  ──►  │ • Run in GDB    │   │
│  │ • Strings       │        │ • Map functions │       │ • Set breakpts  │   │
│  │ • Symbols       │        │ • Trace logic   │       │ • Test inputs   │   │
│  │ • Entry point   │        │ • Find patterns │       │ • Verify hypo.  │   │
│  └─────────────────┘        └─────────────────┘       └─────────────────┘   │
│           │                         │                         │              │
│           └─────────────────────────┴─────────────────────────┘              │
│                                     │                                        │
│                                     ▼                                        │
│                          ┌─────────────────────┐                            │
│                          │  4. DOCUMENTATION   │                            │
│                          │  • Input found      │                            │
│                          │  • Reasoning chain  │                            │
│                          │  • Assembly proof   │                            │
│                          └─────────────────────┘                            │
└─────────────────────────────────────────────────────────────────────────────┘

The Golden Rules of Bomb Defusal

  1. Never run without a breakpoint: The bomb will explode if you give wrong input
  2. Prove before you try: Extract constraints mathematically, don’t guess
  3. Read assembly, not intuition: Your assumptions about “what it should do” will mislead you
  4. Document everything: You’ll need to explain your reasoning later
  5. Use GDB’s power: Examine memory, trace calls, modify registers to test hypotheses

2.2 x86-64 Control Flow Patterns

Understanding how C constructs map to assembly is essential. Here are the critical patterns:

2.2.1 Comparisons and Conditional Jumps

The comparison-jump pattern is the foundation of all control flow:

# C: if (x == 5)
cmpq    $5, %rax        # Compare rax with 5
je      .L_equal        # Jump if equal (ZF=1)
jne     .L_not_equal    # Jump if not equal (ZF=0)

Key Comparison Instructions:

Instruction Operation Flags Set
cmp a, b Compute b - a (discard result) CF, ZF, SF, OF
test a, b Compute b & a (discard result) ZF, SF, PF

Common Conditional Jumps:

Signed Unsigned Condition Meaning
je/jz je/jz ZF=1 Equal / Zero
jne/jnz jne/jnz ZF=0 Not equal / Not zero
jg ja   Greater / Above
jge jae   Greater or equal / Above or equal
jl jb   Less / Below
jle jbe   Less or equal / Below or equal
js - SF=1 Negative (sign flag set)
jns - SF=0 Non-negative

Critical Insight: The difference between signed (jg, jl) and unsigned (ja, jb) comparisons is crucial. A value like 0xFFFFFFFF is -1 when signed but 4294967295 when unsigned.

2.2.2 If-Then-Else Structures

// C code
if (x > 10) {
    result = a;
} else {
    result = b;
}
# Assembly pattern
    cmpq    $10, %rdi       # Compare x with 10
    jle     .L_else         # If x <= 10, go to else
    movq    %rsi, %rax      # result = a
    jmp     .L_end
.L_else:
    movq    %rdx, %rax      # result = b
.L_end:

2.2.3 Loops

While Loop Pattern:

while (i < n) {
    // body
    i++;
}
.L_loop_start:
    cmpq    %rsi, %rdi      # Compare i with n
    jge     .L_loop_end     # Exit if i >= n
    # ... loop body ...
    incq    %rdi            # i++
    jmp     .L_loop_start   # Back to start
.L_loop_end:

For Loop Pattern (often optimized):

    movl    $0, %ecx        # i = 0
.L_loop:
    # ... body using %ecx as counter ...
    incl    %ecx            # i++
    cmpl    %esi, %ecx      # Compare i with limit
    jl      .L_loop         # Continue if i < limit

Key Insight: Compilers often restructure loops to have the test at the end (do-while style) for efficiency.

2.3 Switch Statements and Jump Tables

Switch statements with dense case values typically compile to jump tables:

switch (x) {
    case 0: return 'a';
    case 1: return 'b';
    case 2: return 'c';
    default: return '?';
}
# Range check first
    cmpl    $2, %edi            # Is x > 2?
    ja      .L_default          # If so, use default

# Jump table access
    leaq    .L_jump_table(%rip), %rax   # Load table address
    movslq  (%rax,%rdi,4), %rdx         # Get offset for case x
    addq    %rax, %rdx                   # Compute absolute address
    jmpq    *%rdx                        # Indirect jump

.L_jump_table:
    .long   .L_case0 - .L_jump_table     # Offset to case 0
    .long   .L_case1 - .L_jump_table     # Offset to case 1
    .long   .L_case2 - .L_jump_table     # Offset to case 2

.L_case0:
    movl    $97, %eax           # 'a'
    ret
.L_case1:
    movl    $98, %eax           # 'b'
    ret
# ... etc

Recognizing Jump Tables:

  1. Look for a range check (cmp followed by ja or similar)
  2. Look for lea loading an address followed by indexed memory access
  3. The pattern jmpq *%reg (indirect jump through register)
  4. A data section with a sequence of .long or .quad entries

2.4 String Operations and Comparisons

Bombs often compare input strings to expected values:

2.4.1 Character-by-Character Comparison

# Loop comparing strings
.L_loop:
    movzbl  (%rdi), %eax        # Load byte from string1
    movzbl  (%rsi), %edx        # Load byte from string2
    cmpb    %dl, %al            # Compare bytes
    jne     .L_not_equal        # Mismatch found
    testb   %al, %al            # Check for null terminator
    je      .L_equal            # Both strings ended
    incq    %rdi                # Move to next char
    incq    %rsi
    jmp     .L_loop

2.4.2 Using Library Functions

# Using strcmp
    movq    input_string, %rdi   # First argument
    leaq    expected(%rip), %rsi # Second argument
    call    strcmp
    testl   %eax, %eax           # strcmp returns 0 if equal
    jne     explode_bomb

Key Insight: When you see call strcmp or call strings_not_equal, you can use GDB to examine both string arguments before the call executes.

2.4.3 Finding String Constants

# Find all printable strings in the binary
$ strings bomb | head -20

# Or use objdump to find strings in .rodata
$ objdump -s -j .rodata bomb

2.5 Function Calls and Parameter Passing

Understanding the x86-64 calling convention is essential:

┌───────────────────────────────────────────────────────────────────────────┐
│                    x86-64 CALLING CONVENTION (System V ABI)                │
├───────────────────────────────────────────────────────────────────────────┤
│                                                                            │
│  INTEGER/POINTER ARGUMENTS (in order):                                     │
│    1st: %rdi    2nd: %rsi    3rd: %rdx    4th: %rcx    5th: %r8    6th: %r9│
│                                                                            │
│  RETURN VALUE:                                                             │
│    %rax (and %rdx for 128-bit values)                                      │
│                                                                            │
│  CALLER-SAVED (may be clobbered by callee):                                │
│    %rax, %rcx, %rdx, %rsi, %rdi, %r8, %r9, %r10, %r11                      │
│                                                                            │
│  CALLEE-SAVED (must be preserved by callee):                               │
│    %rbx, %rbp, %r12, %r13, %r14, %r15                                      │
│                                                                            │
│  STACK POINTER:                                                            │
│    %rsp (always 16-byte aligned before CALL instruction)                   │
│                                                                            │
└───────────────────────────────────────────────────────────────────────────┘

Example: Analyzing a Function Call

    movl    $6, %edx            # 3rd arg: 6
    movl    $2, %esi            # 2nd arg: 2
    leaq    input(%rip), %rdi   # 1st arg: pointer to input
    call    read_n_numbers
    # Returns count in %eax

This translates to: read_n_numbers(input_ptr, 2, 6)

2.6 Reading Disassembly Systematically

2.6.1 The STRIDE Method

When analyzing a function, use STRIDE:

  1. Signature: What are the inputs (register arguments, stack args)?
  2. Termination: Where does the function return? What conditions lead there?
  3. Register usage: Track which values go into which registers
  4. Important operations: Mark comparisons, branches, and calls
  5. Data access: Note memory reads/writes
  6. Explode paths: Find all paths that lead to explode_bomb

2.6.2 Annotation Example

# PHASE 2 ANALYSIS
# Input: %rdi = pointer to input string
#
phase_2:
    pushq   %rbp                # Save callee-saved register
    pushq   %rbx
    subq    $40, %rsp           # Allocate 40 bytes on stack

    movq    %rsp, %rsi          # 2nd arg: stack buffer (array of 6 ints)
    call    read_six_numbers    # Parse 6 integers from input

    cmpl    $1, (%rsp)          # CONSTRAINT: first number must be 1
    je      .L1                 # If first == 1, continue
    call    explode_bomb        # Otherwise BOOM

.L1:
    leaq    4(%rsp), %rbx       # rbx = &array[1]
    leaq    24(%rsp), %rbp      # rbp = &array[6] (end marker)

.L2:
    movl    -4(%rbx), %eax      # eax = previous element
    addl    %eax, %eax          # eax = 2 * previous (PATTERN: doubling!)
    cmpl    %eax, (%rbx)        # CONSTRAINT: current == 2 * previous
    je      .L3
    call    explode_bomb

.L3:
    addq    $4, %rbx            # Move to next element
    cmpq    %rbp, %rbx          # Check if at end
    jne     .L2                 # Continue loop if not

    # SOLUTION: 1 2 4 8 16 32 (each number is double the previous)

2.7 Using GDB for Binary Analysis

2.7.1 Essential GDB Commands for Bomb Lab

# Starting GDB with the bomb
$ gdb bomb

# Set breakpoint at a phase function
(gdb) break phase_1
(gdb) break explode_bomb

# Run with input file
(gdb) run answers.txt

# Examine disassembly
(gdb) disassemble phase_1
(gdb) disas phase_1              # Short form
(gdb) x/20i phase_1              # 20 instructions from phase_1

# Examine registers
(gdb) info registers
(gdb) info reg rax rdi rsi       # Specific registers
(gdb) print $rax                 # Print value of rax
(gdb) print/x $rax               # Print in hex

# Examine memory
(gdb) x/s 0x402400               # Print string at address
(gdb) x/6d $rsp                  # 6 decimal integers at stack pointer
(gdb) x/10x $rsp                 # 10 hex words at stack pointer
(gdb) x/8c 0x402400              # 8 characters at address

# Step through execution
(gdb) stepi                      # Execute one instruction
(gdb) nexti                      # Execute one instruction (skip calls)
(gdb) continue                   # Continue to next breakpoint

# Examine stack
(gdb) x/20x $rsp                 # View 20 words on stack
(gdb) info frame                 # Current stack frame info

# Set conditional breakpoints
(gdb) break *0x401000 if $rax == 5

# Print with nice format
(gdb) print (char *)$rdi         # Print string argument
(gdb) print/d *(int *)$rsp       # Print integer at stack top

2.7.2 GDB Memory Examination Formats

Format Meaning Example Use
x Hexadecimal x/x $rsp
d Signed decimal x/d $rsp
u Unsigned decimal x/u $rsp
s String x/s 0x402400
c Character x/8c $rdi
i Instructions x/10i $rip

2.7.3 Defusing Strategy in GDB

# 1. Always set breakpoint before the bomb
(gdb) break explode_bomb

# 2. Set breakpoint at the start of the phase
(gdb) break phase_3

# 3. Run until you hit the phase
(gdb) run answers.txt

# 4. When at phase_3, examine what comparison is made
(gdb) disas
# Look for cmp instructions, note addresses

# 5. Set breakpoints at key comparison points
(gdb) break *0x401234

# 6. Step through, examining registers before comparisons
(gdb) stepi
(gdb) print $rax
(gdb) print $rdi

# 7. If you hit explode_bomb breakpoint:
#    - You can inspect why the comparison failed
#    - Modify your input and restart
(gdb) quit

2.8 Static vs Dynamic Analysis

Both approaches are valuable and complementary:

Static Analysis (reading disassembly without running):

  • Safe: Can’t trigger the bomb
  • Complete: See all code paths
  • Challenging: Must trace logic mentally
  • Tools: objdump, readelf, Ghidra, IDA

Dynamic Analysis (running under debugger):

  • Interactive: See actual values
  • Focused: Follow execution path
  • Risky: Could trigger bomb
  • Tools: GDB, ltrace, strace

Best Practice: Do static analysis first to understand the structure, then use dynamic analysis to verify your understanding and extract exact values.

┌─────────────────────────────────────────────────────────────────────────────┐
│                    STATIC + DYNAMIC WORKFLOW                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   STATIC ANALYSIS                      DYNAMIC ANALYSIS                      │
│   ┌─────────────────┐                  ┌─────────────────┐                  │
│   │ Disassemble     │                  │ Set breakpoints │                  │
│   │ phase function  │                  │ at phase start  │                  │
│   └────────┬────────┘                  └────────┬────────┘                  │
│            │                                    │                            │
│            ▼                                    ▼                            │
│   ┌─────────────────┐                  ┌─────────────────┐                  │
│   │ Identify        │                  │ Examine string  │                  │
│   │ comparisons     │──── Verify ───▶  │ / number args   │                  │
│   └────────┬────────┘                  └────────┬────────┘                  │
│            │                                    │                            │
│            ▼                                    ▼                            │
│   ┌─────────────────┐                  ┌─────────────────┐                  │
│   │ Trace logic     │                  │ Step through    │                  │
│   │ to constraints  │◀─── Confirm ────│ with test input │                  │
│   └────────┬────────┘                  └────────┬────────┘                  │
│            │                                    │                            │
│            └────────────────┬───────────────────┘                            │
│                             │                                                │
│                             ▼                                                │
│                  ┌─────────────────────┐                                    │
│                  │ SOLUTION CONFIRMED  │                                    │
│                  └─────────────────────┘                                    │
└─────────────────────────────────────────────────────────────────────────────┘

3. Project Specification

3.1 What You Will Build

A comprehensive “Defusal Dossier” documenting:

  1. Your systematic methodology for approaching each phase
  2. Annotated disassembly with constraint identification
  3. The reasoning chain from assembly to solution
  4. Verified solutions with execution traces
  5. Generalized patterns applicable to other bomb variants

3.2 Deliverable Structure

Your dossier should contain:

bomb-defusal-dossier/
├── methodology.md           # Your systematic approach
├── tools-setup.md           # GDB configuration, scripts
├── phase1/
│   ├── disassembly.txt      # Annotated disassembly
│   ├── analysis.md          # Constraint extraction
│   ├── solution.txt         # The answer
│   └── verification.txt     # GDB trace proving it works
├── phase2/
│   └── ...
├── phase3/
│   └── ...
├── phase4/
│   └── ...
├── phase5/
│   └── ...
├── phase6/
│   └── ...
├── secret_phase/            # If you find it!
│   └── ...
└── patterns.md              # Common patterns you identified

3.3 Example Phase Walkthrough

Here’s a complete example of analyzing a typical Phase 1:

Step 1: Initial Reconnaissance

$ objdump -d bomb | grep -A20 "<phase_1>:"

0000000000400ee0 <phase_1>:
  400ee0:   48 83 ec 08             sub    $0x8,%rsp
  400ee4:   be 00 24 40 00          mov    $0x402400,%esi
  400ee9:   e8 4a 04 00 00          call   401338 <strings_not_equal>
  400eee:   85 c0                   test   %eax,%eax
  400ef0:   74 05                   je     400ef7 <phase_1+0x17>
  400ef2:   e8 43 05 00 00          call   40143a <explode_bomb>
  400ef7:   48 83 c4 08             add    $0x8,%rsp
  400efb:   c3                      ret

Step 2: Static Analysis

PHASE 1 ANALYSIS
================

Function signature:
  - Input: %rdi = pointer to input string (standard calling convention)
  - Calls: strings_not_equal(input, expected)

Key observation:
  - %esi is set to 0x402400 before calling strings_not_equal
  - This is the EXPECTED STRING address!
  - If strings_not_equal returns non-zero, bomb explodes

Constraint:
  - Input string MUST equal the string at address 0x402400

Step 3: Dynamic Analysis (GDB)

$ gdb bomb
(gdb) break phase_1
(gdb) run
# Enter any input when prompted

# Now at phase_1 entry, examine the expected string
(gdb) x/s 0x402400
0x402400: "Border relations with Canada have never been better."

# That's the answer! Let's verify:
(gdb) quit

$ echo "Border relations with Canada have never been better." > answers.txt
$ ./bomb answers.txt
# Phase 1 defused!

Step 4: Documentation

## Phase 1 Solution

### Answer

Border relations with Canada have never been better.


### Reasoning
1. Phase 1 calls `strings_not_equal(input, 0x402400)`
2. If the return value is non-zero (strings differ), bomb explodes
3. The expected string at 0x402400 is: "Border relations with Canada..."
4. Therefore, input must exactly match this string

### Assembly Evidence
```asm
400ee4:   mov    $0x402400,%esi    ; Load expected string address
400ee9:   call   strings_not_equal ; Compare input (%rdi) with expected (%rsi)
400eee:   test   %eax,%eax         ; Check if return == 0
400ef0:   je     400ef7            ; If equal, skip explosion
400ef2:   call   explode_bomb      ; Otherwise, BOOM

Verification

Tested in GDB with breakpoint at phase_1. String comparison succeeds, execution continues past the explode_bomb call.


---

## 4. Solution Architecture

### 4.1 The Defusal Dossier Format

For each phase, document using this template:

```markdown
# Phase N: [Descriptive Title]

## 1. Quick Summary
- **Answer**: [The solution]
- **Type**: [String compare / Numeric sequence / etc.]
- **Difficulty**: [Easy/Medium/Hard]

## 2. Disassembly (Annotated)
```asm
[Full disassembly with your comments]

3. Constraint Analysis

  • [List each constraint derived from the code]
  • [Show the assembly instruction that creates each constraint]

4. Solution Derivation

[Step-by-step reasoning from constraints to answer]

5. GDB Verification

[Commands and output showing successful defusal]

6. Patterns Identified

  • [Any reusable patterns for other bombs] ```

4.2 Methodology Documentation

Create a reusable methodology document:

# Bomb Lab Methodology

## Phase Approach Checklist

### Before Starting
- [ ] Set up GDB with .gdbinit for convenience
- [ ] Create answers.txt file for inputs
- [ ] Set breakpoint at explode_bomb
- [ ] Disassemble all phase_N functions

### For Each Phase
1. [ ] Disassemble the phase function
2. [ ] Identify the input format (string? numbers? how many?)
3. [ ] Find all comparison instructions
4. [ ] Trace paths leading to explode_bomb
5. [ ] Extract mathematical constraints
6. [ ] Solve constraints for valid input
7. [ ] Test in GDB with breakpoints
8. [ ] Document solution and reasoning

### Input Reading Functions
- `read_line()`: Reads one line of text input
- `sscanf()`: Often parses numbers from input
- `read_six_numbers()`: Common helper, reads 6 integers

### Common Patterns
- String comparison: Look for `strings_not_equal` or `strcmp` calls
- Number sequences: Look for loops with arithmetic constraints
- Lookup tables: Look for indexed memory access patterns

4.3 Tools Setup Document

# Tools Setup for Bomb Lab

## GDB Configuration (.gdbinit)

Create a file `~/.gdbinit` or a local `.gdbinit`:

Don’t confirm quit

set confirm off

Better assembly display

set disassembly-flavor intel

OR for AT&T syntax (matches CS:APP):

set disassembly-flavor att

History

set history save on set history size 10000

Pretty printing

set print pretty on


## Useful GDB Scripts

### bomb_init.gdb

Run this at the start of each session

file bomb break explode_bomb break phase_1 break phase_2 break phase_3 break phase_4 break phase_5 break phase_6


### Usage
```bash
$ gdb -x bomb_init.gdb
(gdb) run answers.txt

Alternative Tools

objdump

objdump -d bomb > bomb.asm          # Full disassembly
objdump -t bomb                     # Symbol table
objdump -s -j .rodata bomb          # Read-only data section

strings

strings bomb | less                  # All strings
strings -t x bomb                    # With offsets

---

## 5. Implementation Guide

### 5.1 Development Environment Setup

```bash
# Essential tools (already installed on most systems)
gdb --version           # GNU Debugger
objdump --version       # From binutils
strings --version       # String extractor

# Create working directory
mkdir -p bomb-lab-work
cd bomb-lab-work

# Copy bomb binary here
cp /path/to/bomb .

# Create answer file
touch answers.txt

# Create dossier structure
mkdir -p dossier/{phase1,phase2,phase3,phase4,phase5,phase6,secret_phase}

5.2 Phase-by-Phase Strategy

Phase 1: String Comparison (Typical)

Goal: Find the expected string

Approach:

  1. Disassemble phase_1
  2. Look for mov instruction loading an address before a compare function
  3. Use GDB to examine the string at that address
(gdb) disas phase_1
# Look for: mov $0x4024XX, %esi  or  lea 0xXXXX(%rip), %rsi
# Then:
(gdb) x/s 0x4024XX

Phase 2: Numeric Sequence (Typical)

Goal: Find a sequence of numbers that satisfies constraints

Approach:

  1. Look for read_six_numbers or similar
  2. Identify loop structure
  3. Extract the mathematical relationship

Common patterns:

  • Arithmetic sequence: each number = previous + constant
  • Geometric sequence: each number = previous * constant
  • Fibonacci-like: each number depends on two previous
# Example: doubling sequence (1, 2, 4, 8, 16, 32)
cmpl    $1, (%rsp)          # First must be 1
...
addl    %eax, %eax          # Double the previous
cmpl    %eax, (%rbx)        # Current must equal doubled

Phase 3: Switch Statement (Typical)

Goal: Find an index and corresponding value

Approach:

  1. Look for jump table pattern
  2. Identify the range check (determines valid cases)
  3. Trace each case to find expected second value
# Examine jump table
(gdb) x/8x 0x402470

# For each case, find what value is expected
# Often: sscanf reads two numbers, first selects case, second must match

Phase 4: Recursive Function (Typical)

Goal: Find input that produces expected output through recursion

Approach:

  1. Understand the recursive function
  2. Work backwards from expected output
  3. Use GDB to test specific inputs

Common: Binary tree traversals, recursive calculations

Phase 5: String Manipulation (Typical)

Goal: Find input that transforms into expected string

Approach:

  1. Identify the lookup table
  2. Understand the transformation (often: input char -> index -> lookup)
  3. Reverse engineer: for each output char, find input that produces it
# Example: input[i] & 0xF used as index into lookup table
movzbl  (%rdi,%rax), %ecx     # Get input char
andl    $15, %ecx             # Use low 4 bits as index
movzbl  lookup(,%rcx,1), %ecx # Get transformed char

Phase 6: Linked List Reordering (Typical)

Goal: Find a sequence that correctly orders a linked list

Approach:

  1. Identify the linked list structure in memory
  2. Understand how input maps to node selection
  3. Determine the required ordering

This is typically the hardest phase!

Secret Phase (If Present)

Finding it:

  1. Search for references to “secret” in strings
  2. Look for conditional calls to phase_defused
  3. Often triggered by specific input format in an earlier phase

5.3 Checkpoint Milestones

Milestone Criteria
Setup Complete GDB configured, bomb disassembled, structure created
Phase 1 Solved within 30 minutes using systematic approach
Phase 2 Numeric pattern identified and documented
Phase 3 Jump table analyzed, all cases mapped
Phase 4 Recursive logic understood, solution derived
Phase 5 Transformation table reversed
Phase 6 Linked list ordering solved
Dossier Complete All phases documented with reasoning

6. Testing Strategy

6.1 Verification Method

For each phase, create a verification trace:

# Start GDB with breakpoints
$ gdb bomb
(gdb) break phase_1
(gdb) break phase_2
# etc.
(gdb) break explode_bomb

# Run with your answers
(gdb) run answers.txt

# At each phase, verify:
# 1. Input was read correctly
(gdb) x/s $rdi

# 2. Comparison values are as expected
(gdb) print $eax

# 3. Correct path taken (no hit on explode_bomb)
(gdb) continue

6.2 Testing Checklist

## Phase N Verification Checklist

- [ ] Disassembly matches my understanding
- [ ] All constraints identified and documented
- [ ] Solution satisfies each constraint
- [ ] GDB trace shows correct execution path
- [ ] No false assumptions about signed/unsigned
- [ ] No off-by-one errors in loops
- [ ] Solution works on fresh run (not just in GDB)

6.3 Edge Cases to Consider

Phase Type Common Edge Cases
Strings Whitespace, case sensitivity, null terminator
Numbers Signed vs unsigned, overflow, negative values
Sequences First/last element special cases
Switch Default case, sparse case values
Recursion Base case conditions
Lists Node ordering direction

7. Common Pitfalls

7.1 Critical Mistakes to Avoid

Pitfall Why It’s Dangerous Prevention
Running without breakpoint Bomb explodes on wrong input Always break explode_bomb first
Assuming string content Strings may have hidden chars Use x/s to verify exactly
Confusing signed/unsigned jg vs ja behave differently Check jump instruction carefully
Ignoring calling convention Wrong arg in wrong register Follow %rdi, %rsi, %rdx order
Off-by-one in loops Wrong array index Trace loop boundaries exactly
Modifying binary May invalidate lab submission Work with pristine copy

7.2 Common Misreads

# WRONG: Thinking cmp works like subtraction
cmpq %rax, %rbx      # This computes %rbx - %rax, not %rax - %rbx!
jg   somewhere       # Jumps if %rbx > %rax (signed)

# WRONG: Confusing lea with mov
leaq 8(%rsp), %rax   # %rax = %rsp + 8 (address calculation)
movq 8(%rsp), %rax   # %rax = memory contents at %rsp + 8

# WRONG: Forgetting sign extension
movl (%rsp), %eax    # Loads 32-bit value
# Upper 32 bits of %rax are zeroed (zero extension for 32-bit ops)

movslq (%rsp), %rax  # Sign-extends 32-bit to 64-bit

7.3 Debugging Strategies

# When stuck: examine all interesting memory
(gdb) x/20x $rsp           # Stack contents
(gdb) x/s $rdi             # Input string
(gdb) info registers       # All register values

# When comparison fails: check both sides
(gdb) break *0x401234      # Address of cmp instruction
(gdb) continue
(gdb) print $rax
(gdb) print $rdx
# Now you know exactly what's being compared

# When loop seems wrong: trace iteration by iteration
(gdb) break *0x401240      # Top of loop
(gdb) continue
(gdb) print $rcx           # Loop counter
# Repeat to see pattern

8. Extensions

8.1 Beginner Extensions

  • Create GDB scripts: Automate common sequences of commands
  • Visualize control flow: Draw flowcharts for each phase
  • Time yourself: Track how long each phase takes to improve efficiency

8.2 Intermediate Extensions

  • Solve multiple bomb variants: Apply your methodology to different bomb instances
  • Write a constraint solver: Automate extraction of numeric constraints
  • Create annotated walkthrough video: Explain your process for others

8.3 Advanced Extensions

  • Use Ghidra/IDA: Repeat analysis with professional tools, compare results
  • Static analysis only: Solve without running the bomb at all
  • Create your own bomb: Design phases for others to solve
  • Binary patching: (Educational only) Patch the binary to skip explode_bomb
  • Automation: Write a solver that automatically defuses given any bomb

9. Real-World Connections

9.1 Capture The Flag (CTF) Competitions

Bomb lab skills directly apply to CTF reverse engineering challenges:

  • Crackmes: Binaries that require finding a valid input/key
  • Binary exploitation: Understanding assembly for writing exploits
  • Protocol analysis: Reverse engineering network protocols
  • Malware analysis: Understanding malicious software behavior

Popular CTFs:

  • picoCTF (beginner-friendly)
  • DEF CON CTF
  • Google CTF
  • Pwn2Own

9.2 Malware Analysis

Security researchers use these exact techniques:

  • Unpacking: Analyzing obfuscated malware
  • C2 analysis: Finding command-and-control servers
  • IoC extraction: Identifying indicators of compromise
  • Behavioral analysis: Understanding what malware does

9.3 Software Protection

Understanding reverse engineering helps:

  • Design better copy protection
  • Implement anti-debugging techniques
  • Protect intellectual property in software

9.4 Career Applications

Role How Bomb Lab Skills Apply
Security Researcher Vulnerability discovery, exploit development
Malware Analyst Reverse engineering malicious code
Game Hacker/Modder Understanding and modifying game binaries
Embedded Developer Debugging without source code
Compiler Engineer Understanding code generation
  • radare2: Open source reverse engineering framework
  • Ghidra: NSA’s reverse engineering tool
  • Binary Ninja: Commercial binary analysis platform
  • pwntools: CTF framework for exploitation
  • angr: Binary analysis framework with symbolic execution

10. Resources

10.1 Essential Reading

10.2 GDB Resources

  • GDB Manual: info gdb or online documentation
  • GDB Cheat Sheet: Quick reference for common commands
  • CGDB: Curses-based GDB frontend (easier visualization)
  • pwndbg/GEF: Enhanced GDB for reverse engineering

10.3 Video Resources

  • CMU 15-213 Bomb Lab recitation videos
  • LiveOverflow YouTube series on reverse engineering
  • “Reverse Engineering for Beginners” lectures

10.4 Practice Materials

  • Crackmes.one: Collection of practice binaries
  • Pwnable.kr: Wargames for binary exploitation
  • Microcorruption: Embedded CTF challenges
  • Nightmare: CTF reverse engineering course
  • Previous: P4 (x86-64 Calling Convention Crash Cart) - Foundation for understanding assembly
  • Next: P6 (Attack Lab Workflow) - Applying RE skills to exploitation

11. Self-Assessment Checklist

Before considering this project complete, verify:

Understanding

  • I can read x86-64 assembly without constantly referencing documentation
  • I understand the difference between signed and unsigned comparisons
  • I can identify loops, conditionals, and switch statements in assembly
  • I can trace function calls and understand parameter passing
  • I can recognize jump table patterns
  • I understand how strings are stored and compared

Practical Skills

  • I can use GDB to set breakpoints, examine registers, and inspect memory
  • I can disassemble a function and annotate it meaningfully
  • I can extract constraints from comparison instructions
  • I can verify solutions before committing input
  • I can work systematically rather than guessing

Documentation

  • Each phase has annotated disassembly
  • Each solution includes the reasoning chain
  • I can explain WHY each answer works, not just THAT it works
  • My methodology is documented and reproducible
  • I’ve identified patterns applicable to other bombs

Growth

  • I solved phases faster as I progressed
  • I can approach new binary puzzles with confidence
  • I understand how compiler output relates to source code
  • I’ve developed intuition for common patterns

12. Submission / Completion Criteria

Minimum Viable Completion:

  • All six phases defused
  • Basic documentation of solutions
  • Can explain each answer

Full Completion:

  • Complete defusal dossier with all sections
  • Annotated disassembly for each phase
  • GDB verification traces
  • Methodology document
  • Pattern identification

Excellence (Going Above & Beyond):

  • Secret phase discovered and solved
  • Multiple bomb variants solved
  • Automated analysis scripts
  • Video walkthrough created
  • Contribution to bomb lab resources for others

13. Real World Outcome

When you complete this project, here is exactly what you will see when running your defusal dossier:

$ cd bomb-defusal-dossier
$ ls -la
total 48
drwxr-xr-x  10 user  staff   320 Dec 15 14:23 .
drwxr-xr-x   5 user  staff   160 Dec 15 10:00 ..
-rw-r--r--   1 user  staff  2048 Dec 15 14:23 methodology.md
-rw-r--r--   1 user  staff  1536 Dec 15 12:00 tools-setup.md
drwxr-xr-x   5 user  staff   160 Dec 15 14:00 phase1
drwxr-xr-x   5 user  staff   160 Dec 15 14:10 phase2
drwxr-xr-x   5 user  staff   160 Dec 15 14:15 phase3
drwxr-xr-x   5 user  staff   160 Dec 15 14:18 phase4
drwxr-xr-x   5 user  staff   160 Dec 15 14:20 phase5
drwxr-xr-x   5 user  staff   160 Dec 15 14:23 phase6
-rw-r--r--   1 user  staff  3072 Dec 15 14:23 patterns.md

$ cat phase1/solution.txt
Border relations with Canada have never been better.

$ cat phase2/solution.txt
1 2 4 8 16 32

$ ./bomb answers.txt
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2.  Keep going!
Halfway there!
So you got that one.  Try this one.
Good work!  On to the next...
Congratulations! You've defused the bomb!

$ gdb bomb
GNU gdb (GDB) 13.2
(gdb) break phase_1
Breakpoint 1 at 0x400ee0
(gdb) run answers.txt
Starting program: /home/user/bomb-lab/bomb answers.txt
Welcome to my fiendish little bomb...

Breakpoint 1, 0x0000000000400ee0 in phase_1 ()
(gdb) x/s 0x402400
0x402400: "Border relations with Canada have never been better."
(gdb) disas
Dump of assembler code for function phase_1:
   0x0000000000400ee0 <+0>:     sub    $0x8,%rsp
=> 0x0000000000400ee4 <+4>:     mov    $0x402400,%esi
   0x0000000000400ee9 <+9>:     call   0x401338 <strings_not_equal>
   0x0000000000400eee <+14>:    test   %eax,%eax
   0x0000000000400ef0 <+16>:    je     0x400ef7 <phase_1+23>
   0x0000000000400ef2 <+18>:    call   0x40143a <explode_bomb>
   0x0000000000400ef7 <+23>:    add    $0x8,%rsp
   0x0000000000400efb <+27>:    ret
End of assembler dump.
(gdb) continue
Continuing.
Phase 1 defused. How about the next one?
...

14. The Core Question You’re Answering

“How do I systematically extract program behavior from machine code when I have no source code, and how do I prove my understanding is correct before committing to a potentially destructive action?”

This question sits at the heart of reverse engineering. You’re learning to:

  • Read a language (assembly) that was never meant for humans to read
  • Reconstruct the programmer’s intent from compiled output
  • Build mental models and validate them through controlled experimentation
  • Document your reasoning chain so others can verify and replicate your work

15. Concepts You Must Understand First

Before starting this project, ensure you understand these concepts:

Concept Where to Learn Why It’s Needed
x86-64 Register Set CS:APP 3.4 You must know %rax-%r15, their purposes, and calling conventions
Instruction Encoding CS:APP 3.1-3.3 Understand how instructions are represented in bytes
Control Flow (jumps, calls) CS:APP 3.6 Recognize conditionals and loops in assembly
Stack Operations CS:APP 3.7 Understand push/pop, call/ret, stack frames
Condition Codes CS:APP 3.6.1 Know how ZF, SF, OF affect conditional jumps
Memory Addressing Modes CS:APP 3.4.1 Decode operands like 8(%rbp) or (%rax,%rcx,4)
Calling Convention CS:APP 3.7.2 Know argument registers and return value location

16. Questions to Guide Your Design

As you work through each phase, ask yourself:

  1. What is the input format? Is the bomb expecting a string? Numbers? How many?
  2. Where does the input go? Which register holds the pointer to your input?
  3. What functions are called? Do you see sscanf, strings_not_equal, read_six_numbers?
  4. What are the constraints? For each comparison instruction, what condition must be satisfied?
  5. What paths lead to explosion? Trace every path to explode_bomb - what conditions trigger them?
  6. What is the mathematical relationship? For numeric phases, what formula relates the numbers?
  7. Can I verify without exploding? How can I use GDB to test my hypothesis safely?

17. Thinking Exercise

Before you start coding/analyzing, work through this hand-trace exercise:

Given this assembly snippet from a hypothetical phase:

phase_X:
    sub    $0x18, %rsp
    mov    %rsp, %rsi
    call   read_six_numbers
    cmpl   $0x0, (%rsp)
    jns    .L1
    call   explode_bomb
.L1:
    mov    $0x1, %eax
    jmp    .L3
.L2:
    mov    -0x4(%rsp,%rax,4), %edx
    add    %edx, %edx
    cmp    %edx, (%rsp,%rax,4)
    je     .L4
    call   explode_bomb
.L4:
    add    $0x1, %rax
.L3:
    cmp    $0x6, %rax
    jne    .L2
    add    $0x18, %rsp
    ret

Questions to answer on paper:

  1. How many numbers does this phase read? Where are they stored?
  2. What constraint does the first cmpl/jns enforce about the first number?
  3. What is the loop doing? What does -0x4(%rsp,%rax,4) access?
  4. Write out the mathematical relationship between consecutive numbers.
  5. If the first number is 3, what must the remaining numbers be?

Expected answers (don’t peek until you’ve tried!):

  1. Six numbers, stored on the stack at %rsp (24 bytes = 6 x 4-byte integers)
  2. First number must be >= 0 (non-negative, since jns = jump if not sign/negative)
  3. Loop checks that each number equals twice the previous: arr[i] == 2 * arr[i-1]
  4. Geometric sequence with ratio 2: each number is double the previous
  5. 3, 6, 12, 24, 48, 96

18. The Interview Questions They’ll Ask

After completing this project, you should be able to answer these interview questions:

  1. “Explain the difference between static and dynamic analysis. When would you use each?”
    • Static: Reading code without executing (safe, complete view, but harder to trace)
    • Dynamic: Running under debugger (see actual values, but can trigger side effects)
    • Best practice: Static first to understand structure, dynamic to verify specific values
  2. “How does the x86-64 calling convention work? Where are arguments passed?”
    • First 6 integer args: %rdi, %rsi, %rdx, %rcx, %r8, %r9
    • Return value in %rax
    • Callee-saved: %rbx, %rbp, %r12-r15
  3. “What is the difference between cmp and test instructions?”
    • cmp a, b computes b-a and sets flags (for comparing values)
    • test a, b computes a&b and sets flags (typically used as test %rax, %rax to check zero)
  4. “How would you identify a switch statement in assembly?”
    • Look for bounds check (cmp then ja to default)
    • Look for jump table access (lea of table address, indexed read, jmp *%reg)
    • Data section with sequence of addresses or offsets
  5. “What is a jump table and why do compilers use them?”
    • Array of addresses for switch case targets
    • O(1) dispatch instead of O(n) if-else chain
    • Efficient for dense, integer-based switch statements
  6. “How do you safely debug a program that has destructive behavior?”
    • Set breakpoint at destructive function FIRST
    • Use watchpoints to catch memory corruption
    • Never run without breakpoints; verify hypotheses before testing

19. Hints in Layers

If you get stuck, reveal hints one at a time. Try each level before moving to the next.

Hint 1 - General Approach

For any phase: (1) Disassemble completely, (2) Find all paths to explode_bomb, (3) Work backwards from each comparison to determine what makes it succeed.

Hint 2 - Finding Strings

If a phase compares strings, look for a mov or lea instruction that loads an address into %rsi or %esi before calling a compare function. Use GDB’s x/s address to see the string.

Hint 3 - Numeric Phases

For phases that read numbers:

  • read_six_numbers reads 6 integers onto the stack
  • sscanf with format strings like "%d %d" indicates number count
  • Look for loop patterns with increment and comparison to find relationships

Hint 4 - When Truly Stuck

If you’ve spent more than 2 hours on a single phase:

  1. Write out EVERY instruction with a comment explaining what it does
  2. Draw the stack layout showing where values live
  3. Create a table: for each comparison, list “what’s compared” and “what must be true”
  4. Consider: Is there a lookup table? A mathematical formula? A string encoding?

20. Books That Will Help

Topic Book Chapter/Section
x86-64 Assembly CS:APP 3e Chapter 3 (entire chapter)
Control Flow in Assembly CS:APP 3e Section 3.6
Procedure Calls CS:APP 3e Section 3.7
Data Representations CS:APP 3e Sections 2.1-2.4
Reverse Engineering Fundamentals Practical Binary Analysis Chapters 1-4
GDB Mastery Art of Debugging with GDB Chapters 1-3
x86 Instruction Reference Intel SDM Vol. 2 Instruction Set Reference
Exploit Development Context Hacking: Art of Exploitation Chapter 2

This guide was expanded from CSAPP_3E_DEEP_LEARNING_PROJECTS.md. For the complete learning path, see the project index.