Ghidra: the first hour with a new binary
Ghidra is free, open source, and has a good decompiler. Opening a binary for the first time can still be overwhelming: thousands of functions named FUN_00401136, and decompiler output full of param_1 and local_18. This is the routine I follow in the first hour. Practise it on programs you compiled yourself, where you can check your guesses against the source.
1. Project and import
File → New Project (non-shared), then File → Import File and pick the binary. Ghidra detects the format (ELF, PE, Mach-O, raw) and the architecture. Look at the import summary: it repeats what file and readelf -h would tell you.
Double-click the file to open it in the CodeBrowser and accept auto-analysis with the default options. For a small program it takes seconds.
2. Get oriented
Four windows do most of the work:
- Symbol Tree (left): functions, imports, exports, labels
- Listing (centre): the disassembly
- Decompiler (right): C-like pseudocode for the current function
- Defined Strings (Window → Defined Strings): every string Ghidra found
Start with the imports (Symbol Tree → Imports). They tell you what the program can do: file access, networking, process creation, cryptography. Then skim Defined Strings: messages, paths and format strings often point straight at the interesting functions.
3. Find your way in
Three good starting points:
- The entry point →
__libc_start_main→ its first argument ismain(on Linux). Ghidra usually names it for you if symbols exist. - Cross-references from a string: in Defined Strings, double-click a string, then right-click its label → References → Show References to (Ctrl+Shift+F). That lists the code that uses it.
- Cross-references from an import: same thing on, say,
fopen, to find every place that opens a file.
4. Make the decompiler readable
This is where the time goes, and it compounds: every name you add makes every other function easier to read.
- Rename a function or variable: click it, press L. Name what you're sure of; use a prefix like
maybe_for guesses. - Retype a variable: Ctrl+L. Changing
undefined8tochar *or to a struct pointer often transforms a whole function. - Fix a function signature: right-click the function name in the decompiler → Edit Function Signature. Correct argument types propagate to every caller.
- Comment: ; in the Listing adds a comment at the current line. Write down why you believe something, not just what.
- Create a struct from a pointer: right-click the variable → Auto Create Structure, then rename the fields (see "Structs in disassembly").
- Go to an address or name: G.
Work outward from what you understand: leaf functions (ones that call nothing) are usually small and easiest to name, and their names make their callers readable.
5. Don't trust the decompiler blindly
Decompiled code is a reconstruction. Check the Listing when:
- types look wrong (a pointer shown as a
long, odd casts everywhere) - a function seems to take the wrong number of arguments: fix the signature
- there are
switchstatements or jump tables that weren't recovered - the code uses tricks the decompiler simplifies away
Clicking a line in the Decompiler highlights the matching instructions in the Listing, and the other way round. Use that constantly.
6. Save your work
Ghidra saves everything in the project: names, types, comments. File → Save often. For notes that outlive the project, keep a plain text file next to it: the hash of the binary, what you've confirmed, open questions, and the addresses of the important functions.
Practice
Compile one of the small programs from the earlier posts (the sum loop, the switch example, the struct packet functions), strip it with strip, and load it in Ghidra. Rename everything back to the original names using only what you can see. Then compare with the source: where you were wrong teaches you the most.