orbitcurve/firmware-reverse-engineering

ghidra-re

Expert-level Ghidra reverse engineering for firmware binaries with emphasis on stripped binary analysis, automated function discovery, cryptographic routine identification, authentication logic detection, and vulnerability hunting.

Quelltext ansehen
Originales Skill-Dokument

Aus dem Quell-Repository gerendert; Überschriften, Beispiele, Code, Tabellen, Links und Bilder bleiben erhalten.

Ghidra Reverse Engineering for Firmware

Expert-level Ghidra workflows for analyzing stripped firmware binaries, with automation via Python scripting.

Skill Scope

Use this skill for:

  • Deep analysis of individual firmware binaries in Ghidra
  • Stripped binary reverse engineering
  • Automated vulnerability hunting
  • Cryptographic routine identification
  • Authentication logic discovery
  • Custom script development

Prerequisites:

  • Ghidra 12.1.3 with its bundled Jython extension installed (File → Install Extensions → Jython, then restart). Scripts explicitly select # @runtime Jython; they do not run in a standalone Python interpreter.
  • JDK required by the installed Ghidra release (JDK 21 for 12.1.3)
  • Python scripting knowledge
  • Understanding of assembly (ARM/MIPS/x86)
  • Binary already extracted (use firmware-extraction skill)

Integration:

  • After: firmware-extraction, firmware-static-analysis (initial recon)
  • Before/During: firmware-emulation (validate findings dynamically)

The four bundled scripts produce review candidates, not confirmed vulnerabilities. They inspect recovered instructions and resolved references; indirect calls, inlined code and unrecovered data may be missed. They preserve existing names and comments; candidate renames apply only to default symbols. Save the project before running analysis scripts.

Set these paths for the headless examples (replace with absolute local paths):

bash
export GHIDRA_INSTALL_DIR=/path/to/ghidra_12.1.3_PUBLIC
export GHIDRA_SCRIPT_DIR=/path/to/ghidra-re/scripts
mkdir -p /projects

Analysis Workflow

1. Project Setup

bash
# Create project
"$GHIDRA_INSTALL_DIR/ghidraRun"

# Or headless for automation
"$GHIDRA_INSTALL_DIR/support/analyzeHeadless" /projects FirmwareProject -scriptPath "$GHIDRA_SCRIPT_DIR" -import /path/to/binary.elf

