Home Reverse Engineering

Static vs Dynamic Binary Analysis: Which Should You Use First?

Reverse Engineering

October 12, 2026

Static vs Dynamic Binary Analysis: Which Should You Use First?

A stripped ELF lands in your queue. Or a packed PE pulled out of a phishing email, imports resolving to two functions and a section table that reads like noise. You want an answer before the end of the day, and you have two ways in: read the file, or run it.

Most analysts have a default. Some go straight to the disassembler and stay there for hours, building a beautiful map of code that never executes on their machine. Others drop everything into a sandbox and treat the report as the final word. Both habits cost time. Static and dynamic analysis answer different questions, and the order you use them matters more than which one you prefer.

Two ways of asking the same question

Static analysis reads bytes without executing them. Disassemblers, decompilers, string extraction, header and entropy checks, cross-references, and the slow work of renaming functions until the code starts to make sense. You see every path the program could take, including the ones that never run on your hardware.

Dynamic analysis runs the thing under observation. Debuggers, sandboxes, API tracers, network captures, filesystem monitors. You see one path — the one the program actually took with the inputs and environment you gave it — with real values attached to every register and buffer.

The framing that keeps you honest: static tells you what is possible, dynamic tells you what happened. Strong analyses move between the two, sometimes several times in a single hour.

Start static when the binary will not cooperate

  • It is packed or obfuscated. Entropy near the top of the scale, imports that resolve to almost nothing, sections named like UPX0. You want to know that before spending a VM on it.
  • It refuses to run. Anti-VM checks, a C2 server that no longer answers, a license file that is missing. Static reads the code regardless of what the code thinks about its environment.
  • You cannot execute it safely or legally. Firmware images, early bootloader stages, kernel drivers on production hardware, code pulled off a device you do not own.
  • You need coverage, not one sample path. Every function that references a particular crypto constant. Every handler for a specific opcode. A disassembler answers in seconds what a debugger would take an afternoon to walk.
  • The binary is enormous. Mapping tens of megabytes of statically linked Go or Rust is tedious, but it still beats stepping through it instruction by instruction.

Static has its own traps. Indirect jumps through computed tables break recursive disassembly. A decompiler's C output is a hypothesis about intent, not source code. Heavy optimization inlines, reorders and merges variables until the names lie to you. Treat every symbol you did not assign yourself as provisional.

Start dynamic when you need ground truth

  • Triage. You do not yet know whether the sample is malicious, and a sandbox tells you about network, process and filesystem behavior in minutes.
  • Unpacking. Let the stub decrypt itself, then dump process memory. Packing is a runtime problem far more often than a static one.
  • Protocol reversing. Run the client against a fake server, capture the traffic, and use the bytes as an anchor for the static work that follows.
  • Anti-analysis you cannot untangle. Custom virtual machines, self-modifying code, encrypted string tables. Watch it run and see what it touches.
  • Crash triage. A stack trace and a register dump point at the interesting code faster than reading symbol-less disassembly.

The trade-offs are real. One path per run. Environment sensitivity. Timing conditions that never reproduce. Code that sleeps for three days or waits for a specific command-line argument. And an API trace tells you that a process was created, never why.

If your static notes are a map and your dynamic notes are a route, you want both before you claim to know the territory.

The tools, and what each is actually good for

Disassemblers

IDA Pro, Ghidra, Binary Ninja, radare2 and rizin. These give you structure: functions, cross-references, call graphs, and a place to record what you learn. Recursive-descent algorithms handle normal compiler output well; linear sweep trips over data embedded in code sections.

Decompilers

Ghidra's decompiler, Hex-Rays, and similar tools. Read the output for intent — loop bounds, comparison logic, structure layout — then confirm the details in assembly. Optimization happily turns a bounds check into something you would never write by hand.

Debuggers and instrumentation

x64dbg and WinDbg on Windows, GDB and lldb elsewhere, plus Frida when you want to hook a function without attaching a debugger. Breakpoints are how a static hypothesis becomes a fact: read the register, watch the buffer, patch the branch, run it again.

Sandboxes and tracers

Automated sandboxes for triage, strace and ltrace on Linux, Procmon on Windows, Wireshark or tcpdump for traffic. Broad coverage, thin detail. Use them to generate questions for the debugger, not to answer them.

A workflow that holds up under a deadline

  1. Triage first, ten minutes. Format, architecture, packing, imports, strings, entropy. Cheap tools, large payoff.
  2. Map statically. Find entry and the real main, follow the imports that matter, rename everything you understand as you go.
  3. Write your hypothesis in one sentence. "Beacons to a hardcoded address and drops a file in AppData." If you cannot write that sentence, you are not ready to run anything.
  4. Confirm dynamically in an isolated VM under a snapshot. No shared folders, no real credentials, no bridged network where you can avoid it.
  5. Take the surprises back to static. The branch that never fired is usually the interesting one.
  6. Document as you work. Renamed functions and comments are the only reason the next run takes ten minutes instead of two hours.

Your first hour, in order

Spend fifteen minutes statically before you execute anything. Get the format, the imports, the strings, and a rough sense of where the interesting code lives. Then form a hypothesis and run the sample in a disposable VM. Come back to the disassembler with the questions the run created.

A concrete loop: a license check. Static analysis locates the comparison and the branch that follows it. A debugger breakpoint on that comparison hands you the correct value sitting in a register. Patch the branch or supply the value, and the behavior is confirmed. Neither technique alone gets there quickly.

Two rules of thumb cover most cases. If the binary is packed or will not run, start static — or at least start static long enough to understand why. If it is unpacked, talks to the network, or you need to know whether it is malicious at all, start dynamic. When you are stuck in both directions, the answer is usually instrumentation: hook the function, log the arguments, and let the program tell you what it is doing.

Photo: Tima Miroshnichenko / Pexels

Related Posts

Embedded Debugging Checklist: Logic Analyzer, SWD, and UART Before You Rewrite Code
Embedded Systems

October 08, 2026

Embedded Debugging Checklist: Logic Analyzer, SWD, and UART Before You Rewrite Code

A hardware-first checklist for embedded targets: verify power, clock, UART, and SWD before assuming your firmware is at fault.

read more
From Source to Silicon: A Beginner's Guide to the Compiler, Assembler, and Linker Pipeline
Architecture

October 03, 2026

From Source to Silicon: A Beginner's Guide to the Compiler, Assembler, and Linker Pipeline

Follow a C file through the preprocessor, compiler, assembler, linker, and loader, and learn the commands that leave every intermediate...

read more
Endianness Explained: Why Your Binary Looks Backwards
Binary

October 02, 2026

Endianness Explained: Why Your Binary Looks Backwards

Endianness decides which byte of a multi-byte value lands first in memory. Here's how little- and big-endian differ, and how to debug...

read more