The Three Pillars of Vulnerability Research: Code Review, Reverse Engineering, and Fuzzing

The Three Pillars of Vulnerability Research: Code Review, Reverse Engineering, and Fuzzing

Every technique in vulnerability research eventually traces back to one of three disciplines: reading code (or its equivalent when there’s no source), understanding a system by taking it apart, and throwing carefully-designed chaos at it until it breaks. I’ve come to think of code review, reverse engineering, and fuzzing as three complementary pillars — each with its own strengths, blind spots, and ideal use cases, and the researchers who get the most out of their time are the ones who know which pillar to lean on for a given target, and how to combine them rather than relying on just one.

Why Three Pillars, Not One “Best” Method

A common beginner mistake is treating one of these as universally superior. In reality, each pillar answers a different question:

  • Code review answers: given the logic as written, where could it fail?
  • Reverse engineering answers: what does this system actually do, when I have no source to trust?
  • Fuzzing answers: what inputs, that I wouldn’t have thought to craft by hand, cause this system to misbehave?

They’re not competing methodologies — they’re complementary lenses, and most serious vulnerability research combines all three at different stages.

flowchart TD
    A[Target Software] --> B{Source Available?}
    B -->|Yes| C[Code Review]
    B -->|No/Partial| D[Reverse Engineering]
    C --> E[Identify Candidate Weak Points]
    D --> E
    E --> F[Fuzzing to Confirm & Discover]
    F --> G[Crash/Anomaly Triage]
    G --> H[Root Cause Analysis]
    H --> I{Exploitable?}
    I -->|Yes| J[Proof of Concept & Reporting]
    I -->|No| E

Pillar One: Code Review

Code review is the most direct and, when source is available, usually the highest-signal method — you’re reading the actual logic rather than inferring it. Effective security code review isn’t the same as general code review; it’s specifically oriented around trust boundaries, data flow, and dangerous operations (see the source-to-sink and sink-to-source disciplines discussed elsewhere).

Strengths:

  • Highest precision — you see exactly what the code does, not an approximation
  • Finds logic bugs (authorization flaws, business logic errors) that fuzzing and reverse engineering typically miss entirely, since these bugs don’t crash anything
  • Fastest path to root-cause understanding once a bug is found

Limitations:

  • Requires source access (or very good decompilation)
  • Doesn’t scale well to enormous codebases without static analysis tooling assistance
  • Easy to miss subtle interactions between distant parts of a codebase that don’t manifest as an obvious local issue
  • Reviewer fatigue and cognitive load are real — human reviewers miss things, especially in repetitive or large-volume code

Typical workflow:

  1. Build a mental (or literal) map of trust boundaries and data flow.
  2. Catalog sources and sinks relevant to the language/framework.
  3. Trace data flow, manually and with static analysis tooling (Semgrep, CodeQL) as a force multiplier.
  4. Pay special attention to authentication/authorization logic, which fuzzing essentially never finds, since these bugs are about missing checks rather than crashes.
  5. Review recent diffs/commits with extra scrutiny — newly introduced code has a disproportionately higher bug density than stable, long-reviewed code.

Pillar Two: Reverse Engineering

When source isn’t available — proprietary software, firmware, compiled binaries, obfuscated mobile apps — reverse engineering becomes the primary way to understand what a system actually does. This spans a spectrum from relatively light techniques (dynamic analysis, API tracing) to deep static disassembly and decompilation.

Core techniques:

TechniqueToolsWhat It Reveals
Static disassembly/decompilationGhidra, IDA Pro, Binary NinjaControl flow, function boundaries, approximate source reconstruction
Dynamic analysis / debuggingx64dbg, GDB, WinDbg, FridaRuntime behavior, actual values in memory, execution tracing
Binary diffingBinDiff, DiaphoraComparing patched vs. unpatched binaries to identify what a security patch actually fixed
Network/API tracingWireshark, Frida hooks, mitmproxyProtocol behavior and data formats without needing full disassembly
String/symbol analysisstrings, symbol table inspectionQuick clues about functionality, embedded credentials, debug info leaks

