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
- Open Ghidra and create a new project (File -> New Project -> Non-Shared Project).
- Drag and drop the
crackmeexecutable into the Ghidra project window. - Click the green dragon icon (CodeBrowser) to open the binary.
- When prompted to analyze, click Yes and launch Auto Analyze.
Step 2: Explore the Symbol Tree
In the left Symbol Tree panel:
- Expand the
Functionssection. - Locate
mainorcheck_password. - Clicking
mainshows 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
- Strings Search (
Search -> For Strings): Find all plaintext strings stored in.rodataand.datasections. - Rename Variables (Press
N): Rename decompiled variables and functions for readability. - Cross-References (Press
X): Place the cursor on any function or variable and pressXto view all usage locations across the binary. - Patch Instructions: Right-click any instruction in the Listing view to patch bytes (useful for bypassing condition jumps).