Deep dives into software, hardware, and the ideas reshaping how we build things.

Author: Gavin Bishop (page 3 of 11)

Static Analysis Tools That Actually Work: A Field Guide for the Paranoid

Static analysis is the art of interrogating code without ever letting it run. No execution, no sandbox, just you and the binary staring each other down. For reverse engineers, vulnerability researchers, and anyone who’s ever squinted at a suspicious firmware blob at 2 a.m., the right tool doesn’t just help—it’s the difference between spotting the landmine and stepping on it. This isn’t about automated CI scanners that spit out false positives like confetti. It’s about understanding structure, control flow, and the weird, hidden assumptions baked into compiled software when you have no source, no symbols, and zero trust.

Over the years, a handful of tools have earned their keep. Some are open-source workhorses that rewired the industry overnight. Others are commercial beasts with price tags that make managers choke on their coffee. All of them have one thing in common: they’re useless unless the person driving them knows what questions to ask. Here’s what actually delivers when the stakes are high.

Ghidra: The NSA’s Parting Gift

When Ghidra dropped in 2019, it didn’t just make waves—it redrew the map. The National Security Agency built it, then open-sourced it, handing the reverse engineering world a disassembler, decompiler, and analysis suite rolled into one Java-based package. The decompiler is the star of the show. It spits out C code that’s often cleaner and more readable than what you’d get from tools that cost a fortune. I’ve seen it reconstruct logic from heavily optimized binaries that left other decompilers in the dust.

But the real magic for static analysis is Ghidra’s extensibility. The API lets you write scripts in Java or Python to automate the soul-crushing stuff—hunting for known-bad patterns, extracting data structures, or just renaming functions so you don’t lose your mind. The graph views for control flow and call trees are indispensable when you’re trying to map out a sprawling malware sample or a monolithic router firmware. And because it’s a full reverse engineering environment, you can pivot from static analysis to patching and annotation without juggling three different tools. The learning curve is brutal—expect to spend a week just figuring out the project management—but once it clicks, you’ll wonder how you ever worked without it.

Close-up of a computer screen displaying lines of code in a dark room

Binary Ninja: Speed and Clarity

Binary Ninja, from the folks at Vector 35, is the commercial upstart that made speed and a clean interface its selling points—and then backed it up with serious analytical horsepower. The platform’s intermediate language (IL) system is where it really earns its reputation. It lifts disassembly into a stack of ILs: Low Level IL, Medium Level IL, High Level IL. Each layer peels away architecture-specific weirdness, so you can write analysis plugins that think about program logic instead of x86 opcode quirks. That’s a game-changer for static analysis across multiple targets.

The collaborative features and headless API have made Binary Ninja a go-to for teams building custom detection pipelines. Its type recovery and data flow analysis are sharp—often flagging issues that would take hours of manual annotation in other tools. If you’re tearing apart embedded systems or IoT firmware, the support for obscure architectures is a lifeline. It’s not cheap, but the time it saves on analysis pays for itself faster than you’d think. The interface feels modern, responsive, and doesn’t fight you the way some legacy tools do.

IDA Pro: The Old Guard

You can’t talk about static analysis without IDA Pro. It’s the granddaddy, the one that’s been around so long it’s practically furniture. But don’t let its age fool you—it’s still a beast. The interactive nature of IDA is what sets it apart. You rename variables, define structs, annotate the disassembly, and slowly the binary turns from a wall of hex into a story you can follow. Its FLIRT signature recognition automatically labels library functions, saving you from the tedium of reverse engineering printf for the hundredth time.

The plugin ecosystem is a sprawling, chaotic bazaar. The Hex-Rays decompiler (sold separately, because of course) is the gold standard, and community scripts can spot obfuscation, crypto constants, or anti-analysis tricks. The trade-off? Cost and a learning curve that feels like climbing a cliff. The interface looks like it was designed in the ’90s and the licensing model can be a headache. Still, for deep-dive analysis of complex malware or proprietary protocols, IDA is the measuring stick. If you’re serious, you’ll end up here eventually.

A person typing on a laptop keyboard with lines of code reflected in their glasses

Radare2 / Rizin: The Swiss Army Knife

Radare2 and its sleeker fork, Rizin, are the tools you grab when you need to work fast, in a terminal, and without a GUI getting in the way. These open-source frameworks are scriptable, portable, and support a frankly ridiculous number of file formats and architectures. The learning curve is a brick wall—the command syntax is terse, cryptic, and utterly unforgiving—but once you’ve internalized it, you can rip through complex analysis with a few keystrokes. It’s the tool for people who think mice are a distraction.

For static analysis, Radare2’s built-in commands can pull strings, hunt byte patterns, compute entropy, and generate call graphs. Its visual mode gives you a lightweight alternative to heavier GUIs when you’re stuck in an SSH session. Rizin has cleaned up a lot of the legacy mess and improved decompiler integration. These tools shine when you’re triaging a mountain of samples or working in resource-constrained environments where installing a full IDE isn’t an option. They’re not pretty, but they get the job done.

Angr: Symbolic Execution for the Masses

Angr isn’t a disassembler. It’s a binary analysis framework built on symbolic execution, born at UC Santa Barbara, and it lets you reason about programs mathematically. You can ask questions like “What input reaches this basic block?” or “Is there a path that dodges this crash?” without ever running the binary. That’s static analysis pushed to its logical extreme—and it’s as powerful as it sounds.

The catch is state explosion. Angr’s ability to explore all possible execution paths is its superpower and its kryptonite. Using it well means understanding constraint solving and knowing how to guide the analysis with hooks and path pruning. It’s not a point-and-click affair. But for finding deep logic bugs or generating inputs that trigger specific code paths, nothing else comes close. Pair it with a disassembler to visualize the results, and you’ve got a combination that can crack problems other tools can’t even see.

Practical Workflow: Combining Tools

No single tool does it all. A typical static analysis session might start with Ghidra or IDA to get a high-level overview and start annotating. Suspicious functions get exported and fed into Binary Ninja for IL-level analysis. If a code path looks interesting but impossible to trigger, Angr steps in to solve for the necessary conditions. Meanwhile, Radare2 scripts handle bulk extraction of strings and metadata across hundreds of related samples. It’s a workflow built on knowing each tool’s strengths and weaknesses.

The real skill is avoiding the black-box trap. You need to understand what’s happening under the hood—how the disassembler resolves indirect calls, how the decompiler infers types—so you can spot when the analysis is lying to you. Static analysis is fundamentally about building a mental model of the code. The tools are just lenses. If you don’t understand the lens, you’ll misread what you’re seeing.

Common Pitfalls in Static Analysis

Obfuscation is the obvious enemy. Packed binaries, control flow flattening, opaque predicates—they can turn a clean disassembly into gibberish. But even without deliberate obfuscation, compiler optimizations can tie your brain in knots. Inlined functions, tail-call elimination, jump table optimizations—they all obscure the original program logic. Recognizing these patterns isn’t something you pick up from a tutorial. It comes from staring at disassembly until your eyes bleed.

Another trap is over-relying on decompiler output. Decompilers are incredible, but they make assumptions. They can misidentify calling conventions, misinterpret data as code, or produce C that compiles but doesn’t match the original semantics. Always verify against the disassembly. When in doubt, trace the data flow by hand. It’s slow, but it’s the only way to be sure.

A magnifying glass over a printed circuit board with glowing traces

FAQ

What’s the difference between static and dynamic analysis?

Static analysis examines a program without executing it, looking at code structure, control flow, and data references. Dynamic analysis runs the program in a controlled environment—a debugger or sandbox—to observe its behavior. Static analysis is safer for malware and can reveal code paths that aren’t easily triggered, but it can’t see runtime values. The two approaches are complementary, not competing.

Do I need to know assembly language to use these tools?

Yes, absolutely. While decompilers can produce C-like output, you’ll frequently need to read and understand the underlying assembly to verify the decompiler’s work, especially when dealing with obfuscated code or unusual constructs. At minimum, you should be comfortable with x86/x64 and ARM. Without assembly, you’re just guessing.

Which tool is best for analyzing malware?

There’s no single best tool, but Ghidra and IDA Pro are the most common starting points. Ghidra’s decompiler and collaborative features make it excellent for team-based malware analysis. IDA’s mature plugin ecosystem offers specialized scripts for unpacking, deobfuscation, and signature detection. Radare2 is useful for quick triage and automation when you’re dealing with a flood of samples.

Can static analysis find all vulnerabilities?

No. Static analysis can identify potential vulnerabilities like buffer overflows, use-after-free patterns, or insecure API calls, but it cannot confirm exploitability without runtime context. Many bugs only become apparent when specific inputs interact with program state. Static analysis is a filtering and discovery mechanism, not a guarantee. Think of it as a metal detector, not an X-ray machine.

Static Dissection: The Tools That Expose Binaries Without Execution

There’s a quiet war going on inside every compiled binary. On one side, the original logic—obfuscated, stripped, or just buried under layers of compiler optimizations—sits frozen in time. On the other, a reverse engineer armed with nothing but a disassembler and a hex editor tries to reconstruct meaning from a sea of bytes. No sandbox, no debugger, no execution. Just the file, its structure, and the tools that can peel it apart. If you’ve spent any time in the trenches of malware triage, firmware extraction, or vulnerability research, you already know: dynamic analysis only gets you so far. Sometimes the sample won’t run. Sometimes it’s a kernel driver. Sometimes you just need to understand what a binary can do without ever letting it touch a CPU. That’s where your toolkit makes or breaks you.

This isn’t a beginner’s shopping list. It’s a walkthrough of the instruments that actually matter when you’re staring at a raw ELF, PE, or Mach-O and need to figure out what it’s up to. We’ll cover disassemblers, hex editors with real muscle, format parsers, and the specialized gear that catches what the big suites miss. No buzzwords, no sales pitches—just the stuff that belongs in a serious reverse engineer’s locker.

The Heavy Hitters: Disassemblers and Decompilers

If static analysis has a center of gravity, it’s the disassembler. This is where machine code gets translated into assembly language, giving you a map of the binary’s logic. A good disassembler doesn’t just dump opcodes; it reconstructs control flow, identifies functions, and cross-references data. It becomes your primary lens for understanding the binary.

Ghidra is the NSA’s open-source beast, and it’s earned its place in the toolkit. The decompiler is shockingly good for a free tool, often producing cleaner C-like output than some paid alternatives. It handles x86, ARM, MIPS, and a growing list of exotic architectures. The real power, though, is in its scripting engine—Java or Python—which lets you automate tedious tasks like decoding custom string obfuscation or identifying known cryptographic constants. The graph view makes control flow visible at a glance, and the collaborative server means multiple analysts can work the same binary without stepping on each other’s toes. The learning curve is real, but once you’ve got the data type manager dialed in and a few scripts under your belt, you’ll wonder how you ever worked without it.

IDA Pro still holds the throne for complex or exotic binaries. Its interactive interface turns a disassembly listing into a living document—renaming functions, retyping variables, adding comments, all with a few keystrokes. The FLIRT signature engine automatically labels known library functions, saving you hours of manual identification. The free version is a capable disassembler, but the full suite with the Hex-Rays decompiler is where IDA really shines. The plugin ecosystem is enormous: Python scripting, third-party extensions like Diaphora for binary diffing, and custom loaders for obscure file formats. It’s less a tool and more a platform.

Binary Ninja has carved out a loyal following with its slick interface and a genuinely useful intermediate language (IL) analysis. The medium-level IL (MLIL) output often reads cleaner than raw decompilation, making it easier to spot patterns at a glance. Its API-first design appeals to teams building custom analysis pipelines, and its speed on large binaries is noticeable. For ARM or MIPS firmware, Binary Ninja’s support is solid, and the collaborative features keep getting better.

radare2 (and its GUI frontend, Cutter) is the command-line junkie’s Swiss Army knife. It’s free, open source, and can dissect everything from x86 to obscure microcontrollers. The learning curve is brutal—memorizing commands like afl (analyze functions list) and izz (search strings) takes time—but the payoff is a tool that can be scripted, piped, and embedded into automated pipelines. For quick triage of a suspicious file, radare2’s string search and entropy analysis are hard to beat.

Close-up of a computer screen displaying disassembly code

Peeling Back the Layers: File Format Parsers

Before you even fire up a disassembler, you need to understand the binary’s anatomy. Format parsers dissect headers, sections, imports, and resources, flagging anomalies that might point to packing, corruption, or deliberate tampering. Think of them as your first reconnaissance pass.

readelf and objdump (from GNU binutils) are the old guard for ELF files. A few flags dump section headers, symbol tables, and dynamic linking info. For PE files, pev (PE Viewer) and pecheck do the same from the command line. But when you want a visual approach, PE-bear offers a Qt-based interface that makes navigating the PE structure intuitive. It highlights anomalies, decodes rich headers, and lets you edit fields on the fly—handy for repairing corrupted files or understanding packer stubs.

For Mach-O binaries, MachOView is the go-to. It graphically displays the entire Mach-O structure, from fat binary headers to load commands and sections. When you’re dealing with iOS or macOS malware, this tool helps spot suspicious entitlements, encrypted segments, or abnormal dyld shared cache references. Pair it with jtool2 for command-line parsing and disassembly of Mach-O files, especially when working on a remote server or embedded device.

Hex Editors with Brains

Sometimes you need to get your hands dirty at the byte level. A hex editor isn’t just for viewing raw data—it’s for patching, carving, and manually reconstructing structures. The right hex editor understands binary formats and can interpret data on the fly.

010 Editor stands out with its binary templates. These templates parse file structures and display them in a tree view, letting you click through headers, fields, and substructures. For reverse engineering custom file formats or network protocols, you can write your own templates in a C-like syntax. The integrated disassembler and data inspector make it a lightweight analysis environment all on its own.

