← All posts
June 18, 2026 · Reverse Engineering

Introduction to Binary Reverse Engineering with Ghidra

Discover the basics of reverse engineering using the NSA's open-source tool, Ghidra. Learn how to analyze a C binary and understand its logical flow.

Introduction — What is Reverse Engineering?

Reverse Engineering is the discipline of taking a compiled computer program — a file composed of binary machine instructions — and deconstructing it to understand its internal logic without access to the original source code.

This skillset is critical across several cybersecurity domains:

  • Malware Analysis: Understanding ransomware or trojan behavior without blindly executing it.
  • Vulnerability Research: Identifying memory corruption (Buffer Overflow, Use-After-Free) in proprietary software.
  • Firmware & IoT Security: Auditing native binaries embedded in electronic chips and embedded systems.
  • CTF (Capture the Flag) Competitions: Crackme challenges and binary exploitation exercises.

Ghidra is the comprehensive reverse engineering framework developed and open-sourced by the NSA. It integrates a disassembler, control flow analyzer, and most importantly, one of the world's most capable C decompilers.


1. From Source Code to Binary (Compilation & Reverse)

When a programmer writes C code and compiles it with gcc or clang, the compiler transforms readable text into machine-level binary instructions (x86-64, ARM, or MIPS).

C Source Code ───> [ GCC Compiler ] ───> Machine Instructions (.ELF / .EXE)
                                                     │
                                                     ▼
Pseudo-C Code <─── [ Ghidra Decompiler ] <─── [ Disassembler ]

2. Practical Case: C Program with Password Validation

Here is the source of a sample binary (crackme.c) we will compile and analyze in Ghidra as if we didn't have the source.

❌ Original C Source Code (crackme.c)

#include <stdio.h>
#include <string.h>

int check_password(const char *input) {
    // ❌ VULNERABILITY: Secret key stored in plaintext in the .rodata binary section!
    const char *secret = "BMJ-Ghidra-2026-Pass!";
    return (strcmp(input, secret) == 0);
}

int main(int argc, char **argv) {
    if (argc < 2) {
        printf("Usage: %s <password>\n", argv[0]);
        return 1;
    }

    if (check_password(argv[1])) {
        printf("[+] Access granted! Welcome to the secure system.\n");
    } else {
        printf("[-] Incorrect password.\n");
    }

    return 0;
}
gcc -O2 crackme.c -o crackme

3. Step-by-Step Analysis in Ghidra

Step 1: Project Creation and Import

  1. Open Ghidra and create a new project (File -> New Project -> Non-Shared Project).
  2. Drag and drop the crackme executable into the Ghidra project window.
  3. Click the green dragon icon (CodeBrowser) to open the binary.
  4. When prompted to analyze, click Yes and launch Auto Analyze.

Step 2: Explore the Symbol Tree

In the left Symbol Tree panel:

  • Expand the Functions section.
  • Locate main or check_password.
  • Clicking main shows the x86-64 disassembled ASM in the center listing.

Step 3: Read the Decompiler Output

In the right Decompiler panel, Ghidra reconstructs equivalent C code:

/* Auto-generated pseudo-code by Ghidra Decompiler */
undefined8 main(int param_1, long param_2) {
  int iVar1;
  
  if (param_1 < 2) {
    printf("Usage: %s <password>", *(char **)param_2);
    return 1;
  }

  // 👈 Ghidra immediately reveals the secret string!
  iVar1 = strcmp(*(char **)(param_2 + 8), "BMJ-Ghidra-2026-Pass!");
  
  if (iVar1 == 0) {
    puts("[+] Access granted! Welcome to the secure system.");
  } else {
    puts("[-] Incorrect password.");
  }
  return 0;
}

The secret key "BMJ-Ghidra-2026-Pass!" appears directly in the Decompiler window without any further cracking!


4. Remediation — Preventing Secret Exposure in Binaries

✅ Hardened C Code (Salted SHA-256 Hash Comparison)

#include <stdio.h>
#include <string.h>
#include <openssl/sha.h>

// Never store password strings in plaintext — compare only SHA-256 hashes
int check_password_secure(const char *input) {
    const unsigned char expected_hash[SHA256_DIGEST_LENGTH] = {
        0x5e, 0x88, 0x48, 0x98, /* ... pre-computed SHA-256 bytes ... */
    };

    unsigned char user_hash[SHA256_DIGEST_LENGTH];
    SHA256((const unsigned char*)input, strlen(input), user_hash);

    return (memcmp(user_hash, expected_hash, SHA256_DIGEST_LENGTH) == 0);
}

Key Ghidra Techniques to Know

  1. Strings Search (Search -> For Strings): Find all plaintext strings stored in .rodata and .data sections.
  2. Rename Variables (Press N): Rename decompiled variables and functions for readability.
  3. Cross-References (Press X): Place the cursor on any function or variable and press X to view all usage locations across the binary.
  4. Patch Instructions: Right-click any instruction in the Listing view to patch bytes (useful for bypassing condition jumps).