Project 5: Ghidra Reverse Engineering

Expanded deep-dive guide for Project 5 from the Binary Analysis sprint.

Quick Reference

Attribute Value
Difficulty Level 2: Intermediate
Time Estimate 2-3 weeks
Main Programming Language Java (for scripts), Ghidra
Alternative Programming Languages Python (Ghidrathon)
Coolness Level Level 4: Hardcore Tech Flex
Business Potential 2. The “Micro-SaaS / Pro Tool”
Knowledge Area Static Analysis / Decompilation
Software or Tool Ghidra (NSA), sample binaries
Main Book “Ghidra Software Reverse Engineering for Beginners”

1. Learning Objectives

  1. Build a working implementation with reproducible outputs.
  2. Justify key design choices with binary-analysis principles.
  3. Produce an evidence-backed report of findings and limitations.
  4. Document hardening or next-step improvements.

2. All Theory Needed (Per-Concept Breakdown)

This project depends on concepts from the main sprint primer: loader semantics, control/data-flow recovery, runtime observation, and mitigation-aware vulnerability reasoning. Before implementation, restate the project’s core assumptions in your own words and define how you will validate them.

3. Project Specification

3.1 What You Will Build

Complete reverse engineering of several binaries of increasing complexity, including writing Ghidra scripts for automation.

3.2 Functional Requirements

  1. Accept the target binary/input and validate format assumptions.
  2. Produce analyzable outputs (console report and/or artifacts).
  3. Handle malformed inputs safely with explicit errors.

3.3 Non-Functional Requirements

  • Reproducibility: same input should produce equivalent findings.
  • Safety: unknown samples run only in isolated lab contexts.
  • Clarity: separate facts, hypotheses, and inferred conclusions.

3.4 Expanded Project Brief

  • File: P05-ghidra-reverse-engineering.md

  • Main Programming Language: Java (for scripts), Ghidra
  • Alternative Programming Languages: Python (Ghidrathon)
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Static Analysis / Decompilation
  • Software or Tool: Ghidra (NSA), sample binaries
  • Main Book: “Ghidra Software Reverse Engineering for Beginners”

What you’ll build: Complete reverse engineering of several binaries of increasing complexity, including writing Ghidra scripts for automation.

Why it teaches binary analysis: Ghidra is the industry-standard free tool. Its decompiler produces C-like code from assembly, dramatically speeding up analysis.

Core challenges you’ll face:

  • Navigating Ghidra’s UI → maps to efficient workflow
  • Using the decompiler → maps to understanding control flow
  • Cross-references → maps to finding function usage
  • Writing scripts → maps to automating analysis

Resources for key challenges:

Key Concepts:

  • Code Browser: Ghidra documentation
  • Decompiler Window: “Ghidra RE for Beginners” Ch. 4
  • Ghidra Scripting: Ghidra API documentation

Difficulty: Intermediate Time estimate: 2-3 weeks Prerequisites: Projects 1-4, solid assembly knowledge

Real World Outcome

Deliverables:

  • Analysis output or tooling scripts
  • Report with control/data flow notes

Validation checklist:

  • Parses sample binaries correctly
  • Findings are reproducible in debugger
  • No unsafe execution outside lab ``` Analyzing a CTF crackme in Ghidra:
  1. Load binary → Auto-analysis runs
  2. Find main() → Entry point analysis
  3. Decompile main() → See C-like code:

    int main(int argc, char **argv) { char input[32]; printf(“Enter password: “); scanf(“%s”, input); if (check_password(input)) { printf(“Correct!\n”); } else { printf(“Wrong!\n”); } return 0; }

  4. Analyze check_password() → Find algorithm
  5. Write keygen or patch binary ```

Hints in Layers

Ghidra workflow:

  1. Create project → Import binary
  2. Let auto-analysis complete
  3. Navigate with ‘G’ (goto address) or symbol tree
  4. Use ‘L’ to rename functions/variables
  5. Use ‘;’ to add comments
  6. Use ‘X’ to find cross-references

Scripting example (Ghidra Python):

# Find all calls to dangerous functions
dangerous = ["gets", "strcpy", "sprintf"]
for func_name in dangerous:
    func = getFunction(func_name)
    if func:
        refs = getReferencesTo(func.getEntryPoint())
        for ref in refs:
            print(f"Call to {func_name} at {ref.getFromAddress()}")

