🔬 Ghidra

Ghidra Quick Reference

Keyboard shortcuts, analysis workflows, cross-references, crypto constant identification, and Ghidra Python scripting for malware analysis.

⌨️ Keyboard Shortcuts

Navigation
Go to addressG
Go to previous locationAlt+←
Go to next locationAlt+→
Go to entry pointCtrl+E
Search memoryS
Search for stringsShift+S
Jump to functionCtrl+F
Symbol tableCtrl+T
Disassembly / Decompiler
Disassemble at cursorD
Define/redefine dataT
Clear code/dataC
Create function at cursorF
Re-decompile functionCtrl+F5
Toggle decompiler/listingCtrl+E
Cursor sync (both panels)Ctrl+Alt+T
Renaming & Retyping
Rename symbol/variableL
Retype variableCtrl+L
Edit data typeCtrl+D
Create structureShift+OpenBrace
Auto-create structureShift+S (decompiler)
Cross-References
Show all references toCtrl+Shift+F
References to addressCtrl+Alt+F
Next referenceCtrl+Alt+G
Back from xrefAlt+←
Show call graphCtrl+Shift+E
Bookmarks & Comments
Add bookmarkCtrl+D
View bookmarksCtrl+Alt+D
Add comment (EOL);
Add pre-comment/
Add plate commentShift+;
Selection & Highlighting
Select all referencesCtrl+A (in xref)
Highlight instructionMiddle click
Clear highlightCtrl+Space
Find next highlightCtrl+N
Find prev highlightCtrl+P

🔬 Malware Analysis Workflow in Ghidra

Step 1
Import & Initial Analysis
File → Import File → select sample. Accept default analysis options. Tick all analysis options, especially: Decompiler Parameter ID, Aggressive Instruction Finder, Shared Return Calls, Reference Analysis. Let auto-analysis complete fully before doing anything else — can take 1-10 minutes for complex binaries.
Step 2
String-Based Triage
Window → Defined Strings (or Search → For Strings). Sort by length. Look for URLs, file paths, registry keys, mutex names, error messages. Double-click any interesting string to jump to its location in the binary — then check what function references it (Ctrl+Shift+F).
Step 3
Import Analysis
Window → Symbol Table → filter by "FUNCTION" type and "EXTERNAL" namespace. This shows all imported functions. Sort by name. Look for suspicious combinations — any injection-related imports, crypto, network, keylogging. Right-click any import → References → Show References to find all call sites.
Step 4
Entry Point Analysis
Navigation → Go To → "entry". The entry function is usually a compiler stub that calls the real main. Look for WinMain or main. In the decompiler: follow calls until you reach real application code (functions that call imported APIs). Most malware interesting logic is 2-5 function calls from entry.
Step 5
Rename & Comment as You Go
Press L to rename any function or variable. Use descriptive names: decrypt_config, send_beacon, check_vm. Add comments with ;. Good naming transforms unreadable decompiler output. Work from known points (string references, imports) outward.
Step 6
Identify Crypto & Encoding
Use the Crypto Constants tab (in this reference) to identify encryption algorithms by their constants. Common in malware: RC4 (key scheduling), XOR loops, AES (S-box values). Once identified, find the decrypt function and trace what data it's decrypting — usually the config, C2 address, or payload.
Step 7
Config Extraction
Most RATs and stealers have a configuration block — hardcoded C2 host, port, mutex, campaign ID. Look for: data immediately following a decryption function, large byte arrays in .data or .rdata, structures accessed by the first function after decryption. Use Ghidra's memory viewer to examine raw bytes.

🔗 Cross-Reference Workflows

Cross-references (xrefs) are the most powerful navigation tool in Ghidra. Mastering xrefs means being able to trace any value from its origin to every place it's used.

Find all call sites for a suspicious import

1. Window → Symbol Table
2. Filter by namespace: EXTERNAL
3. Find VirtualAllocEx (or any target import)
4. Right-click → References → Show References to Symbol
5. Every row = one call site. Double-click to jump there.
6. For each call site: examine what arguments are passed (address, size, protection flags)

Trace a decrypted string back to its source

1. Find an interesting string in Defined Strings
2. Double-click → jumps to string location in memory
3. Press Ctrl+Shift+F → shows all references to this address
4. Follow the reference to the function that uses this string
5. Look at what calls THAT function (xref the function) — reveals execution chain

Find all functions that call a specific function

1. Navigate to the target function (e.g., send_beacon)
2. Right-click function name → References → Show References to Function
3. All callers listed. This shows you the call graph without the graph view.
4. For malware: trace backwards from network functions to find what data is being sent

Function call graph

Window → Function Call Graph (or Ctrl+Shift+E)
Ensure "Show Calls Into" and "Show Calls From" are ticked
Use to understand the overall code structure — identify clusters of related functions
Tip: right-click any node → "Set Function as Root" to explore from that function's perspective

Data type xrefs — finding struct usage

1. Window → Data Type Manager
2. Find or create a struct (e.g., HTTP_REQUEST)
3. Right-click struct → Find Uses
4. All variables and functions using that struct type are listed
5. Apply imported Windows structs (from Built-In types) to decompiler output for readable code

🔐 Crypto Constant Identification

Cryptographic algorithms can be identified by their magic constants. Search for these values in Ghidra (Search → Memory, hex value) to locate crypto code in a malware sample.