ImHex is a newer, open-source alternative that’s gaining traction. It features a pattern language for defining structures, a built-in node graph for data processing, and a modern dark interface. Its diffing capabilities are useful for comparing two versions of a binary to spot patches or injected code. For quick edits, HxD on Windows remains a fast, no-nonsense option with disk editing and memory dumping features.

Person analyzing code on multiple monitors in a dark room

String Analysis and Entropy Detection

Strings are the low-hanging fruit of static analysis. A quick strings dump can reveal IP addresses, URLs, registry keys, and even debug messages left by the developer. But modern malware rarely hands you plaintext. Strings get obfuscated, encrypted, or built on the stack at runtime, so you need tools that go beyond ASCII extraction.

FLOSS (FireEye Labs Obfuscated String Solver) is designed to automatically extract deobfuscated strings from malware. It uses heuristics and light emulation to decode stack strings, tight loops, and other common obfuscation techniques. Running FLOSS on a sample before you open a disassembler can give you a serious head start on identifying capabilities.

Entropy analysis helps detect packing and encryption. High entropy sections suggest compressed or encrypted data that might be unpacked at runtime. Detect It Easy (DIE) is a packer identifier that goes beyond simple signatures—it calculates entropy, examines section characteristics, and uses heuristics to name the packer or compiler. It’s cross-platform and supports plugins for custom detection logic.

Specialized Static Analyzers

Some tasks fall through the cracks of general-purpose disassemblers. That’s where niche tools come in, saving you hours of manual work when you’re dealing with specific file types or analysis goals.

Checksec is a tiny shell script that checks binary hardening features: PIE, RELRO, stack canaries, NX, and Fortify. It’s part of the pwntools suite and is essential for exploit developers assessing target difficulty. For a deeper dive into ELF security, readelf with the -l flag reveals GNU_RELRO segments and stack executability.

BinDiff (now free, integrated into Ghidra) and Diaphora are binary diffing tools that compare two versions of a binary to identify changed functions. This is invaluable for patch analysis: diff the vulnerable and patched versions to find the exact code fix, then reverse the vulnerability. Diaphora works as an IDA plugin and uses multiple heuristics—assembly, pseudo-code, graph matching—to produce high-quality matches.

For analyzing shellcode, scdbg is a libemu-based emulator that logs API calls without executing the code natively. It’s not truly static, but it bridges the gap by emulating just enough to decode the shellcode’s intent. Pair it with sctest for automated testing of shellcode samples.

Building a Workflow

Static analysis isn’t about picking one tool—it’s about chaining them into a pipeline that answers specific questions. Start with file identification: the file command, DIE, and a format parser. Check entropy and strings. If the binary is packed, consider unpacking it statically by locating the original entry point and dumping the unpacked code—tools like UPX can handle common packers, but for custom ones you’ll need to manually reconstruct the import table. Then load the unpacked binary into your disassembler of choice and begin function-level analysis.

Document as you go. Use the disassembler’s commenting and bookmarking features to mark interesting functions, suspicious strings, and potential vulnerabilities. Export your findings to a report or share the project file with your team. The goal is to build a mental model of the binary’s behavior without ever running it—a skill that separates the script kiddies from the professionals.

Close-up of a laptop keyboard with code on the screen

FAQ

What’s the difference between static and dynamic binary analysis?

Static analysis examines a binary without executing it, focusing on its structure, code, and data. Dynamic analysis runs the binary in a controlled environment (sandbox, debugger) to observe its behavior. Static analysis is safer for malware, can cover all code paths, and is often the only option for non-executable files like firmware or drivers. Dynamic analysis reveals runtime behavior like network connections and process injection. A complete investigation uses both.

Do I need to learn assembly language for static analysis?

Yes, at least one architecture’s assembly (x86, ARM, or MIPS) is necessary. Decompilers can produce C-like pseudo-code, but they’re imperfect—especially with obfuscated or hand-crafted assembly. Understanding the instruction set lets you verify decompiler output, spot anti-disassembly tricks, and manually analyze critical sections. Start with x86-64, as it’s widely documented and used in most desktop malware.

Can static analysis detect all types of malware?

No. Heavily obfuscated, polymorphic, or VM-protected malware can resist static analysis entirely. Some samples decrypt or download payloads only at runtime. In these cases, static analysis might reveal the packer or loader, but dynamic analysis is needed to capture the final payload. However, static analysis is still valuable for initial triage, identifying packers, and extracting metadata like compilation timestamps.

What’s the best free tool for static analysis?

Ghidra is the most powerful free option, offering a full decompiler and collaborative features. For quick triage, radare2/Cutter is excellent. If you’re on a budget, combine GNU binutils, strings, and a hex editor like HxD or ImHex. The free version of IDA is also useful for basic disassembly but lacks decompilation. Your choice depends on the task: Ghidra for deep dives, radare2 for automation, and binutils for quick checks.

Static Binary Analysis: Tools and Tactics for the Underground Engineer

Static binary analysis is the craft of taking apart compiled code without ever running it. For the reverse engineer, the exploit developer, or the security researcher working in the trenches, it’s a core skill. You’re not just firing up a tool and skimming a report—you’re reconstructing logic, hunting for flaws, and figuring out how a piece of software actually ticks at the machine level. This isn’t about automated scanners that flood you with false positives; it’s about the manual and semi-automated gear that puts you in control.

This guide digs into the tools that matter when you’re deep in a disassembler, tracing control flow, or trying to make sense of a stripped firmware blob. We’ll cover disassemblers, decompilers, binary inspection frameworks, and specialized utilities that help you peel back the layers of an ELF, PE, or Mach-O file. No marketing speak—just the stuff that works when you’re staring at hex dumps at 3 AM.

Close-up of a computer screen displaying hexadecimal code and disassembly output

Disassemblers: The Heart of the Operation

A disassembler turns machine code back into assembly language. It’s the first real step in understanding what a binary is up to. The quality of your disassembler dictates how quickly you can spot functions, loops, and data structures. You want one that handles multiple architectures, resolves cross-references cleanly, and gives you a navigable graph view. The big names here are IDA Pro and Ghidra, but there are other players worth your time.

IDA Pro

IDA Pro has been the industry workhorse for decades. Its interactive interface, broad processor support, and powerful scripting (via IDC and Python) make it a go-to for professionals. The graph view is crisp, the type system is deep, and the plugin ecosystem is enormous—you can bolt on everything from decompilation to pattern-matching engines. The catch? It’s pricey, and the licensing can feel restrictive. But if you’re doing this work daily, the time it saves often justifies the cost.

Ghidra

When the NSA released Ghidra in 2019, it shook up the scene. Open-source, free, and packed with features that rival IDA Pro, Ghidra’s decompiler is often surprisingly clean, especially on ARM and MIPS binaries. The collaborative mode, which lets multiple analysts work on the same binary at once, is a genuine advantage for team projects. Scripting is in Java, which can be a hurdle if you’re a Python diehard, but the API is well-documented. For anyone starting out or working without a budget, Ghidra is the obvious pick. It handles x86, ARM, MIPS, and more without flinching.

Binary Ninja

Binary Ninja occupies a middle lane: commercial but affordable, with a modern interface and a Python API that feels natural. Its intermediate language (IL) is a standout, letting you write analysis scripts that work across different architectures. The decompiler is solid, though not as battle-tested as Ghidra’s or IDA’s. If you want a polished, scriptable environment without the IDA price tag, Binary Ninja is a strong contender.

Decompilers: From Assembly to Something You Can Read

Reading assembly is necessary, but reading C-like pseudocode is faster. Decompilers lift assembly into a higher-level representation, so you can grasp the logic at a glance. They’re not magic—obfuscated code, indirect calls, and weird calling conventions can still trip them up—but they’re indispensable for quick comprehension.

Hex-Rays Decompiler

Hex-Rays is the decompiler bundled with IDA Pro. It’s mature, highly configurable, and integrates tightly with IDA’s database. You can rename variables, retype functions, and drop comments that propagate back to the disassembly. The output is usually clean, though heavily optimized or obfuscated code can make it stumble. The microcode API (available in recent versions) lets you write custom optimization passes—a deep rabbit hole, but incredibly powerful if you need it.

Ghidra’s Decompiler

Ghidra’s decompiler is, honestly, remarkable for a free tool. It often produces more readable output than Hex-Rays, especially on ARM binaries. The ability to quickly patch bytes and re-decompile without restarting the analysis is a huge time-saver. It’s not as extensible as Hex-Rays at the microcode level, but for most reverse engineering tasks, it’s more than enough.

Abstract visualization of binary code and data flow

Binary Inspection and Analysis Utilities

Sometimes you don’t need a full disassembler. You just want to peek at headers, strings, imports, or entropy. These utilities are the Swiss Army knives of binary analysis—fast, focused, and scriptable.

Radare2 / Rizin

Radare2 (and its modern fork, Rizin) is a command-line toolbox for binary analysis. It’s not just a disassembler; it’s a hex editor, debugger, and binary diffing tool rolled into one. The learning curve is steep, but once you internalize the commands, you can slice through binaries at lightning speed. It’s particularly useful for CTFs, malware triage, and embedded firmware analysis where you need to script repetitive tasks.

readelf, objdump, and nm

Don’t overlook the classics. These GNU binutils are available on any Linux system and give you immediate insight into ELF structure. readelf dumps section headers, symbol tables, and dynamic linking information. objdump provides quick disassembly and relocation data. nm lists symbols. When you’re dealing with a suspicious shared object or a stripped binary, these tools are your first line of reconnaissance. Combine them with strings and file to build an initial profile before firing up a heavy disassembler.

Cutter

Cutter is the graphical frontend for Rizin. It brings a more intuitive interface to the radare2 engine, with graph views, hex dumps, and decompilation (via the Ghidra decompiler or Rizin’s own). It’s a solid choice if you want the power of radare2 without memorizing a thousand commands.

Specialized Tools for Deeper Analysis

Beyond the general-purpose platforms, there are tools built for specific tasks: identifying packers, analyzing shellcode, or tracing data flow. These are the tools you reach for when the standard disassembler isn’t enough.

Detect It Easy (DIE)

Before you even open a disassembler, you need to know what you’re dealing with. Detect It Easy is a packer identifier and binary analysis tool that goes far beyond the old PEiD. It identifies compilers, linkers, packers, and cryptors across PE, ELF, and Mach-O formats. It’s scriptable, open-source, and constantly updated with new signatures. If a binary is packed with a custom variant of UPX or a lesser-known protector, DIE will often give you the first clue.

angr

angr is a binary analysis framework built for symbolic execution and control-flow analysis. It’s not a disassembler you’d use for manual reversing; it’s a Python framework for automating complex analysis tasks. Want to find a specific code path that leads to a vulnerable function? angr can symbolically execute the binary and give you the input constraints. It’s heavy, sometimes slow, but incredibly powerful for vulnerability research and automated exploit generation.

Binwalk

When you’re dealing with firmware images or embedded systems, Binwalk is essential. It scans binary blobs for embedded files and known magic bytes, extracting filesystems, kernels, and compressed archives. It’s not a disassembler, but it’s often the first tool you run on a router firmware dump to unpack the filesystem and find the actual binaries you need to reverse.

Digital representation of binary code streams and data analysis

Building a Workflow

Static analysis isn’t about using one tool; it’s about chaining them together. A typical workflow for an unknown binary might look like this:

  1. Triage: Run file to identify the format, then strings to grab any human-readable data. Use Detect It Easy to identify the compiler, packer, or any known signatures.
  2. Unpacking/Extraction: If the binary is packed, use Binwalk or a dedicated unpacker to get to the raw code. For firmware, Binwalk extracts the filesystem.
  3. Disassembly: Load the binary into Ghidra or IDA. Run initial auto-analysis to identify functions and cross-references.
  4. Decompilation: Switch to the decompiler view to understand high-level logic. Rename variables and functions as you identify them.
  5. Deep Dive: For complex functions, use a framework like angr to symbolically explore paths or find specific conditions.
  6. Scripting: Automate repetitive tasks with IDAPython, Ghidra scripts, or radare2 commands.

This workflow is iterative. You’ll jump back and forth between steps as you uncover new information. The key is to stay flexible and use the right tool for the immediate problem.

Why Static Analysis Still Matters

In an era of sandboxes and dynamic analysis platforms, static analysis remains the bedrock of understanding compiled code. Dynamic analysis shows you what a binary does in a specific environment; static analysis shows you what it can do. It reveals hidden code paths, dormant backdoors, and logic bombs that might never trigger in a sandbox. For vulnerability research, static analysis lets you reason about memory corruption and control flow without needing a working exploit. It’s the difference between observing behavior and understanding mechanism.

Static analysis is often the only option when dealing with proprietary firmware, embedded systems, or malware that refuses to run in a VM. If you’re tearing down IoT device firmware or analyzing a rootkit, you won’t have the luxury of a debugger. You need to be comfortable staring at raw disassembly and making sense of it.

FAQ

What’s the best free tool for static binary analysis?

Ghidra is the top free option. It offers a full-featured disassembler, a high-quality decompiler, and collaborative analysis capabilities. For quick command-line tasks, radare2 (or Rizin) is also free and extremely powerful, though it has a steeper learning curve.

How do I handle obfuscated or packed binaries?

Start with Detect It Easy to identify the packer. If it’s a known packer, use the appropriate unpacker or manually dump the process from memory after execution. For custom obfuscation, you’ll need to combine static analysis with dynamic techniques—run the binary in a debugger, break after the unpacking stub executes, and then dump the clean code. Tools like angr can also help deobfuscate control flow.

Is IDA Pro still worth the cost?