Learning milestones:

  1. Navigate efficiently → Find functions, strings, imports
  2. Understand decompiler output → Read C-like code
  3. Rename and annotate → Make code understandable
  4. Write scripts → Automate repetitive analysis

The Core Question You Are Answering

How do you transform an opaque binary blob into understandable, analyzable code without access to source, and how can you automate this process at scale?

This project teaches you to bridge the gap between raw machine code and high-level logic using industry-standard tooling. You’ll learn not just to read binaries, but to make them readable for others.

Concepts You Must Understand First

1. Intermediate Representations (IR)

An IR is a translation layer between machine code and high-level code. Ghidra uses “P-Code” as its IR, which normalizes different CPU architectures into a common format.

Guiding Questions:

  • Why can’t decompilers directly translate assembly to C without an intermediate step?
  • How does P-Code handle architecture-specific quirks (endianness, calling conventions)?
  • What information is lost when converting from assembly to IR?

Book Reference: “Practical Binary Analysis” Ch. 6 - Binary Analysis Fundamentals

2. Control Flow Graphs (CFG)

CFGs represent program execution paths as nodes (basic blocks) and edges (jumps/branches). Ghidra automatically builds CFGs to understand program structure.

Guiding Questions:

  • What defines a basic block boundary (entry/exit points)?
  • How do conditional branches create multiple paths in a CFG?
  • Why are CFGs essential for decompilation quality?

Book Reference: “Practical Binary Analysis” Ch. 7 - Simple Code Injection

3. Data Flow Analysis

Understanding how data moves through a program—from parameters through operations to return values—is key to renaming variables meaningfully.

Guiding Questions:

  • How do you track a value from function entry to its use in a comparison?
  • What’s the difference between reaching definitions and use-def chains?
  • How does stack frame analysis help identify local variables vs parameters?

Book Reference: “Computer Systems: A Programmer’s Perspective” Ch. 3.7 - Procedures

4. Type Inference

Decompilers guess variable types from their usage (pointer arithmetic, function calls, comparisons). Understanding this helps you correct wrong guesses.

Guiding Questions:

  • How does Ghidra infer that mov rax, [rbx] suggests rbx is a pointer?
  • What clues indicate a variable is a string vs a byte array?
  • When do you need to manually fix type annotations?

Book Reference: “Practical Binary Analysis” Ch. 6.3 - Disassembly and Binary Analysis Fundamentals

5. Symbol Resolution

Binaries often lack symbol names. Learning to identify functions by their behavior (string references, API calls) is critical.

Guiding Questions:

  • How do you identify main() in a stripped binary?
  • What patterns indicate a function is a constructor vs destructor?
  • How do import tables help identify library functions?

Book Reference: “Practical Binary Analysis” Ch. 5 - Basic Binary Analysis in Linux

6. Cross-References (Xrefs)

Xrefs show where data/code is used. They’re essential for understanding program flow and finding all uses of a particular function or string.

Guiding Questions:

  • What’s the difference between “calls to” and “called by” in xref analysis?
  • How do you use xrefs to find all error-handling code paths?
  • Why do string references often lead directly to interesting functionality?

Book Reference: “Ghidra Software Reverse Engineering for Beginners” Ch. 3

7. Calling Conventions

Different platforms pass arguments differently (stack vs registers, order, cleanup responsibility). Ghidra auto-detects these but you need to verify.

Guiding Questions:

  • What’s the difference between __cdecl, __stdcall, and __fastcall?
  • How does x64’s register-based calling differ from x86’s stack-based?
  • When does Ghidra get calling conventions wrong?

Book Reference: “Low-Level Programming” Ch. 9 - Calling Conventions

8. Ghidra Scripting API

Automating analysis with scripts lets you handle repetitive tasks (renaming, searching, reporting) efficiently.

Guiding Questions:

  • What’s the difference between Ghidra’s Java API and Python (Ghidrathon)?
  • How do you iterate over all functions in a program?
  • When should you write a script vs use built-in features?

Book Reference: Official Ghidra API Documentation (included with Ghidra)

