threatfound
Malware Analysis
Malware Analysis · lesson 02

Static triage: reading a binary without running it

Static triage extracts a suspect binary's identity, strings, and capability from its bytes alone, no execution, so you know what you're dealing with before you ever detonate it.

10 min read

Static analysis means examining a suspect file by reading its bytes, never by running it. The goal of static triage is fast, safe orientation: identify the file, pull anything human-readable out of it, and infer its capability from structure, all before you commit to the slower, riskier work of dynamic detonation. Because nothing executes, static triage is the phase you can do with the least risk, but it is also the phase an author works hardest to blind with packing and obfuscation.

Question one: what is it, exactly?

Never trust a file extension. A .pdf can be a Windows executable and a .jpg can be a script. Ask the bytes directly with file, which reads the magic header, then compute a cryptographic hash so the sample has a stable, shareable identity you can cite in every note you write.

# What is this, really? file reads magic bytes, not the name
file suspicious.pdf
# suspicious.pdf: PE32+ executable (GUI) x86-64, for MS Windows

# Give it an identity you can look up and cite
sha256sum suspicious.pdf
# 9f2c...e41a  suspicious.pdf
tip

Work hash-first. Before you disassemble anything, take the SHA-256 and search that hash against threat intel. If the sample is already public and documented you may not need to reverse it at all, you inherit someone else's analysis in seconds. The hash is also how you pin exactly which bytes your findings describe, so a later re-download or a packed variant does not get confused with the original.

A hash lookup on a service like VirusTotal returns detection ratios, first-seen dates, and links to related samples from the hash alone, so the sample's *bytes* never leave your machine (the hash string itself does, which is why even a lookup is a small outbound signal). Uploading the *file* is a different act: it publishes the sample to a shared corpus. Never upload a confidential or client-sensitive artifact, an internal build, a document that may hold customer data, or anything under NDA, because an attacker monitoring VirusTotal for that hash can watch it appear and learn they have been caught.

careful

Handle samples only in an isolated analysis lab, and only samples you own or are explicitly authorised to examine. Even sitting on disk, a real sample is live ordnance: a careless double-click, an auto-preview thumbnailer, or an editor that follows a link can detonate it. Keep it in a snapshotted VM with no path back to your host or production network, and treat every filename as hostile.

Strings: the cheapest signal in the box

strings prints runs of printable characters from the file. Programs leak their intentions here: URLs and IP addresses for command-and-control, registry keys for persistence, mutex names, shell commands, error messages, even library names. By default strings only finds ASCII; Windows binaries are full of 16-bit wide (UTF-16LE) text, so you must scan for both encodings or you will miss half of it.

# ASCII strings, minimum length 8 to cut noise
strings -a -n 8 suspicious.bin

# 16-bit little-endian (wide / UTF-16LE) strings, which Windows uses heavily
# (GNU binutils strings; -e l selects the encoding)
strings -a -e l -n 8 suspicious.bin

# Pro move: FLOSS also decodes stack-built and lightly obfuscated strings
floss suspicious.bin

Read the output with suspicion in both directions. A rich, believable set of strings can still be a decoy. More telling is *absence*: if a full program yields almost no meaningful text, just a few API names and a garbage blob, the real strings are probably compressed or encrypted inside, which is itself a strong signal that the file is packed.

PE structure: reading capability from the imports

A Windows PE file declares which OS functions it calls in its import table, surfaced as the Import Address Table (IAT). Because malware still has to ask the OS to do its dirty work, the imports are a near-honest confession of capability. You do not need to read a single instruction to form a hypothesis, just the list of what it links against. Common tells:

  • VirtualAllocEx / VirtualProtect / WriteProcessMemory / CreateRemoteThread / NtUnmapViewOfSection => allocating executable memory and writing into another process: classic code injection (the last is a tell for process hollowing).
  • InternetOpen / HttpSendRequest (WinInet) or socket / connect / send (ws2_32) => network capability, likely C2 or data exfiltration.
  • RegCreateKeyEx / RegSetValueEx writing to a Run key, or CreateService => persistence.
  • CryptEncrypt / CryptGenKey / CryptAcquireContext => cryptography, which in an unexpected binary can mean ransomware.
  • LoadLibrary + GetProcAddress as almost the *only* meaningful imports => the program resolves its real API calls at runtime to hide them, a hallmark of packed or obfuscated code.

Then look at the sections. Standard names are .text, .data, .rdata, .rsrc. Odd or blank names, a section marked both writable and executable, or a section whose entropy sits near 8.0 bits per byte (statistically random) point to compression or encryption. Combine that with a tiny import table and you are almost certainly looking at a packer; a section pair named UPX0 / UPX1 is the literal signature of UPX. Tools make this quick: Detect It Easy (DIE) flags packers and graphs entropy, CFF Explorer and PEview lay out the headers, sections, and IAT, and Ghidra disassembles when you need to go deeper.

Static triage tells you what a file *is* and what it *can* do, not what it *does*. Its blind spots are exactly the techniques authors use to defeat it: a packer leaves you reading the unpacking stub, not the payload; encrypted strings and config only appear once decrypted in memory; and runtime API resolution empties the IAT so imports lie about capability. When triage hits a wall of high entropy and empty imports, that is not a failure, it is the finding: the sample is protected, and the next move is controlled dynamic analysis, running it in an instrumented sandbox to let it unpack itself so you can dump the real code from memory. Static and dynamic are partners; lead with static because it is safe and fast, then detonate deliberately once you know what you are holding.