For professional reverse engineers who need the most mature ecosystem, extensive processor support, and the Hex-Rays decompiler, IDA Pro remains a solid investment. However, Ghidra has closed the gap significantly, and many independent researchers find it more than sufficient. The choice often comes down to whether you need IDA’s specific plugins or prefer its workflow.

What’s the best way to learn static analysis?

Start with simple crackmes and CTF challenges. Use Ghidra or radare2 to disassemble them, and focus on understanding control flow and data references. Read write-ups after attempting challenges to see how others approach the same binary. Practice on real-world firmware or malware samples from repositories like VirusTotal or firmware dumps from router manufacturers. The skill comes from hours of staring at disassembly, not from reading about it.

Static Analysis Arsenal: Tools and Tactics for the Underground Engineer

Static Analysis Arsenal: Tools and Tactics for the Underground Engineer

When you’re staring at a raw binary blob with no source code, the first instinct shouldn’t be to double-click it. That’s a fast track to owning yourself. The real work starts cold, in the static domain—dissecting the file without ever letting it breathe. This is where you map the minefield, spot the traps, and piece together the logic before you ever risk execution. Here’s a rundown of the tools and techniques that actually matter for static binary analysis, straight from the trenches.

Why Static Analysis Comes First

Dynamic analysis has its moments, but running an unknown sample is a dice roll. You might trip anti-debugging tricks, fire off network beacons, or worse—detonate a destructive payload. Static analysis keeps things in a controlled, offline sandbox. You can pick apart imported functions, sift through strings, map the control flow, and flag suspicious patterns without the binary ever sensing a thing. For malware analysts, vulnerability researchers, and anyone tearing into proprietary firmware, this is the unglamorous foundation. It’s not flashy, but it’s where true understanding takes root.

The tools I’m talking about aren’t the glossy, automated platforms that promise one-click miracles. Those are opaque boxes that fail quietly. Instead, I’m zeroing in on the workhorses—the ones that hand you direct control over disassembly, decompilation, and binary inspection. These are the tools that let you see the raw truth of the machine code, no filters.

Core Disassemblers and Decompilers

Every serious reverse engineer needs a disassembler they’d trust with their life. This is the tool that translates raw bytes into human-readable assembly mnemonics. A solid disassembler juggles multiple architectures, recognizes common library functions, and lets you annotate freely. The decompiler—its close cousin—tries to reconstruct C-like pseudocode from assembly. It’s never perfect, but it slashes the time needed to grok complex functions.

Close-up of code on a computer screen, representing disassembly view

Ghidra: The Open-Source Powerhouse

Dropped by the NSA, Ghidra has become the default for plenty of folks in both underground and professional circles. Its decompiler is top-shelf, often spitting out cleaner pseudocode than the paid alternatives. The real muscle comes from its extensibility—scripts in Java or Python can automate the grunt work: deobfuscation, function renaming, hunting for specific crypto constants. The collaborative server mode lets multiple analysts tear into the same binary at once, which is a godsend for large-scale malware campaigns. Ghidra’s support for weird architectures, from MIPS to SuperH, makes it a must-have when you’re knee-deep in embedded device firmware.

IDA Pro: The Veteran’s Scalpel

IDA Pro still holds the crown for interactive disassembly, even with its eye-watering license fee. The interactive interface is unmatched for manual work. Zipping through cross-references, defining structs, and applying type info makes tracing convoluted code paths feel almost fluid. The plugin scene, especially with the Hex-Rays decompiler, opens up deep customization. If you’re wrestling with heavily obfuscated or custom-protected binaries, IDA’s Python scripting (IDAPython) gives you the low-level grip needed to peel back anti-analysis layers. It’s not cheap, but for certain targets, nothing else comes close.

Radare2 / Rizin: The Terminal Warrior’s Choice

For the diehards who live in the terminal, Radare2 and its fork Rizin deliver a free, scriptable, and absurdly flexible analysis framework. The learning curve is a cliff face, but the payoff is a tool you can warp to any purpose. Its command-line interface enables rapid, repeatable analysis pipelines. Need to yank all strings from a specific section, find every cross-reference to a memory address, and then patch the binary? A one-liner in r2 can handle it. The visual mode and graph views are surprisingly potent for a terminal app. Rizin’s focus on stability and a cleaner codebase makes it the smarter modern pick for many.

Binary Inspection and Format Parsers

Before you even touch the disassembly, you need to understand the binary’s skeleton. These tools parse file format headers, sections, symbols, and other metadata. They’re essential for spotting packed or corrupted files, verifying checksums, and grasping the memory layout the loader will create.

Abstract visualization of binary data streams

readelf and objdump (GNU Binutils)

These are the bedrock. readelf spills detailed info about ELF files—program headers, section headers, symbol tables, dynamic linking details, relocation entries. It’s the first thing I throw at any Linux binary. objdump does disassembly, but its real strength is dumping raw section contents, headers, and full file information. Together, they give you a complete map of the binary’s anatomy without any higher-level analysis that can sometimes muddy the low-level details.

MachOView and otool (macOS)

For Mach-O binaries, MachOView offers a graphical tree view of the entire file structure, making it a breeze to navigate the tangled Mach-O format. otool is the command-line counterpart, capable of showing load commands, sections, and disassembly. When you’re poking at iOS apps or macOS malware, these are your first stops to check for encrypted segments, weird load commands, or suspicious entitlements.

PE-bear and CFF Explorer (Windows)

On Windows, PE-bear is a modern, actively maintained PE viewer that shines at rebuilding corrupted headers and visualizing the PE structure. CFF Explorer is an older tool but still handy for deep PE editing. Both let you inspect import and export tables, resources, and section characteristics. Spotting a section with read, write, and execute permissions is a red flag that static analysis catches in a heartbeat.

Specialized Static Analysis Utilities

Beyond the big frameworks, a clutch of focused utilities can answer specific questions fast. These tools often do one thing exceptionally well and can be chained together in scripts for automated triage.

Strings and FLOSS

The classic strings command is a first pass, but malware authors know this game. They obfuscate, encrypt, or stack-construct strings to hide them. FireEye’s FLOSS (FLARE Obfuscated String Solver) flips the table. It statically analyzes a binary to pull out obfuscated strings by emulating small code sequences, identifying stack strings, and decoding common algorithms. Running FLOSS on a packed sample often surfaces C2 servers, registry keys, and mutex names that a plain strings dump would miss entirely.

YARA: Pattern Matching for Binaries

YARA isn’t just for antivirus engines. Writing custom YARA rules lets you hunt for specific code patterns, cryptographic constants, or unique strings across a massive corpus of binaries. For a reverse engineer, this means you can identify code families, find embedded libraries, or locate known vulnerable functions in firmware dumps. A well-tuned YARA rule can pinpoint a specific version of a statically linked zlib or OpenSSL in seconds.

Binwalk: Firmware Extraction and Analysis

When you’re dealing with embedded device firmware, the binary is often a Frankenstein mashup of a bootloader, kernel, and multiple filesystems. Binwalk scans a binary for magic bytes of known file types and can automatically extract the identified components. It’s essential for pulling apart router firmware, IoT device images, or any blob that contains multiple concatenated filesystems. The entropy analysis feature also helps pinpoint compressed or encrypted sections.

Graph-Based and Visual Analysis

Sometimes, the relationships between functions tell you more than the code itself. Visualizing the call graph or control flow graph can expose the program’s high-level logic, flag the main execution path, and highlight weird branches that need a closer look.

Network graph visualization representing function call relationships

Gephi and Graphviz for Call Graphs

Tools like IDA and Ghidra can export function call graphs, but rendering them in Gephi or with Graphviz allows for interactive exploration and layout algorithms that reveal clusters. A tightly interconnected cluster of functions might be the core crypto engine. A single function with hundreds of incoming calls is probably a logging or memory allocation wrapper. This macro-level view steers your micro-level analysis, saving hours of aimless scrolling.

Binary Ninja’s HLIL and Graph View

Binary Ninja has carved out a niche with its clean interface and powerful intermediate language (IL) analysis. Its High-Level IL (HLIL) is often more readable than Ghidra’s or IDA’s decompiler output for certain constructs. The graph view is snappy, and the platform’s API allows for custom analysis passes that can annotate the graph directly. For those who prefer a modern, scriptable environment without the baggage of older tools, Binary Ninja is a strong contender.

Building Your Own Static Analysis Toolkit

The most effective analysts don’t just use tools—they build their own. A static analysis pipeline tailored to your specific targets can automate the tedious parts and surface the interesting bits. This might involve a Python script that uses the pefile or pyelftools libraries to parse binaries, runs FLOSS and YARA, and then generates a summary report. Or a Ghidra script that automatically renames functions based on resolved API imports and comments on known anti-debug patterns. The goal is to shrink the time from “unknown binary” to “actionable intelligence.”

For example, a common workflow for triaging a suspicious Windows DLL might look like this:

  1. Use PE-bear to check the PE headers, exports, and section entropy.
  2. Run FLOSS to extract any hidden strings.
  3. Apply a set of custom YARA rules to identify known packers or malware families.
  4. Load into Ghidra, run an auto-analysis script, and examine the entry point and exports.
  5. If the binary is packed, use binwalk and manual hex editing to locate the original entry point before unpacking.

This pipeline is repeatable, scriptable, and doesn’t rely on any single point of failure. Each tool provides a different lens, and the overlaps confirm findings while the gaps reveal where deeper manual work is needed.

Frequently Asked Questions

What’s the difference between static and dynamic analysis?

Static analysis examines a binary without executing it. You’re looking at the code, data, and structure as they exist on disk. Dynamic analysis involves running the binary in a controlled environment (like a sandbox or debugger) to observe its behavior. Static analysis is safer for initial triage and reveals the full codebase, while dynamic analysis shows what code actually executes under specific conditions. They’re complementary, but static analysis should always come first to avoid triggering anti-analysis traps.

Can static analysis handle heavily obfuscated or packed binaries?

Yes, but it requires more effort. Packers compress or encrypt the original code, so a static tool will only see the unpacking stub. The first step is to identify the packer (using tools like PEiD or YARA rules) and then manually unpack the binary. For obfuscated code, static analysis can still map the control flow and identify obfuscation patterns. Tools like FLOSS can statically decode some obfuscated strings. In extreme cases, you might need to write a custom deobfuscator script for your disassembler.

Is Ghidra a complete replacement for IDA Pro?

For many tasks, yes. Ghidra’s decompiler is excellent, and its collaborative features are superior. However, IDA Pro still has a more mature plugin ecosystem for niche architectures and advanced anti-reverse engineering techniques. Some debugger integrations and older processor modules are only available for IDA. If you’re starting out or working on common x86/ARM targets, Ghidra is the pragmatic choice. If you’re deep into specialist embedded systems or need specific, battle-tested plugins, IDA might still be necessary.

How do I learn to use these tools effectively?

Start with small, open-source binaries where you can compare the disassembly to the original source code. Crackmes and CTF challenges are excellent for building skills. Focus on understanding the assembly language for your target architecture first—the tool is just a lens. Read other analysts’ write-ups to see their workflows. And most importantly, write scripts. Automating a task forces you to understand the tool’s API and the underlying data structures, which deepens your knowledge far more than clicking through a GUI.

Static analysis is a discipline of patience and precision. The tools are just instruments; the real skill is in knowing how to read the story the bytes are telling. Build your toolkit, learn your architectures, and always question what the tool is showing you. The truth is in the hex.

Plotting the Binary: Reconstructing Narrative Logic from Stripped Code

Most reverse engineers treat a stripped binary like a corpse on a slab. They poke it with disassemblers, pull strings, maybe throw a fuzzer at it, and hope the interesting bits float to the surface. That works for the obvious bugs—the unchecked buffer, the missing bounds check, the classic stack smash. It falls apart when you need to understand why the binary behaves the way it does, especially when the behavior is a tangled state machine buried inside a proprietary network protocol parser, inside a firmware blob that shipped without symbols, without documentation, and without mercy.

I spent two weeks staring at exactly that kind of binary last year. A client had a device that spoke a custom protocol over TCP, and they needed to know whether the parser had exploitable state-transition bugs. The firmware was a single 2MB blob, stripped, compiled for ARM Thumb-2, and the only clue was a PCAP of the device talking to its management console. The traditional approach—find the recv() call, trace the buffer, look for memcpy()—told me nothing about the protocol’s logic. I needed a different mental model.

That model turned out to be narrative. Not the hand-wavy “tell a story” kind, but the structural kind: every binary that implements a protocol parser has characters (key data structures), conflicts (error paths, race windows, unexpected input), and climaxes (the state transitions where assumptions collapse). Reconstructing that narrative is a teachable, repeatable skill. This article walks through the workflow I used, the tools that helped, and the moments where the binary’s plot became clear.

Why “Plotting” Works as a Mental Model

Screenwriters have a term for the skeleton of a story: the beat sheet. It’s a sequence of events that move the protagonist through a series of conflicts toward a resolution. In a well-structured screenplay, each scene has a purpose—it advances the plot, reveals character, or raises the stakes. The same is true of a protocol parser. Each basic block is a scene. Each state variable is a character trait. Each error path is a conflict. And the vulnerable state transitions are the climaxes where the parser’s assumptions about input collide with reality.

This isn’t just a metaphor. When you’re reversing a state machine, you’re literally reconstructing a sequence of conditional branches that determine what happens next. The parser reads a byte, checks it against a set of expected values, and either advances to a new state, stays in the current state, or jumps to an error handler. That’s a plot. The difference is that the plot is encoded in assembly rather than prose, and the characters are structs rather than people.