Strengths:

  • Works with zero source access — applicable to any compiled target
  • Binary diffing against a patch is one of the single most efficient ways to find a vulnerability, since the vendor has effectively told you where the bug was (n-day vulnerability research)
  • Reveals ground truth about actual runtime behavior, including compiler-introduced quirks that wouldn’t be visible in source

Limitations:

  • Time-intensive, especially for large or heavily obfuscated binaries
  • Requires specialized skills (assembly, calling conventions, compiler internals) with a steep learning curve
  • Anti-reverse-engineering measures (packing, obfuscation, anti-debugging) can significantly slow progress
  • Reconstructed logic from decompilation is an approximation — always subject to some interpretation error, especially with heavy compiler optimization

Typical workflow (n-day patch analysis, a very common professional pattern):

  1. Obtain both the patched and unpatched binary versions.
  2. Run a binary diffing tool to identify exactly which functions changed.
  3. Disassemble/decompile the changed functions to understand what the patch actually does differently.
  4. Infer the original vulnerability from the nature of the fix (a new bounds check added, a validation function introduced, a use-after-free pattern corrected).
  5. Confirm by crafting an input that triggers the vulnerable path on the unpatched version.

This “patch diffing” workflow is how a huge fraction of real-world n-day exploitation research is actually conducted — vendors routinely under-disclose the security relevance of a fix in their changelog, so researchers (and, unfortunately, attackers targeting unpatched systems) diff the binaries directly to understand what was actually broken.

Pillar Three: Fuzzing

Fuzzing is automated testing that feeds a program large volumes of malformed, unexpected, or randomly-mutated input, monitoring for crashes, hangs, or other anomalous behavior. It’s the pillar most capable of finding bugs that neither a human code reviewer nor a reverse engineer would think to construct by hand, precisely because it doesn’t rely on human intuition about what input “should” break something.

Major fuzzing approaches:

ApproachDescriptionExample Tools
Mutation-basedStarts from valid seed inputs, randomly mutates bytesAFL++, honggfuzz
Generation-basedConstructs inputs from a grammar/model of the expected formatPeach Fuzzer, custom grammar-based fuzzers
Coverage-guidedUses code coverage feedback to intelligently guide mutation toward unexplored pathsAFL++, libFuzzer, go-fuzz
Structure-awareUnderstands the input format (e.g., a specific file format or protocol) to generate structurally valid-but-malicious inputCustom harnesses using libprotobuf-mutator, format-specific fuzzers
Differential fuzzingRuns the same input against multiple implementations, flags discrepanciesUsed heavily in cryptographic and parser correctness research

Strengths:

  • Finds edge cases no human would think to test
  • Scales extremely well — can run continuously, generating millions of test cases
  • Particularly effective against memory-unsafe languages (C/C++) for finding memory corruption bugs
  • Requires no deep manual understanding of the target’s internals to start finding crashes (though understanding helps enormously with triage)

Limitations:

  • Needs a working harness — isolating the specific function/parser to fuzz, which itself requires some code understanding
  • Poor at finding logic/authorization bugs that don’t manifest as a crash
  • Coverage plateaus are common; getting past complex validation checks (magic bytes, checksums) often requires either a smarter fuzzer or manual harness adjustments
  • Triage overhead — a fuzzer can produce thousands of crashes that need deduplication and root-cause analysis to determine which are actually distinct, exploitable bugs versus noise

Typical workflow:

  1. Build a harness that isolates the target function/parser from the full application (critical for performance and precision).
  2. Gather a good seed corpus of valid inputs representative of real-world usage.
  3. Run coverage-guided fuzzing, monitoring for new coverage and crashes.
  4. Triage crashes: deduplicate by stack trace/crash signature, then determine root cause for each unique crash.
  5. Assess exploitability — not every crash is a security vulnerability; a null pointer dereference causing a clean crash is very different from a heap corruption bug with attacker-controlled write primitives.
# Example: a minimal AFL++ fuzzing session against a parsing binary
afl-fuzz -i seeds/ -o findings/ -- ./target_parser @@

How the Three Pillars Combine in Practice

The most effective real-world research workflows don’t pick one pillar — they move between all three:

