You have a file. It arrived as an email attachment, got flagged by an EDR alert, or turned up in a triage queue with a name like invoice_2026.pdf.exe. The instinct is to run it and watch what happens. Resist that instinct. Before anything touches a CPU, you can learn an enormous amount by reading the binary — its type, its hashes, the strings baked into it, and the functions it imports from the operating system. This is static triage: analysis without execution. Done well, it turns a scary unknown into a documented decision in minutes, and it tells you whether the sample is even worth the expense of a sandbox.

First, the safety rules — these are non-negotiable

Static triage means you never execute the sample during this phase. But “I’m not running it” is not a license to be careless. Files get double-clicked by accident, archive utilities auto-preview, and one misconfigured tool can detonate a payload you meant only to hash.

For the wider framing, NIST SP 800-83 Rev. 1, Guide to Malware Incident Prevention and Handling for Desktops and Laptops, is the authoritative reference. It structures the whole lifecycle — preparation; detection and analysis; containment, eradication, and recovery; post-incident activity — and static triage lives squarely in that “detection and analysis” phase.

Why triage first, and why it matters in 2026

The problem is volume. The Verizon 2025 Data Breach Investigations Report analyzed 22,052 security incidents and 12,195 confirmed breaches across 139 countries. Ransomware was present in 44% of breaches — up from 32% the prior year — and showed up in a staggering 88% of breaches at small and medium businesses. You cannot detonate every suspicious file in a sandbox; you would drown. Static triage is the fast filter that decides which samples earn the expensive, time-consuming dynamic analysis and which can be classified, IOC’d, and closed on the spot.

Step 1 — Identify the file: extensions lie, magic bytes don’t

The .pdf.exe in our filename is already a tell, but do not trust any extension. What a file is lives in its first few bytes — the magic number embedded in the file structure itself. Learn the common ones:

The Unix file command is your first move. It’s backed by libmagic, which reads a “magic” database of signatures and identifies type independently of the filename. For scripting, python-magic wraps the same library. When you want to eyeball the raw header, xxd dumps the hex so you can see the MZ for yourself.

Step 2 — Hash it: the pivot point for everything else

Compute cryptographic hashes immediately. On Linux, sha256sum and md5sum (GNU coreutils); on Windows, certutil -hashfile <file> SHA256. Hashes do two jobs:

  1. Pivoting. Drop the SHA-256 into VirusTotal or your threat-intel platform. If the sample is known, you may get vendor detections, first-seen dates, related files, and behavioral reports — sometimes closing the case before you extract a single string.
  2. IOCs. The hash is an indicator of compromise you can publish and hunt on across the estate. CISA’s Malware Analysis Reports ship file hashes as IOCs for exactly this reason.

One caveat: MD5 is cryptographically broken (collisions are trivial), so treat it strictly as a legacy lookup key, never as proof of integrity or identity. Prefer SHA-256 as your canonical reference.

// try it live

Compute and inspect a file’s MD5 and SHA-256 entirely in your browser — nothing leaves your machine — then take the hash straight to VirusTotal to pivot.

HASH ANALYZER →

Step 3 — Similarity hashing: catching the variants

Here’s the problem with cryptographic hashes: flip a single byte and the SHA-256 changes completely. Attackers know this and recompile trivially, so a hash-only defense misses every variant. Two techniques answer this.

ssdeep (fuzzy / CTPH hashing)

ssdeep computes context-triggered piecewise hashes (CTPH), also called fuzzy hashes, which can match files that share homologies. It was written by Jesse Kornblum — adapting Andrew Tridgell’s spamsum — and published at DFRWS 2006 in “Identifying Almost Identical Files Using Context Triggered Piecewise Hashing.” The algorithm pairs a rolling hash (which triggers segment boundaries) with piecewise hashing of each segment, then compares two fuzzy hashes to produce a 0–100 similarity score. It is widely used in file-similarity work, including integration with VirusTotal.

imphash (import hash)

imphash is Mandiant’s MD5 of a PE’s ordered import list — built by resolving ordinals, lowercasing DLL and function names, dropping module extensions, and hashing the ordered dll.function list. Published by Mandiant (then FireEye) in January 2014, it works because the linker builds the Import Address Table from the specific order of functions in the source, so the value is relatively unique per build and survives recompilation that doesn’t change imports. Two caveats, straight from Mandiant: files sharing an imphash are not guaranteed to belong to the same threat group (though a common origin can reasonably be assumed), and for packed or very simple samples the value may not be unique enough to be useful.

impfuzzy (the hybrid)

JPCERT/CC’s impfuzzy (published May 2016) runs ssdeep over only the import table rather than the whole file, so adding or removing a few APIs still matches related variants. In JPCERT’s testing across 200 samples (10 each of 20 malware types) it outperformed both imphash and ssdeep at classifying same-type malware — though it stumbles on malware with very few imports and cannot process .NET samples.