The Reedsy plot generator describes this process in terms that map directly to reverse engineering: “A protagonist who wants something and is prevented from getting it. This is the irreducible minimum.” In a binary, the protagonist is the parser’s main loop—it wants to consume input and reach a valid end state. The obstruction is malformed input, unexpected sequences, or resource exhaustion. The structured approach to plot generation that writers use—defining characters, conflict, stakes, and structure—is exactly what you need when you’re staring at a disassembly listing and trying to figure out which branch leads to the bug.

The Case Study: A Proprietary Protocol Parser

The firmware I was reversing implemented a protocol I’ll call “DevLink” (the real name is under NDA). The PCAP showed a three-way handshake followed by a series of type-length-value (TLV) messages. The handshake was straightforward: client sends a 4-byte magic number, server responds with a 4-byte challenge, client responds with an 8-byte response, and then the session enters a command loop. The TLV messages had a 1-byte type, a 2-byte length (big-endian), and a variable-length value. Simple enough.

But the PCAP also showed that certain sequences of messages caused the device to reset. Not crash—reset. That meant the parser was hitting a state that triggered a watchdog or a deliberate reboot. The client wanted to know whether that reset was exploitable. To answer that, I needed to reconstruct the entire state machine.

I started with Ghidra. The firmware was a raw binary, so I loaded it at the base address I’d extracted from the bootloader (0x08000000, typical for STM32-based devices). I let Ghidra’s auto-analysis run, then started looking for the recv() wrapper. In embedded firmware, network I/O often goes through a lightweight IP stack like lwIP, so I searched for calls to lwip_recv() and found three. Two were in the HTTP server (irrelevant), and one was in a function I named parse_devlink_message().

Identifying the Characters: Key Data Structures

The first step in plotting a binary is identifying the characters. In a protocol parser, the characters are the data structures that hold state. For DevLink, I found three:

  • The session context: a struct allocated at connection time, holding the current state, a buffer for reassembly, and pointers to the TLV handler table.
  • The TLV handler table: an array of function pointers, indexed by message type. Each handler took a pointer to the session context and a pointer to the TLV value.
  • The state enum: a set of constants representing the parser’s current position in the protocol flow—WAIT_MAGIC, WAIT_CHALLENGE_RESPONSE, COMMAND_LOOP, ERROR, and a few others I discovered later.

I recovered the session context by tracing the allocation call. The firmware used a custom heap allocator (not uncommon in embedded systems), and the allocation size was 0x200 bytes. I created a Ghidra struct for it and started populating fields as I found references. The state field was at offset 0x00, a uint32_t. The reassembly buffer was at offset 0x04, 0x100 bytes. The handler table pointer was at offset 0x104. The rest I filled in as I went.

The TLV handler table was harder. It wasn’t a simple array of function pointers—it was a sparse array, with 256 slots but only 12 handlers implemented. The rest pointed to a default handler that logged an error and incremented a counter. I found it by searching for the pattern of a switch statement in the disassembly: a series of CMP/BEQ pairs that branched to different functions. Ghidra’s decompiler turned it into a switch, but the jump table was inlined, so I had to manually extract the handler addresses.

Mapping the Conflicts: Error Paths and Race Windows

With the characters identified, the next step was mapping the conflicts. In a protocol parser, conflicts are the places where the parser’s expectations meet reality. These are the error paths, the bounds checks, the state validation checks, and—most interestingly—the places where those checks are missing.

I wrote a Python script for Ghidra that walked the control flow graph of parse_devlink_message() and extracted every conditional branch. For each branch, I recorded the condition (from the decompiler output), the taken and not-taken targets, and whether the branch led to an error handler or continued normal execution. The script output a CSV that I loaded into a spreadsheet for analysis.

The spreadsheet revealed something interesting: the parser had a state variable that tracked whether the session was “authenticated,” but the check for that variable was only performed at the start of the command loop. If a TLV message arrived during the handshake—before the command loop started—the parser would process it without checking authentication. That was a conflict: the parser’s assumption that TLV messages only arrive after authentication was violated by the protocol’s own design.

I confirmed this with dynamic analysis. I set up a QEMU emulation of the firmware (more on that later), sent a TLV message during the handshake, and watched the parser jump into a handler that assumed the session context was fully initialized. It wasn’t. The handler dereferenced a null pointer and crashed. That crash was the climax of this particular plot thread.

Finding the Climaxes: Vulnerable State Transitions

In screenwriting, the climax is the moment when the conflict reaches its peak and the protagonist’s fate is decided. In a binary, the climax is the state transition where the parser’s assumptions collapse and something interesting happens—a crash, a memory corruption, a privilege escalation. The StudioBinder guide to screenplay structure emphasizes that “each scene has a purpose—it advances the plot, reveals character, or raises the stakes.” The same structural principles apply to reverse engineering: each basic block advances the parser’s state, reveals information about the protocol, or raises the stakes by introducing new constraints.

To find the climaxes, I used dynamic trace diffing. I ran the firmware under QEMU with two different inputs: one that followed the normal protocol flow, and one that deviated at a specific point. I used QEMU’s -trace option to log every executed basic block, then diffed the traces to find where the execution paths diverged. The divergence points were the state transitions where the parser made a decision based on input.

I automated this with a script that sent a series of TLV messages, each time varying one byte of the input. For each variation, I recorded whether the parser reached the normal end state, an error state, or a crash. The results formed a map of the parser’s state machine: which inputs caused which transitions, and which transitions led to vulnerable states.

The most interesting climax was a state transition that occurred when the parser received a TLV message with type 0x17 (a “file transfer” command) during the handshake. The handler for type 0x17 assumed that a file descriptor had been opened, but during the handshake, that field in the session context was uninitialized. The handler called a function pointer from the uninitialized field, giving me control of the program counter. That was the vulnerability.

The Workflow: From Disassembly to Narrative

The workflow I’ve described isn’t specific to DevLink. It’s a general method for reconstructing the narrative logic of any state-machine-driven binary. Here’s the step-by-step:

  1. Identify the characters: Find the key data structures—the session context, the handler tables, the state enums. Use Ghidra’s struct editor to define them, and populate fields as you find references. The goal is to give names to the anonymous memory regions that the binary manipulates.
  2. Map the conflicts: Extract every conditional branch in the parser’s main loop. For each branch, determine what condition is being tested and what happens on each path. Look for missing checks—places where the parser assumes a condition holds without verifying it.
  3. Find the climaxes: Use dynamic trace diffing to identify the state transitions that lead to crashes, memory corruption, or other interesting behavior. Vary one byte of input at a time and observe how the execution path changes.
  4. Write the beat sheet: Document the parser’s state machine as a sequence of states and transitions. Use a format that’s readable by humans—a directed graph, a table, or a narrative description. The goal is to produce a document that someone else can read and understand without staring at the disassembly.

That last step is where most reverse engineers fall short. They understand the binary in their head, but they never externalize that understanding into a shareable artifact. Writing the beat sheet forces you to confront the gaps in your understanding. If you can’t explain a state transition in plain language, you don’t really understand it.

For the DevLink parser, my beat sheet looked like this:

State: WAIT_MAGIC
  On recv 4 bytes == 0x4C4B5644 ("DVKL"): -> WAIT_CHALLENGE_RESPONSE
  On recv anything else: -> ERROR (log, close connection)

State: WAIT_CHALLENGE_RESPONSE
  On recv 8 bytes: validate response, -> COMMAND_LOOP or ERROR
  On recv TLV message: -> COMMAND_LOOP (BUG: skips authentication)
  On timeout: -> ERROR (log, close connection)

State: COMMAND_LOOP
  On recv TLV message: dispatch to handler by type
  On recv 0x17 (file transfer): handler dereferences uninitialized fd pointer
  On recv 0xFF (keepalive): reset watchdog, stay in COMMAND_LOOP
  On connection close: -> CLEANUP

This beat sheet made the vulnerability obvious: the transition from WAIT_CHALLENGE_RESPONSE to COMMAND_LOOP on receiving a TLV message bypassed the authentication check. And once in COMMAND_LOOP, the type 0x17 handler dereferenced an uninitialized pointer. The fix was to add a state check at the top of the TLV dispatch: if the session isn’t authenticated, drop the message.

Tooling the Narrative Workflow

The tools I used for this analysis were Ghidra, QEMU, Python, and a lot of patience. But the most important tool was the narrative framework itself. By treating the binary as a story with characters, conflicts, and climaxes, I was able to structure my analysis in a way that made the vulnerability surface naturally.

Ghidra’s scripting API was essential for extracting the control flow graph and the conditional branches. I wrote a script that walked the function’s basic blocks, identified the conditional jumps, and output the conditions and targets. The script is less than 200 lines of Python and is reusable for any binary. The key insight was that Ghidra’s decompiler can produce a high-level representation of each branch condition, which is much easier to analyze than raw assembly.

QEMU’s tracing was essential for the dynamic analysis. I used the -trace option with a custom event filter to log only the basic blocks in the parser function. The trace output was a text file with one line per basic block, containing the block’s address and the values of key registers. I wrote a Python script to diff two trace files and highlight the divergence points. The script was crude but effective: it aligned the traces by address and flagged the first block where the addresses differed.

For documenting the beat sheet, I used a plain text format that I could version-control alongside the Ghidra database. The format was simple: each state was a heading, and each transition was a bullet point with the condition and the target state. I added comments for the vulnerabilities and the missing checks. The result was a document that the client’s engineering team could read and act on without needing to understand the disassembly.

When you need to structure complex technical findings into a coherent narrative that non-specialists can follow, tools designed for narrative organization become surprisingly relevant. Writers have been solving the problem of structuring complex plots for centuries, and the techniques they’ve developed—beat sheets, character profiles, conflict mapping—map directly onto reverse engineering. An Unsloppy plot generator that structures narrative elements can serve as a conceptual model for how to organize your reverse engineering findings, even if the tool itself is designed for creative writing. The principle is the same: identify the elements, map the relationships, and document the transitions.

Why This Skill Matters

The difference between a surface-level reverse engineer and a deep one isn’t tool knowledge or assembly fluency. It’s the ability to reconstruct the logic of a binary—to understand not just what it does, but why it does it, and where the assumptions break. That skill is what separates someone who can find a buffer overflow from someone who can find a state machine bug that only triggers after a specific sequence of 15 messages.

State machine bugs are the hardest to find and the most valuable to exploit. They don’t show up in fuzzer output unless the fuzzer understands the protocol. They don’t show up in static analysis unless the analyst has reconstructed the state machine. And they often survive code reviews because the reviewer is looking at individual functions, not at the global flow of control.

The narrative approach forces you to look at the global flow. By treating the binary as a plot, you’re forced to ask questions that don’t arise in a function-by-function review: What are the characters? What do they want? What’s preventing them from getting it? Where do the conflicts peak? Those questions lead directly to the vulnerabilities.

Practical Takeaways

If you want to apply this approach to your own reverse engineering work, here are the concrete steps:

  • Start with the data structures. Before you try to understand the code, understand the data. What structs does the binary allocate? What fields do they contain? What are the valid states for each field? Ghidra’s struct editor is your friend here—use it aggressively.
  • Extract the control flow. Write a script that walks the parser’s main loop and extracts every conditional branch. Don’t try to do this by hand—you’ll miss branches, and you’ll go insane. A 200-line Python script will save you days of work.
  • Diff the traces. Dynamic trace diffing is the fastest way to find the state transitions that matter. Vary one byte of input at a time and watch where the execution path changes. The divergence points are your climaxes.
  • Write the beat sheet. Externalize your understanding into a document that someone else can read. If you can’t explain a state transition in plain language, you don’t understand it. The act of writing will reveal the gaps in your analysis.
  • Look for the missing checks. The most interesting vulnerabilities are the places where the parser assumes a condition holds without verifying it. Those assumptions are the conflicts in your narrative. Find them, and you’ll find the bugs.

The DevLink parser had a classic missing check: it assumed that TLV messages only arrived after authentication, but the protocol allowed them during the handshake. That assumption created a state transition that bypassed authentication and led to a function pointer dereference from an uninitialized field. The fix was a single branch instruction. The analysis took two weeks. The narrative approach made it possible.

Next time you’re staring at a stripped binary and feeling lost, try plotting it. Identify the characters, map the conflicts, find the climaxes. The story the binary is telling will surface, and with it, the vulnerabilities.

Static Analysis Arsenal: Tools for Dissecting Binaries Without Execution

There’s a quiet, almost meditative side to reverse engineering that doesn’t involve debuggers, breakpoints, or live memory snapshots. It’s the craft of taking apart a binary while it sits lifeless on your drive—no execution, no sandbox, just raw bytes and the structures they encode. Static binary analysis is the bedrock of vulnerability research, malware triage, and understanding proprietary software. The right tools turn a hex dump into a story. Here’s a look at the instruments that make that possible, from disassemblers to binary ninjas (the concept, not just the product).

Disassemblers: The Core of the Craft

Disassembly is your first real step into a binary’s logic. A disassembler translates machine code back into assembly, giving you a human-readable (well, mostly) view of the processor’s instructions. The real test of a disassembler is how well it separates code from data, follows control flow, and resolves indirect calls. A sloppy disassembler will send you down rabbit holes of misinterpreted bytes. A sharp one becomes an extension of your own analytical mind.

IDA Pro still sets the standard, especially for its interactive features. Its graph view transforms spaghetti code into a visual map of basic blocks, and its Python scripting (IDAPython) lets you automate the grunt work. The Hex-Rays decompiler plugin, while not purely static, offers a pseudo-C view that can dramatically speed up understanding of complex functions. For those who can’t justify the price tag, Ghidra—released by the NSA—has grown into a formidable alternative. Its collaborative features and built-in decompiler for multiple architectures make it a powerhouse, particularly for firmware analysis. radare2 (and its GUI frontend, Cutter) is the command-line junkie’s dream. It’s scriptable, lightweight, and handles obscure file formats that choke other tools. Its visual mode and deep binary patching capabilities are unmatched for quick, precise work.