sequenceDiagram
    participant CR as Code Review
    participant RE as Reverse Engineering
    participant FZ as Fuzzing
    CR->>CR: Identify complex parsing function as high-risk
    CR->>FZ: Build harness for that specific function
    FZ->>FZ: Discover crashing input via mutation
    FZ->>RE: Hand off crash for root-cause analysis
    RE->>RE: Disassemble crash site, understand memory layout
    RE->>CR: Confirm root cause matches suspected code pattern
    CR->>CR: Assess broader codebase for similar patterns elsewhere

A concrete real-world pattern: a researcher reviewing source code identifies a custom binary parser with a suspicious length-handling pattern (code review). Rather than manually tracing every possible malformed input, they build a fuzzing harness around just that parsing function and let a coverage-guided fuzzer generate thousands of malformed variants (fuzzing). When a crash surfaces, they use a debugger and disassembler to understand exactly what memory corruption occurred and confirm it’s a genuinely exploitable primitive rather than a benign crash (reverse engineering/dynamic analysis) — and having confirmed one instance, they go back to code review to check whether the same flawed pattern was copy-pasted elsewhere in the codebase.

Choosing Pillar Emphasis by Target Type

Target CharacteristicRecommended Primary PillarSupporting Pillars
Open-source web applicationCode reviewFuzzing for input handlers, minimal RE
Closed-source desktop binaryReverse engineeringFuzzing on isolated functions, code review if partial source/SDK exists
Network protocol parser (source available)Code review + FuzzingRE only if binary-only components involved
Firmware/embedded deviceReverse engineeringFuzzing via emulation (QEMU-based harnesses)
Cryptographic libraryCode review + Differential fuzzingRE for compiled dependencies
Mobile applicationReverse engineering (APK/IPA decompilation)Code review of any exposed source, dynamic analysis via Frida

Frequently Asked Questions

Which pillar should a beginner learn first? Code review, generally — it builds foundational understanding of common vulnerability patterns (injection, deserialization, path traversal) using readable source code, before tackling the steeper learning curves of assembly-level reverse engineering or fuzzing harness construction.

Is fuzzing becoming less necessary now that static analysis tools are more advanced? No — they find different, largely non-overlapping bug classes. Static analysis and code review are strong at logic bugs and known unsafe patterns; fuzzing remains uniquely effective at finding memory corruption in complex parsing logic that would be extremely tedious to identify through manual review alone, especially for bugs involving unusual, non-obvious input sequences.

Do professional vulnerability researchers specialize in just one pillar? Many do develop a primary specialization (a lot of browser/kernel researchers lean heavily reverse-engineering and fuzzing-focused; a lot of web application security researchers lean heavily code-review-focused), but the strongest researchers are generally competent across all three, because real targets rarely respect a clean division between “needs code review” and “needs reverse engineering.”

How does AI-assisted tooling fit into these three pillars? It’s increasingly used to augment all three — LLM-assisted code review for spotting suspicious patterns at scale, AI-assisted decompilation cleanup for reverse engineering, and machine-learning-guided fuzzers that use learned models to generate more effective test cases than pure random mutation — but each still requires human validation, since these tools currently produce meaningful false positive/negative rates on their own.

References

Summary and Recommendations

Code review, reverse engineering, and fuzzing each answer a different question about a target, and treating them as interchangeable — or worse, only ever using one — leaves real findings on the table. Code review gives you precision and finds the logic bugs nothing else catches; reverse engineering gives you ground truth when there’s no source to read; fuzzing gives you scale and finds the inputs no human would think to craft. The researchers who consistently find high-value vulnerabilities are the ones who move fluidly between all three — using code review to identify where to focus fuzzing effort, using fuzzing to surface crashes that reverse engineering then explains at the root-cause level, and feeding that understanding back into a sharper, more targeted code review pass. Build competence in all three, and let the target — not habit — decide which one leads at any given moment.

Total
3
Shares

Leave a Reply

Previous Post
Vulnerability Research vs. Penetration Testing: Different Goals, Shared Techniques

Vulnerability Research vs. Penetration Testing: Different Goals, Shared Techniques

Next Post
How to Select the Right Target for Vulnerability Research: A Practical Guide

How to Select the Right Target for Vulnerability Research: A Practical Guide

Related Posts