Step 4 — Strings: run both ASCII and Unicode

Extracting human-readable strings is the highest-yield step for the effort. Use GNU strings (binutils) or Sysinternals Strings (by Mark Russinovich). The critical detail analysts miss: run both an ASCII pass and a UTF-16LE/Unicode pass. Windows malware is full of wide strings; an ASCII-only sweep will silently drop them. In Sysinternals Strings, -a is ASCII and -u is Unicode, with a default minimum length of 3.

What you’re hunting for:

And a crucial negative result: if strings returns almost nothing readable — just garbage — that emptiness is itself a red flag. It strongly suggests the sample is packed or encrypted, which sends you to Step 6.

Step 5 — PE internals: imports, sections, entropy, timestamps

For Windows PEs, the richest static signal is the Import Address Table (IAT) — the list of OS functions the binary calls. The go-to tool is pefile, Ero Carrera’s Python module (it exposes the DOS header, NT headers, section table, and pe.DIRECTORY_ENTRY_IMPORT). For a fast interactive read, Detect It Easy (DIE) shows type, compiler, packer, sections, and entropy in one shot.

Red-flag API clusters

Individual APIs are innocent; combinations reveal intent. Map them to MITRE ATT&CK:

Section entropy

Shannon entropy measures byte-level randomness from 0 to 8 bits per byte. Normal code and English text sit around 4.5; compressed or encrypted data trends above 7.0–7.5 and approaches ~8.0. The seminal reference is Lyda & Hamrock, “Using Entropy Analysis to Find Encrypted and Packed Malware,” IEEE Security & Privacy, vol. 5 no. 2, pp. 40–45, 2007. The practical rule: a section reading ~7.9–8.0 strongly suggests packing or encryption. No single value proves malware — but legitimate software rarely carries sections above 7.5. DIE reports this with -e/--entropy.

Timestamp and resources

Also read the compile timestamp (TimeDateStamp), but treat it with suspicion — it’s trivially forged and often is. And check embedded resources; malware frequently hides a second-stage payload or config blob in the .rsrc section.

Step 6 — Packer detection and what to do next

When strings come up empty and a section’s entropy is near 8.0, you’re probably looking at a packer. The canonical example is UPX (the Ultimate Packer for eXecutables) — free, open source (GPL), UCL-based (an open implementation compatible with the NRV algorithm), cross-platform, and it typically cuts program size by 50–70%. Because UPX is legitimate software, packing is a triage signal, not proof of malice. But it’s a strong one.

The classic UPX tell is non-standard section names UPX0/UPX1/UPX2 alongside high entropy. Detect it with Detect It Easy, whose signature-plus-heuristic engine identifies packers, compilers, linkers, and protectors even when obfuscated. Then:

Step 7 — Family identification with YARA and CAPA

YARA — “the pattern matching swiss knife for malware researchers,” created by Victor Alvarez of VirusTotal and open-sourced in 2013 — lets you describe a malware family’s DNA as a set of strings, hex patterns, and regexes combined with Boolean logic. Its successor engine is YARA-X (a Rust rewrite), and YARA powers VirusTotal’s Retrohunt and Livehunt. A good triage habit: when you confirm a family, write (or pull) a YARA rule so the next sample matches automatically.

Pair it with CAPA from Mandiant’s FLARE team. Where YARA matches byte patterns, CAPA describes code-level capabilities — “can inject into another process,” “communicates over HTTP” — and maps its findings to MITRE ATT&CK and the Malware Behavior Catalog (MBC). It runs on PE, ELF, .NET, shellcode, and even sandbox reports. Together they answer two different questions: YARA says “what is this,” CAPA says “what can it do.”

A real 2026 anchor: BRICKSTORM

To see the pipeline end-to-end, consider a current threat. BRICKSTORM is a Go-based backdoor used by China-nexus actor UNC5221, disclosed by Mandiant in a campaign it has responded to since March 2025. It targets Linux and BSD network appliances (which rarely run EDR), offers SOCKS relay and WebSocket C2, and enabled an eye-watering average dwell time of 393 days. CISA, NSA, and the Canadian Centre for Cyber Security published a Malware Analysis Report with IOCs and YARA rules; a December 19, 2025 update added detection for additional samples, including a .NET Native AOT-compiled variant and newly identified Rust-based variants. Mandiant also released an open-source brickstorm-scanner.

The triage flow maps cleanly: identify the file type (ELF, not PE — so you pivot to strings and the CISA/Mandiant scanner rather than PE imports), hash it and pivot on VT, extract strings to surface C2 and behavioral artifacts, then confirm the family with the published YARA rules. That’s a full static picture of a live, named 2026 threat — no detonation required.

The worked exhibit

Here’s what a first pass over a Windows sample looks like at the terminal, end to end:

$ file suspicious_invoice.pdf.exe
suspicious_invoice.pdf.exe: PE32 executable (GUI) Intel 80386, for MS Windows

$ xxd suspicious_invoice.pdf.exe | head -n 1
00000000: 4d5a 9000 0300 0000 0400 0000 ffff 0000  MZ..............

$ sha256sum suspicious_invoice.pdf.exe
9f2a...c41e  suspicious_invoice.pdf.exe        # <- pivot on VirusTotal

$ ssdeep suspicious_invoice.pdf.exe
3072:ab12Cd...:xY9z   # compare -> 92/100 match vs a known Family.X sample

$ python3 -c "import pefile; print(pefile.PE('suspicious_invoice.pdf.exe').get_imphash())"
f34d8c2b7a1e9f0d6c5b4a3e2d1c0b9a         # <- pivot this imphash on VirusTotal

$ strings -el suspicious_invoice.pdf.exe | grep -Ei 'http|Global\\|Software\\'
http://185.220.101.44/gate.php
Global\M3lt_Mutex_01
Software\Microsoft\Windows\CurrentVersion\Run
CreateRemoteThread
VirtualAllocEx

$ diec -e suspicious_invoice.pdf.exe
PE32
  Packer:   UPX(4.x)[NRV,best]
  Sections: UPX0 (entropy 7.94), UPX1 (entropy 7.91), .rsrc (entropy 4.20)
  --> high entropy + UPX0/UPX1 = packed. Next: `upx -d`, then re-run strings/imports.

Read that block top to bottom and you already have a hypothesis: a UPX-packed Windows executable, masquerading as a PDF, with a hardcoded C2 over plaintext HTTP, a persistence Run key, a mutex, and process-injection APIs. That’s more than enough to write a first YARA rule:

rule Suspicious_Injector_UPX
{
    meta:
        author      = "triage-analyst"
        description = "UPX-packed PE with process-injection API strings"
        reference   = "MITRE ATT&CK T1055"
    strings:
        $mz   = { 4D 5A }
        $upx0 = "UPX0"
        $api1 = "CreateRemoteThread" ascii wide
        $api2 = "WriteProcessMemory"  ascii wide
        $api3 = "VirtualAllocEx"      ascii wide
    condition:
        $mz at 0 and $upx0 and 2 of ($api*)
}

Your static triage toolbox

Static triage is a gate, not a verdict

Everything above produces a hypothesis and a set of IOCs — it does not produce a final ruling. The point of triage is the decision it enables: document the hashes, strings, imports, and any family match; publish the IOCs; and then decide, deliberately, whether this sample is worth detonating in the sandbox. A packed binary with injection APIs and a live C2 earns dynamic analysis. A recompiled variant that your imphash cluster and YARA rule already identify may not. Either way, you spend your expensive analysis on the samples that deserve it — and you do all of it in an isolated VM, from a password-protected archive, never executing until you mean to.

Key Takeaways

References

  1. Verizon 2025 Data Breach Investigations Report
  2. Verizon 2025 DBIR SMB Snapshot (88% ransomware figure)
  3. CISA — Malware Next-Generation Analysis
  4. DOS MZ executable (magic bytes, Mark Zbikowski) — Wikipedia
  5. Mach-O file type identification (0xFEEDFACF, 0xCAFEBABE / Java collision)
  6. Mandiant/FireEye — Tracking Malware with Import Hashing (imphash)
  7. Kornblum — Identifying Almost Identical Files Using Context Triggered Piecewise Hashing (DFRWS 2006)
  8. JPCERT/CC — Classifying Malware using Import API and Fuzzy Hashing (impfuzzy)
  9. MITRE ATT&CK T1055 — Process Injection (ROKRAT / Notepad.exe example)
  10. MITRE ATT&CK T1027.007 — Dynamic API Resolution
  11. Lyda & Hamrock — Using Entropy Analysis to Find Encrypted and Packed Malware, IEEE S&P 5(2):40-45, 2007
  12. ranDecepter (arXiv) — ~93% of ransomware uses standard crypto libraries
  13. UPX — Wikipedia (UCL/NRV, GPL, 50-70% compression, cross-platform)
  14. YARA — VirusTotal (Victor Alvarez, pattern matching swiss knife)
  15. capa — Mandiant FLARE (capabilities, ATT&CK/MBC, PE/ELF/.NET/shellcode)
  16. Mandiant — Another BRICKSTORM: Stealthy Backdoor (UNC5221, 393-day dwell, since March 2025)
  17. CISA AR25-338A — BRICKSTORM Backdoor MAR (Dec 19, 2025 update: .NET AOT and Rust variants)
  18. Mandiant brickstorm-scanner (GitHub)
« Anatomy of a Phishing URL:… Password Cracking in 2026: Entropy,… »