Close-up of a computer screen displaying hexadecimal code and disassembly output

Hex Editors: Where the Bytes Hit the Screen

Sometimes you need to see the forest and the trees. A hex editor shows the raw binary content alongside its ASCII (or Unicode) interpretation, and the best ones understand file structures. 010 Editor shines here with its Binary Templates feature. You can parse a PE, ELF, or Mach-O header with a single click, seeing each field labeled and interpreted. This is invaluable for spotting malformed headers, hidden data in slack space, or manually reconstructing a corrupted file. HxD is a fast, no-nonsense Windows hex editor with disk editing and RAM inspection—handy for grabbing a process’s memory snapshot for offline analysis. On Linux, wxHexEditor offers similar low-level access. The key is finding one that doesn’t choke on massive files, like multi-gigabyte firmware dumps, and gives you unfiltered access to every byte.

File Format Parsers and Structure Analyzers

Before you dive into disassembly, you need to know what you’re looking at. The file command on Unix-like systems is the first line of defense, but it relies on magic bytes and can be fooled. TrID goes deeper, using a database of file signatures to identify thousands of formats. For PE files, PE-bear provides a clean, modern interface for exploring headers, sections, imports, exports, and resources. It highlights anomalies like suspicious section permissions (writable and executable) or unusual entry points. On Linux, readelf and objdump (from binutils) are indispensable for ELF files, dumping symbol tables, dynamic linking information, and section headers. For Mach-O files (macOS/iOS), MachOView gives a detailed tree view of the binary’s structure, including load commands and encrypted segments.

Abstract representation of binary code and data structures

Signature Scanning and Pattern Matching

When you’re hunting for known code patterns—a specific crypto implementation, a malware family trait, a vulnerable library version—signature tools are your best friend. YARA has become the de facto standard for writing and applying pattern-matching rules against files and memory. Its rule syntax is expressive enough to match byte sequences, strings, and even regular expressions at specific offsets. For large-scale binary triage, ClamAV’s signature database and engine can be repurposed, though it’s less flexible than YARA for custom research. A lesser-known but powerful tool is FLOSS (from FireEye’s Mandiant), which statically extracts obfuscated strings from malware binaries—it automates what used to be a manual, tedious process of decoding stack strings and other obfuscation tricks.

Binary Diffing: Spotting the Changes

When a vendor releases a patch, the security implications often hide in the differences between the old and new binaries. Binary diffing tools compare two versions of a file and highlight what changed. BinDiff (now free, from Google) is the classic choice, integrating with IDA to show matched functions and basic blocks, with color-coded similarity scores. It’s essential for patch analysis—identifying which vulnerabilities were fixed without any public disclosure. Diaphora is a powerful open-source alternative that works as an IDA plugin, offering multiple diffing algorithms and a focus on portability across architectures. For quick, text-based comparisons, radiff2 (part of radare2) can show delta differences at the byte and instruction level.

Static Unpacking and Deobfuscation

Packers and obfuscators are the bane of static analysis. They compress or encrypt the real code, leaving only a stub that unpacks at runtime. While dynamic analysis is often needed to fully unpack a binary, several static techniques can peel back layers. UPX (Ultimate Packer for eXecutables) can decompress its own format, and many others, with the -d flag. For custom packers, binwalk is a static analysis swiss army knife: it scans a binary for embedded file signatures and can extract them. This is particularly useful for firmware images that contain multiple filesystems or compressed kernels. XORSearch and bruteforce-salted-openssl help identify and crack simple XOR-based obfuscation or weak encryption of embedded resources.

Digital representation of data extraction and decompression processes

Specialized Analysis Frameworks

Some tools transcend single categories. Binary Ninja (the platform) has gained a cult following for its clean API and intermediate language (IL) design. Its static analysis engine lifts assembly to a medium-level IL, then to a high-level IL, enabling architecture-agnostic analysis. You can write plugins that operate on the IL without caring whether the original binary was x86, ARM, or MIPS. angr is a Python framework that takes static analysis to the extreme: it lifts binaries to an intermediate representation (VEX, via Valgrind) and performs symbolic execution and control-flow graph recovery. While often used for dynamic symbolic execution, its static analysis components—like CFGFast—can recover control flow from stripped binaries with impressive accuracy. BAP (Binary Analysis Platform) is another heavyweight, used in academic and DARPA-funded research, that provides a formal verification layer on top of disassembly.

String Analysis and Metadata Extraction

Never underestimate the power of strings. The classic Unix utility dumps all printable character sequences in a file, often revealing hardcoded URLs, IP addresses, registry keys, and error messages. For a more structured approach, flarestrings (from FireEye’s FLARE team) enhances string extraction with Unicode support and filtering. ExifTool is indispensable for pulling metadata from binaries—compilation timestamps, linker versions, and even debug paths that leak the developer’s username or build environment. These small details can pivot an investigation or provide the context needed to understand a binary’s origin.

FAQ

What’s the difference between static and dynamic analysis?

Static analysis examines a binary without executing it—you’re looking at the code, data, and structure as they exist on disk. Dynamic analysis runs the program in a controlled environment (like a debugger or sandbox) to observe its behavior. Static analysis is safer for malware, but can be thwarted by obfuscation and packing. Dynamic analysis reveals runtime behavior but risks detection or unintended consequences. Most serious reverse engineering combines both.

Do I need to know assembly language for static analysis?

Yes, at least a working knowledge. Disassemblers output assembly, and while decompilers can give you pseudo-C, they’re often wrong or incomplete. Understanding the instruction set of your target architecture (x86/x64, ARM, MIPS) is essential to spot the decompiler’s mistakes and to recognize low-level patterns like system calls, cryptographic primitives, or anti-analysis tricks.

Which tool should a beginner start with?

Ghidra offers the best combination of power and price (free). Its decompiler is excellent, and the UI is approachable. Start with simple crackmes or capture-the-flag challenges to learn the workflow. As you grow comfortable, explore IDA’s free version or radare2 for more specialized tasks. The key is to stick with one tool long enough to internalize its shortcuts and scripting API before jumping around.

How do I handle stripped binaries?

Stripped binaries lack symbol information, making function identification harder. Tools like IDA’s FLIRT (Fast Library Identification and Recognition Technology) can match code patterns against known libraries to restore function names. Ghidra’s function ID does the same. For custom code, focus on identifying the entry point, then trace cross-references from API calls. Signature tools like YARA can also help label known code snippets.

Static analysis is a discipline that rewards patience and pattern recognition. The tools listed here are the ones that have proven themselves in the trenches—each with its own learning curve, but each capable of revealing the secrets locked inside a binary. The best way to master them is to pick a target, any target, and start peeling back the layers.

Static Binary Analysis: The Underground Toolkit for Reverse Engineers

Static binary analysis is the dark art of pulling apart compiled code without ever letting it run. For reverse engineers, malware analysts, and vuln researchers, it’s the first line of defense—and the first step on offense. You’re not just staring at hex dumps; you’re reconstructing logic, sniffing out backdoors, and mapping control flow from a dead file. The right tools turn a weekend lost in IDA’s graph view into a clean hit on an obfuscated payload. This isn’t a glossy product roundup. It’s a field guide to the tools that actually ship results when you’re buried in ELF headers, PE sections, or raw firmware blobs.

Close-up of a computer screen displaying hexadecimal code and disassembly output

Why Static Analysis Still Rules the Underground

Dynamic analysis gets the hype—sandboxes, debuggers, fuzzers spinning up VMs. But static analysis is where you build the mental model. No kernel hooks, no anti-debug traps, no network noise. You’re working with the artifact itself: a binary that might be packed, stripped, or laced with anti-reversing tricks. The goal is to extract symbols, map imports, identify cryptographic constants, and trace execution paths without ever hitting F9. In the underground, where targets range from IoT malware to proprietary firmware blobs, static analysis is often the only option. The hardware isn’t available, the OS is obscure, or the sample self-destructs on launch. Your toolkit needs to handle that reality.

Good static analysis tools share a few traits: they parse file formats deeply, they surface anomalies without choking on malformed headers, and they give you a disassembly or intermediate representation you can actually work with. The best ones let you script your own analysis passes. Below, I’ll walk through the core categories—disassemblers, binary viewers, signature scanners, and specialized utilities—and name the specific tools that earn their keep in the trenches.

Disassemblers and Reverse Engineering Frameworks

IDA Pro: The Heavyweight

No list starts without IDA. The Interactive Disassembler from Hex-Rays is the industry anchor for a reason. Its recursive descent disassembly handles x86, ARM, MIPS, PowerPC, and dozens of other architectures. The real power isn’t just the disassembly listing—it’s the database. IDA builds a cross-reference graph, tracks function boundaries, and lets you annotate and rename everything. The Hex-Rays decompiler plugin turns assembly back into pseudo-C, which is indispensable when you’re staring at a 2MB stripped binary at 3 a.m. The SDK (C++ and Python) means you can write custom loaders for obscure firmware formats or automate control-flow deobfuscation. Yes, it’s expensive. The freeware version is deliberately crippled—no x64, no decompiler, limited scripting. But if you’re serious, you find a way. The underground runs on IDA.

Ghidra: The Open-Source Disruptor

When the NSA dropped Ghidra in 2019, it reshaped the landscape. A full reverse-engineering suite with a decompiler, cross-references, graphing, and collaborative server mode—all free. Ghidra’s decompiler is shockingly good, often producing cleaner output than Hex-Rays for certain code patterns. Its scriptability in Java and Python (via Jython) is deep, and the plugin ecosystem has exploded. For static analysis of malware families, Ghidra’s version tracking and function-hashing features let you diff binaries and identify reused code across samples. The learning curve is real—the UI is idiosyncratic, and the project model takes getting used to—but once you’re fluent, it’s a primary weapon. Many shops now run IDA and Ghidra side by side, cross-checking decompiler output.

Radare2 / Rizin: The Terminal Powerhouse

If you live in the command line, radare2 (and its modern fork Rizin) is your scalpel. It’s a framework more than a single tool: disassembler, hex editor, binary diffing, emulation, and scripting all in one. The learning curve is brutal—the command syntax is terse and non-obvious—but once you internalize it, you move at the speed of thought. Radare2 excels at rapid triage: identify file type, list imports, find strings, dump sections, all in a few keystrokes. Its visual mode (V) gives you a terminal-based graph view that’s surprisingly usable. For embedded firmware with custom architectures, radare2’s plugin system lets you define new CPU profiles quickly. It’s the tool you reach for when you need to script analysis across hundreds of samples.

Binary Ninja: The Middle Ground

Binary Ninja sits between IDA’s polish and Ghidra’s price tag. It’s commercial but affordable, with a clean UI and a powerful Python API. The decompiler is solid, though not as mature as Hex-Rays or Ghidra. Where Binary Ninja shines is in its intermediate language (IL) system: low-level IL, medium-level IL, and high-level IL. You can write analysis passes that operate on any of these representations, making it easier to build architecture-agnostic tools. The type recovery and data-flow analysis are well-integrated. For vulnerability research, the ability to lift binary code to an IL and then reason about it programmatically is a force multiplier. The community is smaller but active, and the developers ship updates frequently.

Multiple monitors displaying code analysis and debugging interfaces in a dark room

Binary Inspection and Hex Editing

010 Editor: Templates and Binary Parsing

Sometimes you don’t need a disassembler—you need to see the raw structure. 010 Editor is a hex editor with a killer feature: binary templates. These are declarative scripts that parse file formats and display fields in a structured tree view. You can write templates for PE, ELF, Mach-O, or any custom format. When you’re reversing an undocumented firmware image, a template lets you isolate headers, checksums, and payload offsets without manually counting bytes. The integrated scripting engine (C-like syntax) can modify files in place, recalculate CRCs, or extract embedded blobs. It’s an essential companion for static analysis of file formats themselves.

ImHex: The Newcomer with Pattern Language

ImHex is a modern, open-source hex editor that’s rapidly gaining traction. Its pattern language is more expressive than 010 Editor’s templates, supporting complex parsing, highlighting, and data transformation. It includes a disassembler, a data inspector, and a diffing mode. For static analysis of binary protocols or file formats, ImHex’s ability to visually annotate hex dumps with parsed fields is unmatched. It’s also cross-platform and actively developed. If you’re doing a lot of manual binary inspection, this tool deserves a spot in your arsenal.

Signature Scanning and Identification

YARA: Pattern Matching for Binaries

YARA is the lingua franca of malware classification. You write rules—text or binary patterns combined with Boolean logic—and YARA scans files for matches. It’s not just for malware; you can use YARA to identify specific libraries, compilers, or known-vulnerable code snippets in any binary. The rule syntax is simple but expressive, and the engine is fast enough to scan thousands of files. For static analysis workflows, YARA is the triage step: before you open IDA, you run a YARA scan to see if the sample matches known families. Writing good YARA rules is an art—too specific and you miss variants, too broad and you drown in false positives. The best rules target unique byte sequences in core functions, not just strings.

BinDiff: Binary Comparison

When you have two versions of a binary—say, a patched and unpatched firmware—BinDiff (now integrated into Ghidra as BinExport/BinDiff) identifies changed functions. It uses graph isomorphism algorithms to match functions across binaries, then highlights structural differences. This is invaluable for patch analysis: you can pinpoint exactly which functions were modified to fix a vulnerability, then reverse only those. BinDiff works best with IDA databases, but the Ghidra integration is improving. For static vulnerability discovery, patch diffing is one of the most efficient techniques.

Specialized Utilities for Deep Static Analysis

Pyew: Python-Based Hex Analysis

