Malware analysis sits between IR and reverse engineering: you take an unknown binary and turn it into answers - what it does, how it persists, who it talks to, and how to detect it everywhere else. These questions probe your triage process, safe-handling discipline, and how you get useful output under time pressure.
Static analysis examines the file without running it: the PE header, imports, strings, entropy, embedded resources, and disassembly. It is safe and fast and gives you a hypothesis, but packing and obfuscation can hide the real behavior.
Dynamic analysis runs the sample in an instrumented sandbox and watches what it actually does: files written, registry keys, processes spawned, and network connections. It cuts through obfuscation but only shows the paths that execute in your environment, and the malware may detect the sandbox and stay dormant.
They are complementary. I usually start static to form a hypothesis and stay safe, then go dynamic to confirm behavior, then back to static (disassembly) for anything the dynamic run didn't reveal.
1. Identify & hash: record SHA-256, file type, and size; check the hash against threat intel (VirusTotal, internal) to see if it is already known.
2. Triage statically: strings, PE imports (Are there hints like WinInet, CreateRemoteThread, crypto APIs?), entropy (high = packed), and any embedded config.
3. Detonate dynamically in an isolated sandbox and capture behavior: process tree, file/registry changes, and network traffic.
4. Go deeper as needed: unpack and disassemble to answer specific questions - capability, persistence, C2 protocol.
5. Produce output: IOCs, a behavioral summary, detection ideas (YARA/Sigma), and a verdict. The goal is actionable intelligence, not a novel.
Strings can reveal URLs and domains (C2), file paths, registry keys, commands, error messages, mutex names, and sometimes cleartext config - free intelligence, though attackers increasingly encrypt these.
The PE header tells you the compile timestamp (roughly when it was built), the sections and their entropy (a tiny .text and a huge high-entropy section screams packed), and the import table, which hints at capability: networking APIs, process injection (VirtualAllocEx, WriteProcessMemory, CreateRemoteThread), crypto, or anti-debug calls. A suspiciously tiny import table often means the real imports are resolved at runtime - itself a sign of packing.
A packer compresses or encrypts the real payload and wraps it in a small stub that unpacks it into memory at runtime. It defeats naive static analysis - your strings and disassembly show the stub, not the malware. High entropy and a tiny import table are the tells.
To deal with it: recognize the packer (some are known, like UPX, which unpacks trivially). For custom packers, I let it unpack itself by running it under a debugger and dumping the process memory once the real code is decrypted (find the tail jump / OEP, then dump and fix the imports). For triage speed, dynamic analysis often gets me the behavior without full unpacking - I only invest in unpacking when I need the disassembly to answer a specific question.
Isolation is everything. I use a dedicated analysis VM with snapshots so I can revert to clean after every detonation, on a network that is isolated or simulated (host-only, or a fake-internet service like INetSim so the sample gets responses without reaching real infrastructure).
Discipline around handling: treat every sample as live, never run it on my host or a domain-joined machine, disable shared folders and clipboard, and store samples password-protected (zipped with infected) so they don't get scanned or accidentally executed. For anything that might detect virtualization, I consider a bare-metal or cloud detonation environment. And I avoid tipping off the attacker - detonating C2 from an attributable IP can burn an investigation.
YARA is a pattern-matching language for identifying and classifying files by textual or binary signatures. A rule has a strings section (byte sequences, text, regex) and a condition that combines them.
I use it two ways. Hunting/retro: after analyzing a sample, I write a YARA rule on durable artifacts - a unique decryption routine, an unusual string, a distinctive code sequence - then sweep the enterprise and intel feeds for related samples. Classification: family rules tag new samples automatically. The craft is picking artifacts specific enough to avoid false positives but durable enough to catch variants - keying on the packer stub or a common library is a classic mistake.
Multiple angles. Static: strings and the config often hold domains, IPs, URIs, and user-agents - though these are frequently encoded or encrypted, so I look for the decryption routine and either replicate it or let the sample decrypt in memory and read the plaintext there.
Dynamic: detonate with full packet capture against a simulated internet and read the beacon - the domain, port, URI pattern, request cadence, and any encoded data in the query. Config extraction: for known families, a config extractor pulls the C2 block directly. I capture not just the atomic IOCs but the pattern (URI structure, beacon interval, JA3) so detection survives infrastructure rotation.
Anti-VM / sandbox evasion: checking for VM artifacts, low core counts, small disks, or lack of user activity, then staying dormant. Defeat it by hardening the sandbox to look real (patched artifacts, realistic uptime, simulated user activity) or detonating on bare metal.
Anti-debugging: IsDebuggerPresent, timing checks, and structured-exception tricks. Defeat it with debugger plugins that hide the debugger and by patching the checks.
Stalling / triggers: long sleeps or environment triggers (specific domain, date, mutex). Defeat by patching sleeps, forcing execution paths, and providing the expected environment. The mindset is: the evasion itself is a behavior I can observe and then neutralize - and each check I bypass tells me something about the author's sophistication.
Structure it as situation, approach, resolution. Pick a real or lab example - a heavily obfuscated or anti-analysis sample where the obvious paths stalled.
Emphasize method over heroics: how you broke the problem down (isolate the one question that mattered, pick the technique most likely to answer it), tried a different angle when one failed (static gave nothing, so you pivoted to memory dumping mid-execution), and knew when to timebox and ask for help or leverage intel rather than burning a day on pride. Close with what you learned and added to your toolkit so the next similar sample is faster.
Depth is driven by the question being asked, not by curiosity. During a live incident the need is usually "is this malicious, what does it do, and what are the IOCs so we can contain and hunt?" - that is triage depth, answerable in tens of minutes, and I stop there.
Full reverse engineering (every function, the exact crypto, capability mapping) is warranted for a novel family, a targeted threat, or building durable detection and intel - not for a commodity loader that intel already documents. I explicitly state the objective and stop when it is met, then note what deeper analysis would add if someone wants it later. Knowing when to stop is a senior skill; over-analyzing commodity malware is a common time sink.
I optimize for what IR can act on now, not a complete analysis. In 30 minutes: hash and intel lookup (if it is known, I relay the existing report immediately), a fast static triage, and a quick sandbox detonation for behavior.
The deliverables: a clear verdict (malicious / suspicious / benign), IOCs IR can block and hunt with right now (C2 domains/IPs, file paths, hashes, mutexes, persistence keys), the behavior that matters for containment (does it spread, does it steal creds, does it beacon), and quick detection ideas. I explicitly flag confidence and what is still unknown - "confirmed C2 to X, persistence via Y; capability analysis pending" - so IR isn't waiting on a perfect report to contain.
"Nothing happened" is itself a clue - it usually means evasion, a missing trigger, or a missing dependency. I work the possibilities: sandbox evasion (harden the VM, add user activity, extend runtime past a stall/sleep), execution requirements (does it need specific arguments, a companion file, a particular OS or architecture, or a C2 that must answer before it proceeds?), and triggers (a specific date, domain, mutex, or a decryption key it expects from its loader).
I confirm with static analysis: the imports and disassembly tell me whether it is checking for a VM, expecting parameters, or gated on a callback. Then I recreate the missing condition - supply the argument, simulate the C2 response, patch the anti-VM check - and detonate again. Falling back to pure static analysis always gets me the capability even if I can't make it run.