Questions to Guide Your Design

  1. How do you efficiently navigate a 100,000-line decompiled binary to find the password validation logic? Consider string searches, API call tracking, and symbolic execution.

  2. When Ghidra’s decompiler produces confusing code (nested ternaries, weird casts), what strategies help you simplify it? Think about variable renaming, type fixing, and understanding the original source idiom.

  3. How would you write a script to find all uses of dangerous functions (strcpy, gets, sprintf) across multiple binaries? Consider iteration, filtering, and reporting.

  4. What workflow lets you collaborate with teammates on reversing a large binary? Think about Ghidra project sharing, version control, and annotation standards.

  5. How do you handle obfuscated or packed binaries that confuse Ghidra’s auto-analysis? Consider manual disassembly, unpacking, and custom analysis passes.

  6. What’s your process for documenting your reverse engineering findings so others can understand them? Think about commenting standards, structure diagrams, and pseudocode.

  7. How would you diff two versions of a binary to find what changed in a security patch? Consider Ghidra’s version tracking and binary diffing capabilities.

  8. When analyzing malware, what sandbox/isolation setup ensures your Ghidra analysis doesn’t trigger malicious behavior? Think about static vs dynamic analysis boundaries.

Thinking Exercise

Before writing any Ghidra scripts, complete this exercise:

  1. Manual CFG Construction: Take a simple crackme binary (20-30 functions). Draw the control flow graph of the password validation function by hand:
    • Identify basic blocks (sequences ending in jumps/branches)
    • Draw edges for conditional and unconditional jumps
    • Label edges with conditions (e.g., “password correct”, “length check failed”)
    • Mark which paths lead to success vs failure
  2. Type Inference Practice: Look at this decompiled snippet:
    undefined8 FUN_00401234(long param_1) {
        long lVar1;
        lVar1 = param_1 + 0x10;
        *lVar1 = 0x41414141;
        return 0;
    }
    

    Without running it, infer:

    • Is param_1 a struct pointer? Array? Something else?
    • What type should lVar1 be (not just long)?
    • What’s really happening in *lVar1 = 0x41414141?
    • Rewrite it with meaningful names and types.
  3. Cross-Reference Tracing: In a binary with debug symbols removed:
    • Find the string “Invalid password” in Ghidra
    • Use xrefs to find which function displays it
    • Trace back to find what calls that function
    • Continue until you find the entry point (main)
    • Document the call chain: main() -> login_handler() -> validate_password() -> error_message()
  4. API Identification: Open a Windows PE binary in Ghidra:
    • List all imported DLLs and functions (use Imports window)
    • Categorize APIs: networking (ws2_32.dll), crypto (advapi32.dll), file I/O (kernel32.dll)
    • For each interesting import, find all calls to it
    • Infer program capabilities (e.g., “Connects to network, encrypts files”)

The Interview Questions They’ll Ask

  1. “Explain how Ghidra’s decompiler works at a high level. What are the major stages?” Expected: Disassembly → CFG construction → P-Code conversion → SSA form → Type inference → C code generation

  2. “You’re reversing a binary and Ghidra shows a function with 50 parameters. What went wrong and how do you fix it?” Expected: Ghidra misidentified the calling convention or function boundary. Check for stack frame setup, use “Edit Function Signature”, verify with debugging.

  3. “How would you use Ghidra to find all SQL injection vulnerabilities in a closed-source web server binary?” Expected: Search for SQL keywords in strings, xref to find query-building code, trace backwards to find unsanitized user input paths.

  4. “What’s the difference between Ghidra’s P-Code and LLVM IR? Why does Ghidra use P-Code?” Expected: P-Code is designed for decompilation (reverse direction), LLVM IR for compilation (forward). P-Code is simpler and architecture-neutral.

  5. “Walk me through your process for analyzing a stripped binary with no symbols.” Expected: Find entry point → identify main (heuristics: called once, calls many) → name key functions → follow interesting strings → build call graph.

  6. “You need to analyze 100 similar malware samples. How do you automate commonality extraction with Ghidra?” Expected: Write headless Ghidra script to batch-process samples, extract features (strings, APIs, crypto constants), generate similarity matrix.

  7. “Ghidra’s decompiler shows code that couldn’t possibly compile. Give three reasons why.” Expected: Hand-written assembly with no C equivalent, compiler optimizations (like overlapping variables), incorrect type inference.

  8. “How do you identify crypto algorithms (AES, SHA256) in decompiled code?” Expected: Look for characteristic constants (AES S-box: 0x63, 0x7c…), specific bit operations, large lookup tables, entropy analysis.

  9. “What are the limitations of static analysis with Ghidra vs dynamic analysis with a debugger?” Expected: Static can’t handle runtime unpacking/decryption, indirect calls, or input-dependent behavior. Dynamic requires execution environment.

  10. “Describe a real scenario where writing a Ghidra script saved you significant time.” Expected: Personal example, e.g., “Found all format string bugs in a 500KB binary by automating xref analysis of printf-family functions.”