Pyew is a lesser-known but powerful tool for static malware analysis. It’s a Python-based hex editor and disassembler that supports scripting for automated analysis. Pyew can parse PE and ELF structures, display disassembly, and let you write custom analysis scripts in Python. It’s particularly useful for analyzing shellcode—you can load a raw binary blob, set the base address, and start disassembling immediately. The tool is lightweight and doesn’t require a heavy GUI, making it ideal for headless analysis pipelines.

FLOSS: String Extraction on Steroids

Standard strings output is noisy and misses obfuscated data. FLOSS (FireEye Labs Obfuscated String Solver) uses static analysis to extract strings that are constructed at runtime—decoded, deobfuscated, or built on the stack. It emulates small portions of the binary to resolve string-building routines, then dumps the results. For malware analysis, this surfaces C2 addresses, registry keys, and mutex names that would otherwise remain hidden. It’s a critical first-pass tool before you even open a disassembler.

angr: Binary Analysis Framework

angr is a Python framework for analyzing binaries. It lifts code into an intermediate representation (VEX, borrowed from Valgrind) and provides symbolic execution, control-flow graph recovery, and data-flow analysis. For static analysis, angr’s CFGFast can recover control flow from stripped binaries with high accuracy. Its backward slicing lets you trace data dependencies from a point of interest back to their origins. angr is complex and resource-intensive, but for deep static analysis—like finding the inputs that reach a vulnerable function—it’s unmatched. It’s more a research tool than a daily driver, but when you need it, nothing else comes close.

Abstract visualization of binary code with glowing nodes and connections

Building a Static Analysis Workflow

Tools are only as good as the process they fit into. A typical static analysis workflow for an unknown binary looks like this:

1. Triage: Run file, check entropy, scan with YARA rules. Use FLOSS to pull obfuscated strings. This gives you a high-level classification—packed? Known family? Interesting strings?

2. Structural Analysis: Open in 010 Editor or ImHex to inspect headers, sections, and any embedded resources. If the binary is packed, this is where you identify the packer and locate the OEP (original entry point).

3. Disassembly: Load into IDA, Ghidra, or radare2. Let the auto-analysis run. Identify the main function, imports, and any anti-analysis tricks. Rename functions and annotate as you go.

4. Deep Dive: For critical functions, use decompiler output to understand logic. If the code is obfuscated, write IDAPython or Ghidra scripts to deobfuscate control flow or decrypt strings. Use BinDiff if you have a related sample.

5. Reporting: Extract IOCs (indicators of compromise), document functionality, and map the binary’s capabilities. YARA rules written during analysis feed back into the triage step for future samples.

This isn’t a linear process—you’ll jump between steps as you discover new leads. The key is having tools that don’t get in your way when you need to pivot quickly.

FAQ

What’s the difference between static and dynamic binary analysis?

Static analysis examines a binary without executing it—you’re reading disassembly, parsing headers, and extracting strings. Dynamic analysis runs the binary in a controlled environment (debugger, sandbox) to observe its behavior. Static analysis is safer (no risk of detonation) and often the only option for non-executable firmware or exotic architectures. Dynamic analysis reveals runtime behavior like network connections and process injection. The two are complementary; most serious reverse engineers combine them.

Do I need to pay for IDA Pro, or can I use free tools?

Free tools like Ghidra and radare2 are capable enough for many tasks, especially malware triage and standard reverse engineering. IDA Pro’s advantages—mature decompiler, extensive processor support, and a vast plugin ecosystem—become critical for complex, obfuscated, or unusual binaries. Many professionals use both: Ghidra for collaboration and scripting, IDA for deep dives. The freeware version of IDA is limited to 32-bit x86 and lacks a decompiler, so it’s not a full replacement.

How do I handle packed or obfuscated binaries statically?

Start with entropy analysis to detect packing. Tools like FLOSS can extract obfuscated strings without unpacking. For manual unpacking, use a hex editor to locate the OEP and dump the unpacked payload, then load that into your disassembler. Some packers can be defeated with static unpacking scripts (e.g., in IDAPython). If static unpacking fails, you’ll need dynamic analysis to let the binary unpack itself in memory, then dump the process. The line between static and dynamic blurs here—many analysts use a hybrid approach.

What’s the best tool for analyzing firmware images?

It depends on the firmware format. For raw dumps, start with binwalk to identify embedded filesystems and compression. Then use a hex editor with custom templates to parse headers. For disassembly, Ghidra’s support for obscure architectures (via SLEIGH) is excellent—you can define a new processor specification if needed. IDA also supports many embedded architectures. The key is identifying the base address and loading the binary at the correct offset so cross-references resolve properly.

The Best Tools for Static Binary Analysis: A Hacker’s Field Guide

Static binary analysis is the dark art of dissecting compiled code without ever letting it run. For reverse engineers, vulnerability researchers, and low-level tinkerers, it’s the first line of reconnaissance—pulling apart ELF headers, sniffing out suspicious imports, and mapping control flow before a single instruction hits the CPU. The right tools make the difference between a clean exploit chain and a week of staring at hex dumps. Here’s a rundown of the best gear for the job, from disassemblers to diffing engines, all battle-tested in the trenches of binary spelunking.

Disassemblers: The Core of the Toolkit

If you’re doing static analysis, you live inside a disassembler. It’s your primary lens into the binary’s soul, translating raw opcodes into something a human can reason about. The landscape is dominated by a few heavy hitters, each with its own flavor of power and pain.

IDA Pro

IDA Pro is the undisputed heavyweight. Its interactive, recursive descent disassembly engine handles everything from x86 to ARM to exotic embedded architectures. The graph view alone is worth the license cost—seeing basic blocks laid out visually makes spotting loops, conditionals, and weird control flow almost intuitive. IDA’s Python scripting layer, IDAPython, lets you automate annotation, rename functions in bulk, or hunt for known byte patterns across massive firmware dumps. The decompiler plugins (Hex-Rays for x86/x64) push it into pseudocode territory, but even without them, IDA’s cross-references and FLIRT signature recognition turn a raw binary into a navigable map. The downside? It’s expensive, and the learning curve is steep enough to break your ankles. Still, for serious work, nothing else matches its depth.

Ghidra

Ghidra came out of the NSA’s vaults and flipped the table. It’s free, open-source, and packs a decompiler that rivals Hex-Rays for many architectures. The collaborative server mode lets teams work on the same binary simultaneously—a feature IDA only recently started catching up on. Ghidra’s scripting is Java-based, which feels clunky compared to Python, but the API is extensive. Its real strength is in handling malformed or obfuscated binaries; the disassembler is aggressive about making sense of garbage bytes, sometimes to a fault. The UI is a bit sluggish on large files, and the analysis can be memory-hungry, but for zero cost, it’s a beast. I’ve used it to tear apart router firmware and found its function identification to be surprisingly accurate even without symbols.

Close-up of a computer screen showing hexadecimal code and disassembly output

Radare2 / Rizin

For the terminal-dwelling purists, Radare2 (and its community fork Rizin) is the scalpel to IDA’s sledgehammer. It’s a command-line framework that can disassemble, analyze, patch, and debug binaries across dozens of architectures. The learning curve is a vertical cliff—memorizing commands like aaa for auto-analysis or afl to list functions becomes muscle memory after a few weeks of pain. But once you’re fluent, the speed is unmatched. Radare2’s scripting in r2pipe lets you drive it from Python, Ruby, or anything with pipes, making it ideal for automated triage of malware samples. The visual mode (V!) gives you a graph view that’s surprisingly usable. It’s not a replacement for IDA or Ghidra in complex, interactive sessions, but for quick hits and headless analysis, it’s indispensable.

Binary Diffing: Spotting the Changes

When you’re comparing two builds of the same firmware—say, a patched and unpatched version—binary diffing tools highlight exactly what changed. This is critical for zero-day hunting: find the fix, and you’ve found the vulnerability.

BinDiff

BinDiff (now free, bundled with Ghidra) is the standard. It works on IDA databases or Ghidra projects, matching functions across binaries using call graphs, basic block hashes, and string references. The visual diff view overlays two control flow graphs, coloring nodes that were added, removed, or modified. When a vendor silently patches a bug, BinDiff is how you reverse-engineer the patch and weaponize it. It’s not perfect—heavily optimized or obfuscated code can confuse the matching algorithms—but it’s the best we’ve got.

Diaphora

Diaphora is an IDA plugin that does binary diffing with a different philosophy. It uses multiple heuristics—mnemonic sequences, graph isomorphism, immediate values—and lets you weight them. This makes it more flexible for binaries where BinDiff’s assumptions break down, like those compiled with link-time optimization or custom calling conventions. Diaphora’s output is a SQLite database you can query directly, which is a godsend for scripting custom analysis pipelines. It’s slower than BinDiff but often more precise on tricky targets.

Format Parsers and Structural Analyzers

Before you even disassemble, you need to understand the binary’s container. ELF, PE, Mach-O—each has its own quirks, and misparsing them leads to wrong load addresses and broken cross-references.

readelf / objdump

These GNU binutils stalwarts are the first thing I run on any unknown sample. readelf -a dumps the full ELF structure: sections, segments, dynamic entries, notes, and symbol tables. It’s the ground truth for how the binary is laid out in memory. objdump -d gives a linear disassembly, which is crude but useful for spotting shellcode or weird instruction sequences that interactive disassemblers might misinterpret. They’re not flashy, but they’re reliable and available everywhere.

LIEF

LIEF (Library to Instrument Executable Formats) is a programmatic swiss army knife for parsing and modifying PE, ELF, and Mach-O files. You can use it to extract sections, add imports, or even inject code into a binary without breaking its structure. For static analysis, it’s invaluable for scripting bulk extraction of metadata—think pulling all exported function names from a folder of DLLs or checking entropy of sections to spot packed malware. The Python API is clean and well-documented, making it a staple in any automated analysis pipeline.

Abstract representation of binary code and data structures in a digital space

Signature and Pattern Matching

Sometimes you don’t need to understand every instruction—you just need to know if a binary contains a known library, a specific vulnerability, or a chunk of borrowed code.

FLIRT (Fast Library Identification and Recognition Technology)

Built into IDA, FLIRT uses byte-pattern signatures to identify standard library functions in statically linked binaries. This is a massive time-saver: instead of reverse-engineering printf from scratch, IDA just labels it. The signature database is extensive, and you can generate your own .sig files for custom libraries. For embedded firmware analysis, where static linking is common, FLIRT is often the difference between a readable disassembly and a sea of unnamed subroutines.

YARA

YARA is the go-to for pattern-based binary classification. You write rules that match byte sequences, strings, or even regex patterns at specific offsets, and YARA scans files or memory dumps to flag hits. It’s used heavily in malware research—write a rule for a particular packer stub or crypto constant, and you can triage thousands of samples in minutes. For static analysis, YARA helps you quickly identify known code or data patterns before you dive into manual reversing. The rule syntax is simple but expressive, and the engine is fast enough to run on large corpora.

Control Flow and Decompilation

Understanding a binary’s logic often means reconstructing high-level control structures from assembly. Decompilers and CFG recovery tools bridge that gap.

Hex-Rays Decompiler

IDA’s decompiler plugin is the gold standard for turning x86/x64 and ARM assembly into C-like pseudocode. It’s not perfect—inlined functions, heavy optimizations, and obfuscation can produce spaghetti—but it’s remarkably good at recovering loops, conditionals, and variable types. The interactive mode lets you rename variables and retype function arguments, and the decompiler updates in real time. For vulnerability research, being able to read a function’s logic in pseudocode instead of raw assembly is a massive cognitive speedup. The main drawback is cost: it’s a pricey add-on to an already expensive IDA license.

Ghidra’s Decompiler

Ghidra’s built-in decompiler is free and supports a wider range of architectures than Hex-Rays, including PowerPC, MIPS, and SPARC. The output is comparable in quality, though it sometimes struggles with complex data type recovery. One advantage is that Ghidra’s decompiler is tightly integrated with its disassembly and patching features—you can modify the decompiled code and push changes back to the binary. For most static analysis tasks, it’s more than sufficient, and the price tag (zero) makes it the default choice for many independent researchers.

Specialized Utilities for Deep Dives

Beyond the big platforms, a handful of smaller tools solve specific pain points in static analysis. These are the ones you reach for when the mainstream options fall short.

Capstone

Capstone is a lightweight, multi-architecture disassembly framework. It’s not a full analysis environment—it’s a library you embed in your own tools. Need to write a custom unpacker that disassembles instructions one at a time? Capstone. Want to build a gadget finder for ROP chain construction? Capstone. It supports x86, ARM, MIPS, PowerPC, and more, with clean bindings for Python, C, and other languages. The API is straightforward: feed it bytes, get back decoded instructions with detailed operand info. For any project where you need programmatic disassembly without the overhead of a full GUI, Capstone is the answer.

angr

angr is a binary analysis framework that does symbolic execution and control flow recovery. It’s not a tool you use interactively—it’s a Python library for building custom analysis scripts. With angr, you can statically explore all possible execution paths through a function, solve for inputs that reach a specific address, or automatically deobfuscate control flow flattened binaries. The learning curve is brutal, and it’s overkill for simple tasks, but when you’re dealing with heavily obfuscated code or need to find a magic value that passes a complex check, angr is the nuclear option. It’s used in CTF competitions and by professional vulnerability researchers to automate what would otherwise be days of manual work.

Digital representation of a binary analysis workflow with code and graphs

Putting It All Together: A Typical Workflow

