Static vs Dynamic Malware Analysis: A Practical Primer
How static and dynamic malware analysis actually work, the tools each uses, the evasion tricks each faces, and how to build a safe lab.
10 min read
Malware analysis gets described as a binary choice between two disciplines, and that framing causes people to pick one and get stuck. Static analysis examines a sample without executing it — parsing file structure, extracting strings, disassembling code. Dynamic analysis executes the sample in a controlled environment and records what it does. In practice the two are a loop, not a fork, and the analyst’s real skill is knowing which one to run next based on what the previous pass produced.
The loop matters because modern malware is built to defeat each approach in isolation. Packers and obfuscators exist to make static analysis return a compressed blob and three imports. Sandbox detection and delayed execution exist to make dynamic analysis return a clean verdict on a malicious file. Attack one wall at a time and you lose. Alternate — unpack in memory dynamically, statically analyze the dumped payload, then re-run with the newly discovered configuration — and most samples fall over within hours.
What follows covers the workflow in both directions, the tooling categories that matter, the evasion you should expect to hit, and how to build a lab that will not leak. The framing is defensive: the output of this work is detection content, indicators with context, and an accurate answer to “what did this thing do to our environment.”
What Each Approach Actually Answers
Before touching tools, be clear about the question. Analysts waste enormous time running the wrong technique for the question in front of them.
Static analysis answers structural and capability questions. What is this file. What was it compiled with. What does it import. What strings, URLs, and configuration data are embedded. Does it share code with a known family. It is fast, repeatable, safe, and it covers code paths that never execute in a single run — including branches that only fire on a domain-joined machine in a specific country.
Dynamic analysis answers behavioral questions. What files did it write. What registry keys did it set. What process did it inject into. Where did it call out to. It gives ground truth about effects and, critically, it defeats obfuscation for free — packed code has to unpack itself in memory to run, and at that moment it is readable.
| Dimension | Static analysis | Dynamic analysis |
|---|---|---|
| Core question | What can it do | What did it do |
| Speed to first result | Seconds to minutes | Minutes to hours |
| Coverage of unexecuted branches | Complete | None |
| Effectiveness against packing | Poor without unpacking | Strong, unpacks itself in memory |
| Risk of infection or callback | Effectively zero | Real, requires containment |
| Primary tooling | Disassemblers, decompilers, PE parsers, YARA | Sandboxes, debuggers, hooking, network capture |
| Typical evasion faced | Packing, obfuscation, encrypted strings | VM and sandbox detection, timing, anti-debug |
| Best output for defenders | File-level detection rules, family attribution | Behavioral detections, host and network IOCs |
The two approaches produce different classes of detection content: static work yields file-based rules that scale across an estate, dynamic work yields behavioral detections that survive recompilation. A detection program needs both.
The Static Workflow
Static triage follows a consistent order because each step narrows what the next step has to consider.
Start with identity and file type. Hash the sample, determine the actual format rather than trusting the extension, and check whether it is already known. Then read structural metadata: for a Windows PE, section names and entropy, compile timestamp, imports, exports, resources, and whether any digital signature validates.
High entropy in a section combined with a tiny import table is the classic packing indicator. A binary importing only LoadLibraryA, GetProcAddress, and VirtualAlloc intends to resolve its real imports at runtime, so the on-disk file will not reveal its capability set.
Strings come next, consistently the highest-value-per-minute step in triage. Modern samples encrypt or stack-build their strings, so plain extraction often fails; obfuscated string recovery tooling that emulates the decoding routines closes much of that gap.
# Static triage, run on an isolated analysis host, never on production.
sha256sum sample.bin
file sample.bin
# Structural view of a PE: sections, entropy, imports, signature status.
pefile sample.bin | head -n 60
python3 -c "import pefile,math;p=pefile.PE('sample.bin');[print(s.Name.decode().strip(chr(0)), round(s.get_entropy(),2)) for s in p.sections]"
# String extraction, both encodings, then obfuscated-string recovery.
strings -a -n 8 sample.bin > ascii.txt
strings -a -e l -n 8 sample.bin > utf16.txt
# Family and capability matching against your rule corpus.
yara -w -r /opt/rules/ sample.bin
capa -v sample.bin
The final static step is code review in a disassembler or decompiler. You are not reading every function. You are locating the entry point, following it to the first meaningful branch, and looking for the handful of routines that matter: the unpacking stub, the configuration decoder, the command dispatch table, the network routine. Everything else can wait until you have a specific question.
The deliverable from a good static pass is a YARA rule that finds the family rather than the sample. Rules built on hardcoded hashes or on the packer’s stub are worthless within a day; rules built on decoded configuration structures, distinctive code sequences, or unique string sets survive rebuilds.
rule Loader_ConfigStruct_Generic
{
meta:
author = "threat-research"
description = "Detects a loader family by its decoded config layout"
reference = "internal case notes, not a public IOC feed"
confidence = "medium"
strings:
$magic = { 4C 44 52 43 ?? ?? 00 00 }
$decode = { 8A 04 0A 32 C3 88 04 0A 42 3B D1 7C ?? }
$ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" ascii
condition:
uint16(0) == 0x5A4D
and filesize < 2MB
and $magic
and $decode
and #ua < 3
}
The Dynamic Workflow
Dynamic analysis has a preparation phase beginners skip and then regret. Before detonation, snapshot the clean VM, start the monitoring stack, and decide the network posture: isolated, simulated internet, or controlled live egress. That is an operational security decision, not a technical one — live callbacks tell the operator their sample is under analysis and can burn the investigation.
Monitoring falls into four categories: process and API monitoring for calls and spawned children; filesystem and registry monitoring for persistence and dropped artifacts; network capture, ideally with TLS interception so you see request structure rather than just destinations; and memory capture taken after the sample has run long enough to unpack.
Automated sandboxes handle the first pass well and should. They produce a behavioral report, a process tree, dropped files, a PCAP, and candidate indicators in minutes. Their limitation is a fixed short window in a recognizable environment, which is precisely what evasive samples are built to survive.
Manual dynamic analysis with a debugger picks up where the sandbox stops: break on the memory allocation and protection change calls that precede unpacking, let the stub complete, then dump the decoded payload from memory and hand it back to static analysis with a reconstructed import table.
{
"sample": {
"sha256": "REDACTED_FOR_PUBLICATION",
"detonation_window_seconds": 600,
"network_mode": "simulated"
},
"observed_behavior": [
{
"stage": "execution",
"detail": "spawned a suspended legitimate system binary and wrote to its memory",
"attack_id": "T1055.012"
},
{
"stage": "persistence",
"detail": "created a scheduled task triggering at user logon",
"attack_id": "T1053.005"
},
{
"stage": "discovery",
"detail": "queried installed security products via WMI before any network activity",
"attack_id": "T1518.001"
},
{
"stage": "c2",
"detail": "HTTPS beacon with fixed 60s interval and 20 percent jitter",
"attack_id": "T1071.001"
}
],
"analyst_notes": "Sample idled for 8 minutes before first network call. Default 3-minute sandbox window returned a benign verdict."
}
That last note is the whole point. The report says benign because the sandbox stopped watching before the malware started working.
How Malware Fights Static Analysis
Packing is the first and most common obstacle. A packer compresses or encrypts the real payload and prepends a stub that reverses that at runtime. Commodity packers unpack trivially with known tooling; custom and commercial protectors do not, and the practical answer there is to unpack dynamically rather than fight the stub statically.
Beyond packing, the common static countermeasures are:
String and API obfuscation. Strings are XORed, RC4-encrypted, or built one character at a time on the stack. API calls are resolved at runtime by hashing function names, so the import table reveals nothing. Recovering these means emulating the decoder, which is exactly what obfuscated-string tooling automates.
Control flow flattening and opaque predicates. Decompiler output becomes a giant switch statement driven by a state variable, with branches that are never taken but which the decompiler cannot prove unreachable. This does not stop analysis; it makes it slow.
Junk code and API hammering. Thousands of meaningless calls inserted to inflate the analysis surface and overwhelm behavioral scoring.
Living-off-the-land delivery. If the malicious logic lives in a script interpreted by a signed system binary, there may be no malicious PE to analyze at all.
How Malware Fights Dynamic Analysis
Evasion is more varied on the dynamic side because there are more environmental signals to check.
Virtualization and sandbox detection. Samples check hypervisor artifacts, driver names, MAC address prefixes, disk size, screen resolution, uptime, CPU core count, and installed software. A machine with two cores, a 40 GB disk, no browser history, and 12 minutes of uptime is not a real user endpoint and the malware knows it.
Analyst tooling detection. Enumerating running processes and window titles for debuggers, packet capture tools, and monitoring utilities. Some samples simply exit; better-written ones behave benignly, which is worse because it produces a false negative rather than a failure.
Timing and trigger conditions. Long sleeps, sleep-skipping detection that measures whether the sleep was patched, waiting for user interaction such as mouse movement or document scrolling, waiting for a reboot, or executing only on a specific weekday. Targeted samples add environmental keying: they decrypt their payload with a key derived from the victim’s domain name or a specific machine attribute, so the payload cannot be recovered anywhere else.
Anti-debugging. Direct API checks, timing checks that detect single-stepping, exception-based tricks, and checks on process memory flags. Anti-debug plugins for common debuggers handle the standard set.
The most dangerous sandbox output is not “malicious.” It is “no suspicious activity observed.” A clean verdict from an automated system is a statement about the sandbox, not about the sample.
The counter is environmental realism plus patience. Give analysis VMs plausible specifications, browsing history, user documents, realistic uptime, and a normal hostname. Extend detonation windows and simulate user activity. Treat any sample that exits immediately with no observable behavior as a manual analysis candidate, not a clean result.
Building a Lab That Does Not Leak
A malware lab has one hard requirement: nothing gets out that you did not intend to get out. Everything else is convenience.
Run the hypervisor on dedicated hardware, not on your working laptop and not on a domain-joined host. Type-2 hypervisors are acceptable for commodity samples; for anything targeted, move to isolated hardware. Disable shared folders, clipboard sharing, and drag-and-drop between guest and host — those convenience features are guest-to-host escape paths.
Network design matters more than most people plan for. Three postures, in increasing risk order: fully isolated with no route out; simulated internet, where an internet-services emulator answers DNS, HTTP, and TLS so the sample believes it has connectivity and reveals its protocol; and controlled live egress through a VPN or dedicated line that is not attributable to your organization. Default to simulated. Escalate to live only with a decision and a reason.
Keep the analysis workstation on a separate segment from the detonation VM and never let the detonation VM route to it. Snapshot before every run and revert after, without exception, because state contamination silently invalidates results. Store samples in encrypted, password-protected archives so endpoint tooling elsewhere does not quarantine your evidence.
Finally, write the handling rules down and follow them: how samples arrive, how they are named, who can access the lab, and what leaves it. Most lab incidents are procedural, not technical — an analyst copies a sample to a share, or mounts a host folder “just this once.”
A Triage Loop That Works Under Pressure
During an incident nobody has three days for a full reverse engineering effort. The responders need answers to four questions fast: is it malicious, what family and what capability, what indicators do we hunt for, and what detection stops the next one.
A workable time-boxed loop: ten minutes of static triage for identity, packing status, imports, and strings, with an automated sandbox detonation running in parallel. Reconcile the two. If the sandbox shows nothing and the static pass shows packing, assume evasion and escalate to manual detonation with a debugger, then dump the unpacked payload, re-run static analysis against it, and extract configuration and network indicators.
From there, produce two artifacts and stop. A YARA rule against the unpacked payload’s stable characteristics, and a behavioral detection derived from the observed sequence — a process ancestry, a persistence mechanism, or a network pattern — because that is what catches the next variant when the hash changes. Protocol reversing and full family documentation are a second pass.
Key Takeaways
- Static and dynamic analysis are a loop, not a choice. Use dynamic execution to defeat packing, then hand the memory-dumped payload back to static analysis for real capability review.
- Static analysis covers code paths that never execute in a given run, which is why it produces file-level detection content that scales across an estate.
- Automated sandbox reports have a fixed short window and a recognizable environment. A clean verdict on a packed sample is a signal to escalate to manual analysis, not to close the case.
- Expect layered evasion: packing and string encryption against static work, VM and debugger detection plus timing triggers against dynamic work. Environmental realism and longer detonation windows defeat much of the latter.
- Lab isolation is a procedural discipline. Dedicated hardware, no shared folders or clipboard, simulated internet by default, snapshot and revert every run, and written handling rules.
- The deliverable of analysis is detection content, not a report. A family-level YARA rule plus one behavioral detection is worth more to responders than a fifty-page walkthrough delivered next week.
Want this checked against your own environment?
We run security posture reviews for engineering teams — cloud config, access control, CI/CD and the gaps between them. Tell us what you are running and we will tell you where we would look first. No charge for the conversation.
Join the discussion
Comments are not enabled on this article yet. Reach the editorial desk directly with corrections or additions.
Contact the editors