Books That Will Help

Topic Book Chapter/Section
Ghidra Basics & UI “Ghidra Software Reverse Engineering for Beginners” Ch. 1-4 (Installation, UI, Basic Analysis)
Decompilation Theory “Practical Binary Analysis” Ch. 6 (Binary Analysis Fundamentals)
Control Flow Graphs “Practical Binary Analysis” Ch. 7 (Simple Code Injection)
x86/x64 Assembly “Low-Level Programming” Ch. 3-4 (Assembly Language, Syntax)
Calling Conventions “Computer Systems: A Programmer’s Perspective” Ch. 3.7 (Procedures)
Stack Frames “Computer Systems: A Programmer’s Perspective” Ch. 3.7.5 (Stack Frames)
Symbol Tables & Linking “Computer Systems: A Programmer’s Perspective” Ch. 7 (Linking)
Reverse Engineering Methodology “Reversing: Secrets of Reverse Engineering” Ch. 1-3 (Foundations)
Static Analysis Techniques “Practical Malware Analysis” Ch. 1, 5 (Basic Static Analysis)
Ghidra Scripting (Java) Official Ghidra Docs GhidraAPI.html (included)
Ghidra Scripting (Python) Ghidrathon GitHub Docs README and examples
Binary File Formats (ELF) “Practical Binary Analysis” Ch. 2 (ELF Format)
Binary File Formats (PE) “Practical Binary Analysis” Ch. 2 (PE Format)
Data Flow Analysis “Compilers: Principles, Techniques, and Tools” (Dragon Book) Ch. 9 (Machine-Independent Optimizations)
Type Inference “Practical Binary Analysis” Ch. 6.3 (Disassembly)
Advanced Reversing “The IDA Pro Book” Ch. 5-8 (applies to Ghidra too)

Common Pitfalls and Debugging

Problem 1: “Your interpretation does not match runtime behavior”

  • Why: Static analysis can hide runtime-resolved addresses, lazy binding, and input-dependent branches.
  • Fix: Reproduce the path with debugger or tracer, then compare static assumptions against live register/memory state.
  • Quick test: Run the same sample through both your static workflow and a debugger transcript, and confirm control-flow decisions align.

Problem 2: “Tool output is inconsistent across machines”

  • Why: ASLR, tool version drift, and different binary build flags (PIE, RELRO, symbols stripped) change observed addresses and metadata.
  • Fix: Pin tool versions, capture checksec/metadata, and document environment assumptions in your report.
  • Quick test: Re-run analysis in a container or VM with pinned tools and compare hashes of generated outputs.

Problem 3: “Analysis accidentally executes unsafe code”

  • Why: Dynamic workflows run binaries in host context without sufficient isolation.
  • Fix: Use disposable snapshots, no-network execution, and non-privileged users for all unknown samples.
  • Quick test: Validate isolation controls first (network disabled, snapshot active, unprivileged user), then execute sample.

Definition of Done

  • Core functionality works on reference inputs
  • Edge cases are tested and documented
  • Results are reproducible (same binary, same tools, same report output)
  • Analysis notes clearly separate observations, assumptions, and conclusions
  • Lab safety controls were applied for any dynamic execution

4. Solution Architecture

Input Artifact -> Parse/Decode -> Analysis Engine -> Validation Layer -> Report

Design each stage so intermediate artifacts are inspectable (JSON/text/notes), which makes debugging and peer review much easier.

5. Implementation Phases

Phase 1: Foundation

  • Define input assumptions and format checks.
  • Produce a minimal golden output on one known sample.

Phase 2: Core Functionality

  • Implement full analysis pass for normal cases.
  • Add validation against an external ground-truth tool.

Phase 3: Hard Cases and Reporting

  • Add malformed/edge-case handling.
  • Finalize report template and reproducibility notes.

6. Testing Strategy

  • Unit-level checks for parser/decoder helpers.
  • Integration checks against known binaries/challenges.
  • Regression tests for previously failing cases.

7. Extensions & Challenges

  • Add automation for batch analysis and comparative reports.
  • Add confidence scoring for each major finding.
  • Add export formats suitable for CI/security pipelines.

8. Production Reflection

Map your project output to a production analogue: what reliability, observability, and security controls would be required to run this continuously in an engineering organization?