Static analysis isn’t about using one tool—it’s about chaining them. Here’s a realistic flow for a firmware reverse-engineering session:

  1. Triage with readelf and YARA. Dump the ELF headers to understand the binary’s architecture and entry point. Run YARA rules to check for known libraries, packers, or crypto signatures.
  2. Load into Ghidra or IDA. Let the auto-analysis run—FLIRT or Ghidra’s function ID will label known code. Skim the imports and strings to get a high-level sense of the binary’s capabilities.
  3. Diff if you have a reference. If you’re comparing two firmware versions, run BinDiff or Diaphora to pinpoint changed functions. Focus your manual analysis there.
  4. Deep dive with decompiler. Use Hex-Rays or Ghidra’s decompiler to understand complex functions. Rename variables, add comments, and map out data structures.
  5. Script the boring parts. Use IDAPython, Ghidra scripts, or Radare2’s r2pipe to automate repetitive tasks—like extracting all strings that look like debug messages or finding every function that calls a specific import.
  6. Bring in angr for hard problems. If you hit obfuscated control flow or need to solve for a specific state, write an angr script to do the heavy lifting.

FAQ

What’s the difference between static and dynamic binary analysis?

Static analysis examines a binary without executing it—you’re looking at the code, data, and structure as they exist on disk. Dynamic analysis runs the binary in a controlled environment (like a debugger or sandbox) to observe its behavior. Static analysis is safer for malware and gives you a complete view of all code paths, but it can’t reveal runtime-decrypted strings or unpacked code. Most serious reverse-engineering workflows combine both.

Do I need to buy IDA Pro, or is Ghidra enough?

For most tasks, Ghidra is more than capable—its decompiler, scripting, and collaboration features rival IDA’s. IDA Pro still has an edge in interactive analysis speed, plugin ecosystem maturity, and support for obscure architectures. If you’re doing professional vulnerability research on x86/x64 targets and can afford the license, IDA is worth it. If you’re learning, working on a budget, or analyzing non-x86 firmware, Ghidra is the clear choice.

How do I handle obfuscated or packed binaries statically?

First, use format parsers like LIEF or readelf to check for abnormal section entropy—packed binaries often have high-entropy sections. YARA rules can identify known packers. For unpacking, you might need to script a static unpacker using Capstone to emulate the unpacking stub, or use a tool like angr to symbolically execute the unpacking routine. Sometimes, you’ll have to resort to dynamic analysis to dump the unpacked code from memory, then switch back to static tools for the real analysis.

What’s the best way to learn these tools?

Start with small, open-source binaries compiled without optimizations. Use Ghidra or Radare2 to disassemble them, and compare the output to the original source code. Capture The Flag (CTF) challenges focused on reverse engineering are excellent practice—they force you to use diffing, scripting, and decompilation under time pressure. Build a lab with firmware images from your router or IoT devices, and try to find the update verification routines. The tools are deep, but you don’t need to master every feature at once.

Can static analysis find all vulnerabilities?

No. Static analysis excels at finding certain bug classes—buffer overflows from unsafe functions, format string vulnerabilities, hardcoded credentials, and missing bounds checks. But it struggles with logic flaws that depend on runtime state, race conditions, or vulnerabilities introduced by compiler optimizations. It’s a powerful filter, but it’s not a substitute for dynamic testing and manual code review.

Static Binary Analysis: Tools That Actually Work in the Trenches

Static binary analysis isn’t a spectator sport. You’re not just reading code—you’re dissecting compiled flesh, hunting for malformations, backdoors, and the subtle fingerprints of a compiler that lied. I’m Zel Mathis, and I’ve spent more nights than I’d like to admit staring at hex dumps and control-flow graphs, trying to figure out what some black-box firmware is really doing. The right tools make the difference between a clean extraction of truth and a week lost in false positives. Here’s what actually works.

Why Static Binary Analysis Matters

When you can’t run the binary—maybe it’s for an embedded device you don’t own, or it’s malware you’re not insane enough to execute—static analysis is your only window. Even when you can run it, dynamic analysis shows you one path. Static analysis shows you all possible paths. That’s the theory, anyway. In practice, you’re wrestling with stripped symbols, obfuscated control flow, and instruction sets that make your eyes bleed. The tools you pick determine whether you’ll find the hidden command-and-control loop or just generate a 200MB PDF of worthless call graphs.

I’ve spent years in the weeds with firmware extraction, reverse engineering, and vulnerability research. The tools below aren’t the shiniest or the most marketed. They’re the ones that don’t crash when you feed them a 40MB monolithic ARM binary, the ones that let you script your way out of a corner, and the ones that actually teach you something about the code you’re dissecting.

Close-up of a circuit board with intricate traces and components

Disassemblers: The Foundation

Without a disassembler, you’re reading raw bytes. That’s a special kind of masochism. A good disassembler doesn’t just translate opcodes—it builds a mental model of the binary’s layout, resolves cross-references, and gives you a canvas for annotation. The two that dominate the underground are IDA Pro and Ghidra, but they serve different masters.

IDA Pro: The Old King

IDA Pro is the standard for a reason. Its interactive interface, mature scripting engine, and vast processor support make it the go-to for professional reverse engineers. The FLIRT signature database can identify library functions in stripped binaries, saving you from reversing memcpy for the hundredth time. The decompiler plugin (Hex-Rays) is expensive but often worth it—it turns assembly into pseudo-C that’s surprisingly readable, especially after you’ve spent a few hours teaching it function prototypes and structure definitions.

But IDA has warts. It’s proprietary, expensive, and its plugin ecosystem is a walled garden. The Python API is powerful but quirky, and you’ll find yourself fighting with type systems when you just want to iterate over basic blocks. For large binaries, the analysis can be slow, and the database files balloon to gigabytes. Still, when I need to deeply understand a binary’s logic, IDA is where I live.

Ghidra: The Open-Source Contender

Ghidra came out of the NSA in 2019 and immediately changed the game. It’s free, open-source, and its decompiler rivals Hex-Rays in many cases—sometimes surpassing it, especially on weird calling conventions or obfuscated code. The collaborative server mode lets multiple analysts work on the same binary simultaneously, which is a godsend for large firmware dumps. Ghidra’s scripting is Java-based, which feels clunky compared to IDA’s Python, but the API is well-designed and you can do serious automation with it.

Where Ghidra really shines is in its analysis of non-standard architectures. I’ve thrown obscure DSP firmware at it and watched it correctly identify function boundaries that IDA missed. The learning curve is steeper—the UI is dense and the terminology is idiosyncratic—but once you internalize its mental model, you can work fast. For anyone starting out or working on a budget, Ghidra is the obvious choice.

Abstract digital binary code streams on a dark background

Binary Ninja: The Middle Ground

Binary Ninja occupies a sweet spot between IDA’s power and Ghidra’s accessibility. It’s commercial but reasonably priced, with a clean, modern UI and a Python API that feels thoughtfully designed rather than bolted on. Its intermediate language (IL) representation is a standout feature—you can write analysis scripts that work across x86, ARM, MIPS, and PowerPC without rewriting architecture-specific logic. The decompiler is good and improving rapidly, and the plugin ecosystem is small but high-quality.

I reach for Binary Ninja when I need to prototype an analysis quickly or when I’m working with a team that needs a gentler learning curve than Ghidra. Its type system and structure recovery are intuitive, and the graph view is the best of any tool I’ve used. The main limitation is architecture support—it covers the majors but lacks the esoteric DSPs and microcontrollers that IDA handles. For mainstream reverse engineering, though, it’s a joy to use.

Specialized Analysis Engines

Disassemblers give you the raw material. Specialized tools help you ask specific questions: Does this binary contain known vulnerable code patterns? What are the actual import dependencies? Where are the crypto constants hiding? These tools don’t replace your brain—they amplify it.

Radare2 / Rizin: The Scriptable Workhorse

Radare2 (and its community fork Rizin) is the command-line powerhouse. It’s not pretty, but it’s fast, scriptable, and can chew through binaries that make IDA choke. I use it for batch analysis—pulling strings, identifying crypto constants, extracting entropy data, and doing initial triage on hundreds of firmware images. The learning curve is a cliff face, but once you’ve internalized the command syntax, you can pipe analysis results into Python or your own toolchain with minimal friction.

Rizin is the cleaner, more modern fork that fixes many of radare2’s historical UI and stability issues. It’s the one I recommend for new users. The rz-ghidra plugin brings Ghidra’s decompiler into Rizin, giving you a powerful free command-line decompiler. For automated binary analysis pipelines, Rizin is hard to beat.

angr: Symbolic Execution for the Brave

angr is a Python framework for binary analysis that includes a symbolic execution engine. It lets you ask questions like “What input reaches this basic block?” or “Is there any path from this function to that sensitive system call?” It’s not a tool you use casually—setting up an angr analysis requires real programming and a deep understanding of the binary’s logic. But when you need to automatically explore state spaces that are too large for manual analysis, angr is unmatched.

I’ve used angr to find authentication bypasses in embedded web servers and to generate inputs that trigger hidden debug modes. It’s slow, memory-hungry, and the learning curve is brutal. But it solves problems that no other tool can touch. If you’re doing vulnerability research on complex binaries, angr belongs in your arsenal.

Digital representation of a lock and security concept

Binary Diffing: Finding the Needle

When a vendor silently patches a vulnerability, you need to know what changed. Binary diffing tools compare two versions of a compiled binary and highlight the differences—new basic blocks, modified functions, changed constants. This is how you find the fix and then reverse-engineer the original bug.

Diaphora: IDA’s Diffing Plugin

Diaphora is a plugin for IDA Pro that performs binary diffing at multiple levels: raw bytes, assembly instructions, basic blocks, and pseudo-code. It’s fast, free, and the output is a searchable database that lets you filter by similarity percentage, function name, or specific instruction patterns. I use it to track changes across firmware versions and to identify borrowed code between different vendors’ products. The pseudo-code diffing is particularly useful—it can spot semantic changes that raw assembly diffing misses.

BinDiff: The Commercial Standard

BinDiff (now owned by Google, distributed as a free IDA plugin) is the other major player. It uses a more sophisticated graph-theoretic approach to function matching, which can be more resilient against compiler changes and optimization differences. BinDiff’s visual diff mode is excellent for quickly understanding how a function’s control flow changed. I tend to use both Diaphora and BinDiff on the same binary pair—they catch different things, and the combination gives a more complete picture.

Unpacking and Deobfuscation

Malware and DRM-protected binaries rarely come in the clear. Packers, cryptors, and obfuscators wrap the real code in layers of garbage. Static analysis of a packed binary is a waste of time—you need to strip the armor first.

Detect It Easy (DIE): Packer Identification

Before you can unpack, you need to know what you’re dealing with. DIE is a signature-based tool that identifies packers, compilers, and cryptors. It’s more comprehensive than the old PEiD and is actively maintained. DIE gives you entropy graphs, section analysis, and heuristic detection that often nails the exact packer version. It’s the first thing I run on an unknown sample.

UnpacMe: Automated Unpacking

UnpacMe is an online service that automates unpacking for many common packers. You upload a sample, it runs it through a gauntlet of unpacking engines, and returns the unpacked binary. It’s not perfect—custom packers and advanced obfuscation will defeat it—but for commodity malware, it saves hours of manual unpacking. The service is free for small files and has an API for batch processing.

String Analysis and Pattern Matching

Strings are the lowest-hanging fruit in static analysis. A binary’s strings can reveal URLs, IP addresses, registry keys, file paths, and even embedded scripts. But standard strings misses Unicode, misses XOR-obfuscated strings, and gives you no context. Better tools exist.

flare-floss: Strings with Brains

FLOSS (FireEye Labs Obfuscated String Solver) is a must-have. It extracts ASCII and Unicode strings like the standard tool, but also statically deobfuscates strings that are built at runtime—XOR loops, stack constructions, and simple decryption routines. It works on Windows, Linux, and macOS binaries. I’ve found C2 addresses and decryption keys that the standard strings command completely missed. FLOSS also provides a “tight strings” score that helps you focus on the most likely interesting strings, filtering out compiler noise.

YARA: Pattern Matching for Binaries

YARA is a rule-based pattern matching engine. You write rules that describe byte sequences, strings, or regular expressions, and YARA scans binaries for matches. It’s the backbone of malware classification and threat intelligence sharing. I use YARA to identify known code families, to find embedded cryptographic constants, and to flag binaries that contain specific vulnerable code patterns. Writing good YARA rules is an art—too specific and you miss variants, too broad and you drown in false positives. But once you have a solid rule set, it’s like having a metal detector in a minefield.

Control Flow and Call Graph Analysis

Understanding how functions relate to each other is essential for reverse engineering. A flat disassembly listing is nearly useless for large binaries—you need to see the call graph, identify clusters of related functions, and spot anomalies like functions that are never called or that call into suspicious APIs.

Gephi + IDA/Ghidra Export: Visualizing the Graph

Both IDA and Ghidra can export call graphs, but their built-in visualization is limited. I export the graph data and load it into Gephi, an open-source graph visualization tool. Gephi lets you apply force-directed layouts, size nodes by centrality, and color by modularity class. This reveals the binary’s architecture at a glance—you can see the main loop, the initialization routines, the network handlers, and any disconnected “islands” of code that might be injected or obfuscated. It’s a technique I picked up from malware analysts and it’s saved me days of manual graph tracing.

FAQ

Which tool should I learn first for static binary analysis?

Start with Ghidra. It’s free, powerful, and has an excellent decompiler. The learning curve is real, but there are plenty of tutorials and the community is active. Once you’re comfortable with Ghidra, you’ll appreciate IDA Pro’s polish and Binary Ninja’s speed, but you won’t be lost without them. If you’re on a budget and need to do real work, Ghidra is the answer.

Can static analysis find all vulnerabilities?

No. Static analysis can find many bug classes—buffer overflows, use-after-free patterns, hardcoded credentials, missing bounds checks—but it can’t find everything. Logic flaws that depend on runtime state, vulnerabilities in dynamically generated code, and issues that only manifest under specific environmental conditions often require dynamic analysis or manual code review. Static analysis is a filter, not a guarantee.

How do I handle heavily obfuscated binaries?