# Batch import
for bin in extracted/bin/*; do
    "$GHIDRA_INSTALL_DIR/support/analyzeHeadless" /projects Firmware -scriptPath "$GHIDRA_SCRIPT_DIR" -import "$bin"
done

2. Initial Analysis (Stripped Binary Focus)

Automated approach:

bash
# Run analysis scripts in sequence
"$GHIDRA_INSTALL_DIR/support/analyzeHeadless" /projects Firmware -scriptPath "$GHIDRA_SCRIPT_DIR" -process binary.elf \
  -postScript find_crypto.py \
  -postScript find_auth_functions.py \
  -postScript find_buffer_overflows.py

Manual approach:

  1. Run Auto-Analysis (Analysis → Auto Analyze)
  • Enable: Aggressive Instruction Finder, Stack, Decompiler Parameter ID
  1. Find Functions - Stripped binaries need manual function discovery
  • Entry point: Find _start or main
  • Prologue scanning (see references/stripped-analysis.md)
  • Cross-reference analysis
  • String reference tracing
  1. Initial Renaming
  • Run scripts/auto_rename.py for heuristic-based naming
  • Manually rename critical functions

3. Target-Specific Analysis

Choose analysis path based on goal:

Authentication Analysis → Use scripts/find_auth_functions.py

  • Identifies strcmp, password string refs, multi-return patterns
  • Ranks by heuristic score (not a probability)
  • Adds candidate names to highly ranked default symbols

Crypto Analysis → Use scripts/find_crypto.py

  • Searches for AES S-boxes, MD5/SHA constants
  • Labels crypto tables
  • Finds functions referencing crypto constants

Vulnerability Hunting → Use scripts/find_buffer_overflows.py

  • Detects dangerous function calls (strcpy, sprintf, gets)
  • Flags source/sink co-occurrence within a function; does not trace taint
  • Lists large recovered stack objects in functions with risky API calls

Network Protocol Analysis

  • Find socket/recv/send calls
  • Trace data flow from network input
  • Identify protocol parsing functions

4. Deep Function Analysis

For each interesting function:

python
# @runtime Jython
# Decompile and enhance
func = getFunctionAt(toAddr("0x00401000"))

# Set signature (if known)
sig = "int verify_password(char *user_input, char *stored_hash)"
from ghidra.app.cmd.function import ApplyFunctionSignatureCmd
from ghidra.app.util.parser import FunctionSignatureParser
from ghidra.program.model.symbol import SourceType
from ghidra.program.model.listing import Function, ParameterImpl
from ghidra.program.model.data import *
assert func is not None, "Select a valid function entry"
definition = FunctionSignatureParser(currentProgram.getDataTypeManager(), None).parse(func.getSignature(), sig)
assert ApplyFunctionSignatureCmd(func.getEntryPoint(), definition, SourceType.USER_DEFINED).applyTo(currentProgram)

# Define structures
dtm = currentProgram.getDataTypeManager()
struct = StructureDataType("auth_request", 0)
struct.add(DWordDataType(), "session_id", None)
struct.add(PointerDataType(CharDataType()), "username", None)
struct.add(PointerDataType(CharDataType()), "password", None)
dtm.addDataType(struct, DataTypeConflictHandler.REPLACE_HANDLER)

# Apply to function parameters
param = ParameterImpl("request", PointerDataType(struct), currentProgram)
func.replaceParameters(Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, True, SourceType.USER_DEFINED, param)

5. Vulnerability Analysis

Buffer Overflow Detection:

python
# @runtime Jython
# Manual verification after script identifies candidates
# 1. Check buffer size
# 2. Trace input length
# 3. Verify bounds checking (or lack thereof)
# 4. Confirm exploitability

# Example: strcpy without length check
# Decompiler shows:
#   strcpy(local_buffer, user_input);
# Check local_buffer size in stack frame
# Verify attacker-controlled length exceeds the destination and reaches this call.
# A write beyond the buffer is a vulnerability; code execution needs separate evidence.

Format String Bugs:

python
# @runtime Jython
# Find printf(user_controlled_string)
# Script pattern:
if "printf" in called_functions:
    # Check if format arg is from user input
    # A variable format is not necessarily attacker-controlled; trace its origin.
    pass  # Manual review, not a complete detector

Command Injection:

python
# @runtime Jython
# Find system/popen with user data
# Pattern: system(cmd) where cmd contains user input
# Look for string concatenation before system() call

6. Type and Structure Recovery

Automated structure inference:

python
# @runtime Jython
# See references/stripped-analysis.md for a sketch, not a complete inference engine
# Analyzes memory access patterns:
# - *(ptr + 0) → field at offset 0
# - *(ptr + 4) → field at offset 4
# Define the structure manually after verifying offsets and field sizes

Manual structure definition:

python
# @runtime Jython
# From decompiler output showing member accesses
struct = StructureDataType("device_state", 0)
struct.add(DWordDataType(), "magic", None)          # offset 0
struct.add(ByteDataType(), "enabled", None)         # offset 4
struct.add(ArrayDataType(CharDataType(), 32, 1), "name", None)  # offset 5
# Apply and watch decompiler improve

7. Cross-Referencing

Find callers:

Right-click function → References → Show References to

Find call sites:

python
# @runtime Jython
func = getFunctionAt(currentAddress)
refs = getReferencesTo(func.getEntryPoint())
for ref in refs:
    if ref.getReferenceType().isCall():
        caller = getFunctionContaining(ref.getFromAddress())
        if caller:
            print("Called from: {}".format(caller.getName()))

Trace data flow:

python
# @runtime Jython
# From source to sink
# 1. Find all calls to source (e.g., recv)
# 2. Track where data goes
# 3. Check if reaches sink (e.g., system)
# See scripts/find_buffer_overflows.py for manual data-flow verification

Provided Scripts

All scripts in scripts/ run in Ghidra with the Jython runtime above:

find_crypto.py

Finds candidate constant prefixes and their direct references; absence is not evidence that crypto is absent:

  • First 16 bytes of the AES S-box
  • First four MD5 / SHA256 constants in either byte order
  • Auto-labels crypto tables
  • Finds functions referencing crypto data

Usage:

bash
"$GHIDRA_INSTALL_DIR/support/analyzeHeadless" /projects Project -scriptPath "$GHIDRA_SCRIPT_DIR" -process binary -postScript find_crypto.py

findauthfunctions.py

Discovers authentication logic via heuristics:

  • String analysis (password, login, auth keywords)
  • API calls (strcmp, crypt, verify)
  • Multi-return patterns (success/fail branches)
  • Scores and ranks candidates

Usage:

bash
"$GHIDRA_INSTALL_DIR/support/analyzeHeadless" /projects Project -scriptPath "$GHIDRA_SCRIPT_DIR" -process binary -postScript find_auth_functions.py

findbufferoverflows.py

Detects potential buffer overflow vulnerabilities:

  • Dangerous function calls (strcpy, sprintf, gets)
  • Source/sink co-occurrence (data flow unverified)
  • Stack buffer identification
  • Appends review notes at candidate locations without deleting analyst comments

Usage:

bash
"$GHIDRA_INSTALL_DIR/support/analyzeHeadless" /projects Project -scriptPath "$GHIDRA_SCRIPT_DIR" -process binary -postScript find_buffer_overflows.py

Scripting Patterns

Template Script

python
# @runtime Jython
# my_analysis.py
# Description: Custom analysis for firmware

from ghidra.program.model.symbol import SourceType

currentProgram = getCurrentProgram()
listing = currentProgram.getListing()
fm = currentProgram.getFunctionManager()
mem = currentProgram.getMemory()

# Your analysis logic
for func in fm.getFunctions(True):
    # Process each function
    pass

Common Operations

python
# @runtime Jython
# Navigate
addr = toAddr("0x00400000")
func = getFunctionAt(addr)
func = getFunctionContaining(addr)

# Modify
func.setName("new_name", SourceType.USER_DEFINED)
createFunction(addr, "function_name")
createLabel(addr, "label_name", True)

# Data types
from ghidra.program.model.data import *
DWordDataType()
PointerDataType(CharDataType())
StructureDataType("struct_name", 0)

# Instructions
instr = listing.getInstructionAt(addr)
instr.getMnemonicString()  # "bl", "mov", etc.
instr.getReferencesFrom()

# Decompiler
from ghidra.app.decompiler import DecompInterface
decompiler = DecompInterface()
decompiler.openProgram(currentProgram)
results = decompiler.decompileFunction(func, 30, monitor)
high_func = results.getHighFunction()

Stripped Binary Techniques

Function Discovery

Method 1: Prologue Scanning

python
# @runtime Jython
# ARM: push {r11, lr} = 0xe92d4800
# MIPS: addiu sp,sp,-XX
# x86: push ebp; mov ebp,esp

# Search for patterns in executable memory
# See references/stripped-analysis.md for complete implementation

Method 2: Cross-Reference Analysis

python
# @runtime Jython
# Find all call instructions
# Target addresses likely are function starts
# See references/stripped-analysis.md

Method 3: String References

python
# @runtime Jython
# Functions that reference strings
# Use string content to infer function purpose
# See references/stripped-analysis.md

Automatic Renaming Heuristics

python
# @runtime Jython
# Pattern-based naming
def infer_name(func):
    strings = get_function_strings(func)
    called = get_called_functions(func)
    
    # Authentication
    if any("password" in s.lower() for s in strings):
        if "strcmp" in called:
            return "check_password"
    
    # Network
    if "socket" in called or "recv" in called:
        return "network_handler"
    
    # Crypto
    if "aes" in "".join(strings).lower():
        return "crypto_aes"
    
    return None

Integration with Emulation

Workflow:

  1. Static analysis in Ghidra (this skill)
  2. Identify interesting functions
  3. Set breakpoints in GDB at those addresses
  4. Run in QEMU (firmware-emulation skill)
  5. Observe behavior at breakpoints
  6. Return to Ghidra with insights

Example:

python
# @runtime Jython
# In Ghidra: Find auth function
auth_func = getFunctionAt(toAddr("0x00401234"))

# Note address: 0x00401234. For PIE/shared objects, translate using the actual
# runtime load bias; do not use a static address unchanged.

# In QEMU with GDB:
# gdb-multiarch binary
# (gdb) target remote :1234
# (gdb) break *0x00401234
# (gdb) continue
# ... trigger auth ...
# (gdb) info registers  # See actual values

# Return to Ghidra with understanding of runtime behavior

Best Practices

  1. Start Automated - Run scripts before manual analysis
  2. Name Incrementally - Don't try to name everything at once
  3. Trust Decompiler, Verify Assembly - Decompiler is good but not perfect
  4. Document Assumptions - Use comments liberally
  5. Version Control - Use a shared Ghidra Server project for program versioning; use Git for exported scripts and notes
  6. Cross-Reference Constantly - Understand call graphs
  7. Type Everything - Proper types improve decompilation dramatically
  8. Script Repetitive Tasks - Don't do the same thing 100 times manually

Keyboard Shortcuts

G                Go to address
L                Label/rename
;                EOL comment
Ctrl-;           Pre-comment
D                Disassemble
P                Create function
X                Show references to
Ctrl-Shift-E     Edit function signature
T                Set data type

Troubleshooting

Decompiler fails:

  • Check for unimplemented instructions
  • Simplify function (may be too complex)
  • Try different decompiler options

Auto-analysis misses functions:

  • Use scripts from scripts/ folder
  • Manual prologue search (see references/stripped-analysis.md)

Poor decompilation quality:

  • Set proper function signatures
  • Define structures for complex data types
  • Add type information to variables

References

  • Stripped Analysis: references/stripped-analysis.md - Complete techniques for analyzing stripped binaries, type recovery, function discovery
  • Workflow: references/workflow.md - Expert workflow patterns, scripting examples, integration tips

Quick Command Reference

bash
# Headless analysis with scripts
"$GHIDRA_INSTALL_DIR/support/analyzeHeadless" /projects Firmware -scriptPath "$GHIDRA_SCRIPT_DIR" -import binary.elf \
  -postScript find_crypto.py -postScript find_auth_functions.py

# Import without auto-analysis (manual control)
"$GHIDRA_INSTALL_DIR/support/analyzeHeadless" /projects Firmware -scriptPath "$GHIDRA_SCRIPT_DIR" -import binary.elf -noanalysis

# Export analysis results (first save the JSON example in references/workflow.md
# as export_results.py in GHIDRA_SCRIPT_DIR; it is not a bundled script)
"$GHIDRA_INSTALL_DIR/support/analyzeHeadless" /projects Firmware -scriptPath "$GHIDRA_SCRIPT_DIR" -process binary.elf \
  -postScript export_results.py
python
# @runtime Jython
# Essential Ghidra Python APIs
currentProgram                              # Program object
getFunctionAt(addr)                         # Get function
createFunction(addr, name)                  # Create function
toAddr("0x00400000")                        # String to address
listing.getInstructions(body, True)         # Iterate instructions
getReferencesTo(addr)                       # Get xrefs to
func.setName(name, SourceType.USER_DEFINED) # Rename function

Next Steps After Ghidra Analysis

  1. Document findings - Create analysis report with key functions, vulnerabilities
  2. Test hypotheses - Use firmware-emulation to verify static findings
  3. Develop exploits - If vulnerabilities found, create PoCs
  4. Report - Prepare comprehensive security assessment

This skill assumes expert RE knowledge and focuses on firmware-specific analysis patterns. For general Ghidra basics, consult official documentation.

aus demselben Repository

Weitere Skills

Alle Skills