Lesson 5 of 9 · 24m
Triage: Strings, Imports, and Cross-References
Nobody reads a binary front to back. Find a legible anchor, follow its references down, and expand outward — the excavation loop that finds the interesting code fast.
Nobody reads a binary top to bottom — a megabyte of .text is thousands of functions and most of them are library plumbing. Real triage works like excavation: find something legible on the surface, follow its references downward, and expand outward. The anchors are strings, imports, and constants.
The anchor loop
- Pick a string that reveals intent —
"license invalid","Usage: %s", a URL, a registry key, a magic file header. - Right-click it → References → Show References to get the xref — the code that loads it.
- You're now inside the function that produces that behavior. Read it, name it.
- Who calls that function? Walk up the call chain until you hit a dispatcher or
main.
One good string puts you inside the interesting code path in minutes. "Strings first" is the oldest advice in the discipline and it's still correct.
Imports are capabilities
The import table is a feature list. connect/send/recv means the binary talks on a network. RegCreateKey/RegSetValue means it persists configuration. CreateRemoteThread/WriteProcessMemory means it reaches into other processes — note that on anything you don't fully trust. The counter-move: dlopen/dlsym (or GetProcAddress) means functions get resolved at runtime, so the import table is lying by omission. Hunt the dlsym calls and the strings they resolve.
Recognize algorithms by their constants
Code shapes repeat; constants are fingerprints. These are worth grepping a hex view for:
// constants.js — fingerprints worth searching a binary for
const fingerprints = {
"CRC32 (zlib)": "0xEDB88320, or its 256-entry table",
"MD5": "init state 0x67452301 0xEFCDAB89 0x98BADCFE 0x10325476",
"SHA-256": "init words 0x6A09E667 0xBB67AE85 0x3C6EF372 ...",
"AES": "the S-box starting 0x63 0x7C 0x77 0x7B 0xF2 0x6B 0x6F 0xC5",
"RC4": "a 256-byte permutation plus two nested 0..255 loops",
"Base64": "the literal alphabet A-Za-z0-9+/ sitting in .rodata",
};
for (const [algo, fp] of Object.entries(fingerprints))
console.log(algo.padEnd(14), "->", fp);Find 0xEDB88320 in .rodata and you've located the checksum routine without reading a line of it. The same trick identifies hash functions inside otherwise obfuscated code — constants survive when names and structure don't.
Don't reverse the library
In a statically-linked binary, most functions are libc, OpenSSL, or a runtime you don't need to understand. Ghidra's Function ID analyzer fingerprints known library code and names it — run it and the noise collapses to the handful of functions that are actually the program. Rule of thumb: if it looks like printf's internals, it is.
Field notes
strings -e l(or Ghidra's UTF-16 filter) for Windows binaries — the interesting strings there are usually wide.- Almost no strings, a tiny import table, and a large high-entropy
.text? It's packed. That's a finding, not a dead end — the answer is dynamic analysis, not more static staring. - Xrefs into
.rodatathat are arrays of code pointers are jump tables or C++ vtables. Define them and entireswitchstatements materialize in the listing.