Start with packer identification (DIE) and automated unpacking (UnpacMe). If those fail, you’re in for manual unpacking—find the original entry point, dump the process memory, and rebuild the import table. For code-level obfuscation like control-flow flattening or opaque predicates, Ghidra’s decompiler often cuts through the noise better than IDA’s. In extreme cases, you may need to write custom scripts using angr or a disassembler API to deobfuscate specific patterns. There’s no silver bullet—obfuscation is an arms race.

What’s the best way to analyze firmware blobs?

First, extract the filesystem with binwalk or unblob. Then identify the architecture—DIE can help, or you can look for telltale opcode patterns. Load the main binary into Ghidra or IDA, but be prepared for a stripped binary with no symbols. Use FLOSS to pull strings, YARA to identify known code, and Gephi to map the call graph. Firmware analysis is mostly about persistence and pattern recognition—the tools help, but experience is what gets you through.

The tools I’ve described are my daily drivers. They’re not the only ones, and they’re not always the best for every situation. But they’re the ones that have earned their place on my hard drive through years of actual use. Static binary analysis is a craft—the tools are your instruments, but your brain is the one making the music. Pick tools that respect your intelligence and don’t get in your way.

Static Dissection: The Tools That Expose Binaries Without Execution

Static binary analysis is the dark art of reverse engineering where you never actually run the code. You sit with a dead file, a hex view, and a disassembler, peeling back layers of logic without ever letting the CPU touch an instruction. For malware analysts, vulnerability researchers, and firmware hackers, this is the first line of defense—and the deepest well of insight. But the tools you choose shape everything: your speed, your accuracy, and your sanity.

This isn’t a listicle for beginners who just discovered strings. This is a breakdown of the instruments that serious reversers keep in their toolchains, the ones that handle obfuscated binaries, exotic architectures, and the kind of deep-dive analysis that reveals hardcoded keys, hidden command-and-control domains, and undocumented backdoors.

Disassemblers: The Core of the Craft

Disassembly is the foundation. Without it, you’re staring at a wall of hex hoping for patterns. A proper disassembler translates machine code into assembly language, reconstructing the program’s logic from raw bytes. The quality of that translation—how well it handles stripped binaries, how accurately it identifies function boundaries, how gracefully it deals with anti-disassembly tricks—defines the tool’s worth.

IDA Pro: The Industry Standard

IDA Pro remains the heavyweight champion of interactive disassemblers. Its recursive descent algorithm is unmatched for teasing out code paths in complex binaries. The FLIRT signature system automatically identifies known library functions, saving hours of manual labeling. For static work, IDA’s graph view turns control flow into a visual map that makes even heavily obfuscated routines navigable. The plugin ecosystem—Hex-Rays decompiler, IDAPython scripting, and community extensions—transforms it from a disassembler into a full reverse engineering platform. The freeware version handles x86/x64 adequately, but serious work on ARM, MIPS, or PowerPC demands the paid license.

Where IDA truly shines is in its handling of non-standard binaries. Stripped firmware images, bootloaders with mixed instruction sets, and malware that deliberately breaks disassembly heuristics—IDA’s interactive nature lets you manually define code regions, switch between ARM and Thumb mode mid-function, and annotate everything. The learning curve is steep, but the payoff is absolute control.

Close-up of a computer screen displaying hexadecimal code and disassembly output

Ghidra: The NSA’s Open-Source Powerhouse

Ghidra changed the game when it dropped in 2019. A full-featured reverse engineering framework released by the NSA, it offers a decompiler that rivals Hex-Rays in quality—and it’s completely free. The collaborative features, where multiple analysts can work on the same binary in a shared repository, are something IDA still can’t match natively. Ghidra’s scripting capabilities in Java and Python let you automate repetitive analysis tasks, and its support for obscure architectures (like SuperH, Tricore, and 68K) makes it indispensable for embedded device work.

The decompiler is the star. It produces remarkably readable C-like output, even from heavily optimized code. The data type recovery system is aggressive and usually accurate, though it can stumble on custom structures. For static vulnerability hunting, Ghidra’s built-in search for dangerous functions and its ability to track data flow across function boundaries make it a powerful auditing tool. The interface feels clunky compared to IDA’s polish, but the price tag—zero—makes that easy to forgive.

Radare2 / Rizin: The Terminal Dweller’s Choice

Radare2 and its modern fork Rizin are for those who live in the command line. These tools are absurdly flexible, scriptable, and fast. They handle everything from raw hex editing to full disassembly with control flow graphs, and they do it without a GUI if you don’t want one. The learning curve is brutal—commands are terse, documentation is scattered—but once you internalize the syntax, you can rip through binaries at a speed that GUI tools can’t touch. For automated pipelines, batch analysis of hundreds of samples, or quick triage on a headless server, Radare2 is the scalpel you want.

Rizin, the community-driven fork that emerged from Radare2’s governance issues, has cleaned up the codebase and improved the decompiler integration. It’s worth watching closely. Both tools support an enormous range of architectures and file formats, including raw flash dumps and weird embedded container formats that commercial tools often reject.

Decompilers: From Assembly to Pseudocode

Disassembly gives you the truth, but decompilation gives you understanding. A good decompiler reconstructs high-level constructs—loops, conditionals, variable names—from the low-level assembly, producing something you can read like source code. This is where static analysis accelerates from tedious to surgical.

Hex-Rays Decompiler (IDA Plugin)

Hex-Rays is the gold standard, tightly integrated with IDA Pro. Its output is clean, its type reconstruction is excellent, and it handles compiler optimizations gracefully. The recent versions have added support for C++ constructs, including virtual function tables and exception handling, which were historically pain points. The microcode API lets advanced users modify the decompilation process itself, fixing errors or adding custom analysis passes. It’s expensive, but for professional vulnerability research on complex targets, nothing else comes close.

Ghidra’s Decompiler

As mentioned, Ghidra’s decompiler is shockingly good for a free tool. It often produces output that’s nearly indistinguishable from Hex-Rays, especially on x86/x64 code. It struggles more with ARM Thumb-2 and some DSP instruction sets, but it’s improving rapidly. The ability to retype variables and see the decompiler output update in real time is addictive. For static analysis on a budget, this is the obvious choice.

Multiple monitors showing code analysis and decompilation output in a dark workspace

Binary Parsing and Structure Analysis

Before you even disassemble, you need to understand what you’re looking at. Is this a raw ARM binary or an ELF with stripped sections? Is that blob actually a compressed filesystem? These tools parse binary formats and extract embedded assets, giving you a map before you start reversing.

Binwalk: Firmware Extraction and Analysis

Binwalk is the first tool any firmware analyst reaches for. It scans binary blobs for magic bytes—filesystem headers, compression signatures, kernel images—and can recursively extract them. The -e flag automates extraction, peeling back layers of a firmware image until you’re left with a root filesystem you can browse. It’s not perfect; custom or encrypted filesystems will stump it. But for 90% of consumer router and IoT firmware, Binwalk is the skeleton key.

Kaitai Struct: Declarative Binary Parsing

Kaitai Struct is a different beast. Instead of a tool that parses known formats, it’s a language for describing binary structures, with a compiler that generates parsers in multiple languages. If you’re reversing a proprietary file format or a custom network protocol, you write a .ksy specification and instantly get a parser in Python, C++, Java, or a dozen other targets. The web IDE lets you visualize the parsed structure against a hex dump, making it invaluable for documenting and sharing your reverse engineering findings.

Hex Editors with Analysis Features

Sometimes you need to get your hands dirty at the byte level. Modern hex editors are far more than viewers; they include structure definition, data inspection, and even disassembly.

010 Editor: Binary Templates and Scripting

010 Editor’s killer feature is Binary Templates—a C-like language for defining data structures that are then applied directly to the hex view, highlighting fields, showing parsed values, and making sense of raw bytes. For reversing file formats, it’s unmatched. The scripting engine lets you automate modifications, and the integrated disassembler handles quick lookups without launching a full reverse engineering suite. It’s commercial software, but the template repository alone is worth the price for anyone doing regular binary analysis.

ImHex: The Open-Source Contender

ImHex is a newer, open-source hex editor built with reverse engineering in mind. It features a pattern language similar to 010 Editor’s templates, a built-in disassembler, data visualization, and a modern dark-themed interface. It’s rapidly gaining features and community support. For those who prefer open-source tools or can’t justify a commercial license, ImHex is a serious alternative.

Static Analysis for Specific Targets

General-purpose tools are great, but some file types demand specialized static analyzers. These tools understand the semantics of particular formats and can catch issues that a generic disassembler would miss.

Checksec: ELF Hardening Checker

Checksec is a simple but essential script that examines ELF binaries for security hardening features: stack canaries, PIE, RELRO, NX, and Fortify Source. Before you even start reversing a Linux binary, Checksec tells you what mitigations are in place—and therefore what attack surfaces are likely viable. It’s part of the pwntools suite and should be run on every target as a first step.

APKTool and JADX: Android Static Analysis

For Android applications, APKTool decodes the APK container and disassembles the Dalvik bytecode to Smali, a human-readable assembly format. JADX goes further, decompiling DEX files directly to Java source. Together, they let you statically analyze an Android app’s logic, permissions, and embedded strings without ever installing it on a device. This is critical for spotting malicious behavior in APKs before they touch a sandbox.

Lines of decompiled code displayed on a monitor in a dimly lit analysis lab

String and Metadata Extraction

Before diving into disassembly, smart analysts harvest every plaintext clue from a binary. Strings, debug symbols, compiler fingerprints—these can reveal functionality, authorship, and intent without executing a single instruction.

Strings and Floss: Beyond the Obvious

The classic Unix strings command is the starting point, but modern malware often obfuscates or encrypts its strings. FireEye’s FLOSS (FireEye Labs Obfuscated String Solver) goes further, using static analysis to decode stack-constructed strings and identify string decryption routines. It works on both x86 and x64 binaries and can automatically extract strings that strings would miss entirely.

Exeinfo PE and Detect It Easy: Packer Identification

Before you can analyze a binary, you need to know if it’s packed. Exeinfo PE and Detect It Easy (DIE) are signature-based tools that identify packers, cryptors, compilers, and protectors. DIE is particularly powerful, with a heuristic engine that can spot unknown packers and a disassembler view for manual verification. If a sample is packed, your static analysis starts with unpacking—skip this step and you’ll waste hours staring at obfuscated stubs.

Building a Static Analysis Workflow

No single tool does everything. A mature static analysis workflow chains multiple tools together, each handling a specific phase. Here’s a practical sequence for an unknown ELF binary:

1. Triage: Run file to identify the format, then checksec to assess hardening. Use DIE to detect packers. If packed, identify the packer and find an unpacker or dump the unpacked payload from memory later.

2. String Harvesting: Run strings with a minimum length of 6, then FLOSS for obfuscated strings. Grep for URLs, IPs, file paths, and error messages. These often reveal C2 infrastructure, targeted files, or debug output.

3. Disassembly and Decompilation: Load into Ghidra or IDA. Run initial auto-analysis. Identify the entry point and main function. Use the decompiler to get a high-level overview, then drill into suspicious functions in the disassembler.

4. Structure Recovery: If the binary parses a custom format, use Kaitai Struct or 010 Editor templates to document the format. This makes the reversing findings reusable and shareable.

5. Annotation and Reporting: As you identify functions, global variables, and code paths, rename them in your disassembler. Export your analysis as a database or script so others can reproduce your work. Ghidra’s collaborative server is excellent for team efforts.

FAQ

What’s the difference between static and dynamic analysis?
Static analysis examines a binary without executing it—reading instructions, parsing headers, and extracting strings. Dynamic analysis runs the code in a controlled environment (sandbox, debugger) to observe its behavior. Static analysis is safer for malware and reveals all code paths, but it can’t show runtime-decrypted data or environment-dependent behavior. The two are complementary; static analysis often guides where to set breakpoints in dynamic analysis.

Do I need to learn assembly language for static analysis?
Yes, but not all architectures at once. Start with x86-64, as it’s the most common in desktop malware and server binaries. ARM (both 32-bit and 64-bit) is essential for mobile and embedded work. Decompilers help, but they make mistakes—especially with hand-crafted assembly or obfuscated code. Being able to read the raw disassembly lets you verify decompiler output and spot anti-analysis tricks.

Can static analysis detect all vulnerabilities?
No. Static analysis excels at finding structural issues—hardcoded credentials, insecure API usage, missing mitigations, and logic flaws visible in the code. But it cannot detect runtime-dependent vulnerabilities like memory corruption that depends on specific input, race conditions, or side-channel leaks. For those, you need dynamic analysis or fuzzing. Static analysis narrows the search space dramatically, though.

Is Ghidra really a replacement for IDA Pro?
For many users, yes. Ghidra’s decompiler is excellent, its collaboration features are unique, and it’s free. IDA still has advantages: a more polished interface, better handling of certain obfuscation techniques, a larger plugin ecosystem, and Hex-Rays’ microcode API for advanced analysis. If you’re doing professional vulnerability research on heavily protected binaries, IDA may be worth the cost. For everyone else, Ghidra is more than sufficient.

How do I handle statically analyzing a packed binary?
First, identify the packer using DIE or Exeinfo PE. If it’s a known packer, search for an unpacker or use a tool like UPX (which can decompress its own format). If it’s custom, you’ll need to let the binary unpack itself in a debugger, then dump the unpacked process memory. That dumped payload becomes your static analysis target. Some analysts use emulation frameworks like Unicorn to run the unpacking stub without touching the real OS, keeping the analysis fully static.

Static binary analysis is a discipline that rewards patience and tool mastery. The binaries won’t give up their secrets easily, but with the right instruments and a methodical approach, you can reconstruct their logic completely—without ever letting them execute.