Ghidra is the open-source disassembler that broke IDA Pro’s monopoly. The decompiler is genuinely competitive, scripting is friendly (Python and Java), and it costs nothing. This tutorial walks through a realistic malware triage workflow.
Setting Up a Safe Lab
Before opening anything malicious:
- VM with no shared folders. VirtualBox or VMware Workstation with networking disabled or routed through a sandbox like INetSim/REMnux.
- Snapshot before importing. Ghidra’s project state changes; snapshots are your time machine.
- Never run the sample on the host. Even read-only analysis is safer in a disposable VM.
infected is the convention) so endpoint AV doesn’t quarantine them mid-analysis.Importing the Sample
File → Import File → select binary. Ghidra autodetects the format. For Windows PE: confirm the language is x86:LE:32:default or x86:LE:64:default. Choose “Analyze” when prompted — defaults are sane.
The Triage Loop
Open the Symbol Tree and look for:
- Imports (
KERNEL32.dll,WS2_32.dll,WININET.dll) — already a behavioral fingerprint. - Suspicious strings (Search → For Strings, min length 6) — URLs, registry paths, base64.
- Entry point function and
WinMain/mainif symbols are present.
A typical commodity loader will show:
URLDownloadToFileA -- payload retrieval
ShellExecuteA / CreateProcessW -- execution
RegSetValueExA -- persistence
VirtualAllocEx + WriteProcessMemory + CreateRemoteThread -- injection
Following Control Flow
Double-click entry. The decompiler view on the right shows pseudo-C. Rename functions and variables as you understand them (L in the listing view; Ctrl+L in decompiler). Naming is the single biggest productivity multiplier in reverse engineering.
// Original
void FUN_00401050(void) {
HANDLE hKey;
RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\...", ...);
RegSetValueExA(hKey, "Updater", 0, REG_SZ, (LPBYTE)PathToBinary, ...);
}
// After renaming
void install_persistence(void) {
HANDLE hKey;
RegCreateKeyExA(HKEY_CURRENT_USER, "Software\\...", ...);
RegSetValueExA(hKey, "Updater", 0, REG_SZ, ...);
}
String Decryption
Most malware obfuscates strings. The pattern is recognizable:
local_30[0] = 0x48 ^ 0x2a;
local_30[1] = 0x54 ^ 0x2a;
local_30[2] = 0x54 ^ 0x2a;
local_30[3] = 0x50 ^ 0x2a;
...
Two strategies:
- Patch + emulate — Ghidra’s emulator runs functions in-isolation.
- Script it — a 20-line Python script in
Window → Script Managerwalks the byte array, applies the XOR, and posts the plaintext as a comment.
# Ghidra Python script
key = 0x2a
addr = currentAddress
data = bytearray()
for i in range(64):
b = getByte(addr.add(i)) & 0xff
data.append(b ^ key)
print(data.decode('latin-1', errors='replace'))
Function Identification
Strip away the noise:
- Library function detection (Tools → Apply Library Signatures) tags known MSVC, MinGW, or Go runtime functions, leaving you with the novel code.
- FidDB signatures cover common cryptographic primitives (AES, RC4, SHA-256). Drop in the Ghidra crypto FID signatures for fast wins.
Comparing Variants
Two samples in the same family share function structure but rename strings and rotate keys. Use Ghidra’s Version Tracking:
Tools → Version Tracking → New Session → choose source and destination programs. Auto-correlators match by symbol name, mnemonic stream, and reference patterns. You will dramatically accelerate analysis of families like Emotet, IcedID, and DanaBot by importing one annotated reference sample and propagating names.
When to Switch to Dynamic
Ghidra is static analysis. If you spend more than an hour fighting heavy obfuscation, switch to dynamic — x64dbg/WinDbg with a memory dump after the unpacker has fired, then re-import the dump into Ghidra for a clean disassembly of the decrypted payload.
References
- Ghidra Documentation
- Practical Malware Analysis (book) — still the canonical lab manual
- MalwareBazaar — public, labeled samples