RC4 — Most common in RATs / Config encryption
No fixed constant — identified by key scheduling loop structure
Look for: nested loop initialising array of 256 bytes (0-255), then swap operations using a key. Decompiler shows: for(i=0;i<256;i++) S[i]=i; followed by a swap loop. RC4 is symmetric — same function encrypts and decrypts.
AES — Cobalt Strike, AsyncRAT, many modern malware
S-box first row: 0x63 0x7C 0x77 0x7B 0xF2 0x6B 0x6F 0xC5
Hex search: 63 7C 77 7B F2 6B 6F C5
AES S-box is a 256-byte lookup table embedded in the binary. Finding it confirms AES is used. Key sizes: 128-bit (10 rounds), 192-bit (12 rounds), 256-bit (14 rounds). Look for 10/12/14 as loop count near the S-box reference.
MD5 — Hashing, integrity checks
Init constants: 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476
These four 32-bit constants initialise the MD5 state. Search for 01 23 45 67 (little-endian). Often used for keying, fingerprinting, or integrity checks in malware.
SHA-1
Init constants: 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0
Same first four constants as MD5, plus 5th constant 0xC3D2E1F0. SHA-1 used in certificate pinning, HMAC-based C2 authentication.
XOR — Simplest obfuscation, very common
No constants — identified by XOR instruction in loop
Single-byte XOR: for(i=0;i<len;i++) buf[i] ^= key;
Multi-byte key XOR: buf[i] ^= key[i % keylen];
In Ghidra decompiler: XOR operation inside loop over buffer. Single-byte key means 256 possible values — brute-force in Python with for k in range(256): print(bytes([b^k for b in data]))
CRC32 — Used for integrity, anti-tamper, hashing API names
Polynomial: 0xEDB88320 (reflected) or 0x04C11DB7 (normal)
CRC32 is frequently used in malware to hash Windows API function names at runtime (avoids importing API names as strings). If you see CRC32 + GetProcAddress, the malware is resolving APIs dynamically. Look for CRC32 lookup table (256 × 4-byte entries) initialised with 0xEDB88320 polynomial.

Finding constants with CAPA (automation)

# CAPA identifies crypto and capabilities automatically pip install capa capa suspicious.exe # Also identifies: encryption, network, persistence, injection # Maps findings to MITRE ATT&CK and MBC (Malware Behavior Catalog) # Far faster than manual analysis for initial classification

🐍 Ghidra Scripting (Python / Java)

Ghidra supports Python 2.7 (Jython) and Java scripts via Window → Script Manager. Scripts automate repetitive analysis tasks — renaming functions, finding patterns, extracting data.

Running a script

Window → Script Manager → New Script (Python) → write code → Run (play button). Script runs in the context of the currently open program.

Script 1 — List all functions with XOR instructions

# Find functions containing XOR instructions — common in decryption routines # Run in Ghidra Script Manager (Python) from ghidra.program.model.listing import InstructionIterator from ghidra.program.model.lang import OperandType funcs_with_xor = set() listing = currentProgram.getListing() instructions = listing.getInstructions(True) for instr in instructions: if instr.getMnemonicString().upper() in ['XOR', 'XORPS', 'PXOR']: func = getFunctionContaining(instr.getAddress()) if func: funcs_with_xor.add(func.getName() + ' @ ' + str(func.getEntryPoint())) print(f"Functions containing XOR: {len(funcs_with_xor)}") for f in sorted(funcs_with_xor): print(' ', f)

Script 2 — Export all defined strings to file

# Export all strings Ghidra has defined to a text file from ghidra.program.model.data import StringDataType import os output_path = os.path.join(os.path.expanduser('~'), 'ghidra_strings.txt') listing = currentProgram.getListing() data_iter = listing.getDefinedData(True) strings = [] for data in data_iter: if 'String' in data.getDataType().getName(): try: val = data.getValue() if val and len(str(val)) > 4: strings.append(f"{data.getAddress()}: {val}") except: pass with open(output_path, 'w') as f: f.write('\n'.join(strings)) print(f"Exported {len(strings)} strings to {output_path}")

Script 3 — Find and label all calls to VirtualAlloc/WriteProcessMemory

# Find injection-related API calls and add comments target_funcs = ['VirtualAllocEx','WriteProcessMemory','CreateRemoteThread', 'VirtualAlloc','NtAllocateVirtualMemory'] for target in target_funcs: sym = getSymbol(target, currentProgram.getGlobalNamespace()) if not sym: # Try external namespace for s in currentProgram.getSymbolTable().getSymbols(target): sym = s; break if sym: refs = getReferencesTo(sym.getAddress()) for ref in refs: addr = ref.getFromAddress() func = getFunctionContaining(addr) comment = f"[INJECTION] Calls {target}" setEOLComment(addr, comment) print(f"Tagged {target} call at {addr}" + (f" in {func.getName()}" if func else "")) else: print(f"Not found: {target}")

Script 4 — Bulk rename FUN_ functions by their first imported call

# Rename unnamed FUN_XXXXXXXX functions based on the first API they call # Helps quickly understand what each function does fm = currentProgram.getFunctionManager() for func in fm.getFunctions(True): name = func.getName() if not name.startswith('FUN_'): continue # Look for first call to an external function body = func.getBody() listing = currentProgram.getListing() instructions = listing.getInstructions(body, True) for instr in instructions: if instr.getMnemonicString().upper() in ['CALL', 'CALLQ']: refs = instr.getReferencesFrom() for ref in refs: called = getFunctionAt(ref.getToAddress()) if called and called.isExternal(): new_name = f"sub_{called.getName()}" try: func.setName(new_name, ghidra.program.model.symbol.SourceType.USER_DEFINED) print(f"Renamed {name} → {new_name}") except: pass break break # Only use first call