When source code is compiled into a stripped binary, the variable types vanish, and without them a security tool analyzing that binary — say, firmware with no source — cannot tell which function an indirect call could really reach or which data actually depends on which, so it drowns in false possibilities; this paper (HKUST) recovers those types by combining several data-flow analyses of different precision that cover each other's weaknesses — a cheap coarse pass to guess a type for nearly every variable, then sharper passes to tighten the over-broad guesses — inferring types at high precision and recall, and using them to prune infeasible call targets and dependencies well enough to have surfaced 86 confirmed firmware vulnerabilities, 64 with assigned identifiers.
The argument in one glance
-
Problem
Compiling to a binary throws away the types a security analysis needs
A great deal of security work happens on binaries with no source code — embedded firmware, industrial control software — where a static analyzer reads the compiled machine code to hunt for vulnerabilities. The trouble is that compilation strips out variable types, and types turn out to be exactly what a static analysis leans on to stay precise. At the source level, knowing that a function pointer has a certain signature lets the analyzer rule out every function of the wrong type as an impossible indirect-call target; knowing two variables have incompatible types lets it rule out a data dependency between them. Strip the types away and the analyzer loses that filter: an indirect call appears able to reach almost any function, and data seems able to flow almost anywhere, so the analysis is swamped with infeasible possibilities and its bug reports become imprecise. The missing type information is the single biggest reason binary bug detection is less precise than source-level analysis.
-
What people did
Recover the types by inferring them from the binary
The natural fix is binary type inference: analyze the machine code to reconstruct each variable's type from how it is used — the sizes of loads and stores, the operations applied, the calls it flows into — and feed those recovered types back to sharpen the bug detection.
-
Why it fell short
Existing inference is either too coarse or too incomplete to help
Existing binary type inference lands in one of two unhelpful places. A low-precision analysis (flow- and context-insensitive) collects every type hint for a variable and unifies them indiscriminately, but because hints from many different variables get merged, it assigns each variable a bloated, over-approximated type — high recall, low precision. A high-precision analysis (flow- and context-sensitive) instead propagates hints carefully along control flow and calling contexts, but that carefulness means it gathers fewer hints, so it leaves the types of many variables simply unknown — high precision, low recall. Over-approximated types cannot prune much, and unknown types prune nothing, so in neither case can the recovered types actually make the binary analysis more precise.
-
This paper's idea
Combine analyses of different precision so they cover each other
Manta is the first hybrid-sensitive type inference: it deliberately combines data-flow analyses of different sensitivities so their strengths cover each other's weaknesses. It starts with a cheap, coarse flow- and context-insensitive pass that assigns a type to nearly every variable, giving high recall but over-approximated types. Then, only for those over-approximated variables, it applies higher-precision context-sensitive and flow-sensitive refinements that narrow the coarse unions down, and it uses the coarse analysis in turn to fill in variables the precise analyses left unknown. The result is precise types for most variables, which it feeds into bug detection to prune infeasible indirect-call targets and data dependencies.
Why this problem is worth solving
The entire precision of a binary analysis rides on a kind of information the binary no longer contains. A type tells an analyzer what a variable really is — a pointer to a particular structure, an integer of a certain width, a function pointer of a given signature — and that knowledge is a powerful filter for pruning impossibilities. In resolving an indirect call, where the target is computed at runtime, knowing the function pointer’s type lets the analyzer permit only functions whose signatures match and reject all the rest, dramatically shrinking the set of possible targets. In tracking data dependencies, knowing that two locations hold incompatible types lets the analyzer rule out a flow of data between them. Source-level analyzers get this filtering for free because the source declares the types. But compilation erases them: a stripped binary is just bytes and machine instructions, with no record of what was an int, a pointer, or a struct. Without types, the analyzer must conservatively assume an indirect call can reach nearly any function and that data can flow nearly anywhere, which floods it with infeasible facts and is the main reason binary bug detection is imprecise. Recovering accurate types is therefore the lever that would let binary analysis approach source-level precision.
The reason existing recovery falls short is a fundamental tension in how static analyses trade breadth for sharpness. Static analyses come in different sensitivities. A flow-insensitive, context-insensitive analysis ignores the order of statements and the calling context, collecting all the type hints associated with a variable and unifying them; this is cheap and gathers hints broadly (high recall) but merges hints that belong to different variables, so it over-approximates each type into a coarse union (low precision). A flow-sensitive, context-sensitive analysis instead respects the order of operations, applying strong updates as a variable is reassigned, and distinguishes calling contexts, propagating hints only along valid paths; this is precise (it does not blur unrelated hints together) but, being selective, it gathers fewer hints and so leaves many variables’ types unknown (low recall). For type inference this is exactly the wrong dilemma: over-approximated types are too coarse to prune anything useful, and unknown types prune nothing at all. Neither extreme produces types that both cover most variables and are precise enough to sharpen a downstream analysis, which is the gap a hybrid of sensitivities is meant to close.
What people did before, and where it broke
- Analyze binaries without types at all. Run indirect-call and data-dependency analysis on the stripped binary directly, conservatively assuming anything is possible. Where it broke. Without types the analysis cannot prune infeasible targets or flows, so it drowns in false possibilities and its bug reports are imprecise — the core problem.
- Low-precision type inference (flow/context-insensitive). Collect every type hint for a variable and unify them, cheaply assigning a type to nearly every variable. Where it broke. Because hints from many unrelated variables are merged, each variable gets a bloated, over-approximated type; high recall but low precision, and coarse types prune little.
- High-precision type inference (flow/context-sensitive). Propagate type hints carefully along control flow with strong updates and along valid calling contexts to avoid blurring. Where it broke. The carefulness gathers fewer hints, leaving many variables’ types unknown; high precision but low recall, and unknown types prune nothing.
- Pick one sensitivity and accept its weakness. Commit to either the coarse-but-complete or the precise-but-partial approach as a fixed design point. Where it broke. Either choice leaves inferred types that cannot effectively assist the downstream analysis — too coarse to filter, or too sparse to matter — so type-assisted binary bug detection never materialized.
- The gap. No approach combined sensitivities so that the coarse analysis’s recall and the precise analyses’ precision covered each other, yielding types that are both broadly assigned and sharp enough to prune real infeasible facts in bug detection. That combination is what Manta introduces.
How Manta works
Lift, infer coarsely, refine precisely, then prune
-
-
new
-
new
-
new
-
new
-
new
The idea
Let analyses of different precision cover each other’s blind spots. The stuck point in binary type inference is that each sensitivity fails in the opposite way: the coarse analysis over-approximates, the precise analysis leaves variables unknown. Manta’s central observation is that these failures are complementary. A precise analysis can pin down exactly the types that a coarse analysis left as bloated unions, because it does not blur unrelated hints together; and a coarse analysis can supply types for exactly the variables a precise analysis abandoned as unknown, because it gathers hints broadly. So rather than picking one sensitivity and living with its weakness, Manta runs several and lets each repair the other’s output, achieving both high precision and high recall. Combining static analyses tuned to different sensitivities so that their strengths compensate for their individual imprecision is the same complementary-precision strategy the path-sensitive, alias-aware analysis work covered elsewhere on this site is built on, dialing analysis sensitivity to where it pays and leaning on cheaper reasoning elsewhere so the whole is more precise than any single setting.
Start coarse for recall, then refine only what is over-approximated. The way Manta orchestrates the combination is a staged refinement that spends effort where it is needed. It begins with the cheapest, most complete analysis — a global flow- and context-insensitive pass that assigns a type to nearly every variable, guaranteeing high recall even though many of those types are coarse. Then it applies the expensive high-precision analyses selectively, only to the variables whose types came out over-approximated, sharpening those coarse unions rather than re-analyzing everything. This staging is what makes precision affordable: the costly context- and flow-sensitive reasoning is aimed exactly at the over-approximations it can fix, not spent uniformly. Reconstructing high-level structure from a stripped binary by first recovering a broad, approximate picture and then progressively refining it is the same reverse-engineering discipline the accurate-disassembly work covered elsewhere on this site relies on, rebuilding what compilation discarded through layered inference rather than one monolithic pass.
Turn recovered types into a filter that prunes the impossible. Types are only worth recovering if they make the downstream analysis better, and Manta’s payoff is precisely that. It feeds the refined types into bug detection as a pruning filter: at an indirect call, only functions whose type matches the function pointer’s recovered type are kept as possible targets, discarding the rest; in data-dependency tracking, a putative dependency between two locations of incompatible types is dropped as infeasible. Removing these impossibilities shrinks the search space the bug detector must consider, which both speeds it up and cuts false positives, and it is accurate enough that program slicing on the binary comes close to slicing on the original source. Using a recovered, precise notion of what a pointer can legitimately be to prune the infeasible targets of an indirect call is the same type-driven filtering the precise pointer-analysis work covered elsewhere on this site performs, tightening the set of legal call targets so a downstream security analysis reasons about only what can really happen.
Following a variable through Manta
Recovering a function pointer's type to prune a call
A trace, from the paper, of how Manta infers a precise type for a variable in a stripped binary and uses it to sharpen bug detection.
Step 1 of 5
Lift the binary and gather type hints
Manta lifts the machine code to an LLVM intermediate representation and runs pointer analysis. For a variable used as a function pointer, it collects hints from how the code uses it — the signature at the callsites it flows into, the sizes and operations applied — but at this stage the hints from many variables are mingled.
The lifting and points-to map give a uniform data-flow view on which the inference operates.
Why it works
- Complementary sensitivities beat any single one. Each sensitivity fails oppositely — coarse over-approximates, precise leaves unknowns → but a precise pass fixes the coarse over-approximations while the coarse pass fills the precise pass’s unknowns → so Manta reaches both high precision (78.7 percent) and high recall (97.2 percent) that neither achieves alone.
- Starting coarse guarantees recall. A precise-only analysis abandons many variables as unknown → but a cheap flow- and context-insensitive first pass assigns a type to nearly every variable → so almost nothing is left untyped before refinement even begins, anchoring the high recall.
- Refining only over-approximations makes precision affordable. Running expensive context- and flow-sensitive analysis on everything would not scale → but applying it only to the variables the coarse pass over-approximated targets the effort where it fixes something → so precision rises without re-analyzing the whole binary.
- Precise types prune what the untyped analysis could not. Without types, an indirect call seems to reach almost any function and data seems to flow almost anywhere → but matching a call’s recovered function-pointer type against candidate functions discards the mismatched ones → so Manta prunes 63.9 percent more infeasible indirect-call targets than existing type analysis.
- Accurate types bring binary analysis near source level. A binary analysis normally lags source analysis precisely because it lacks types → but recovering them precisely restores the filter source analyses enjoy → so program slicing on the binary reaches 61.1 percent similarity to slicing on the original source, and the sharpened detection found real vulnerabilities.
What it costs (trade-offs and scope)
- Precision is high but not perfect. At 78.7 percent precision the inferred types are far better than prior work but still sometimes wrong, so some pruning decisions rest on imperfect types; a mis-inferred type could over-prune (risking missed behavior) or under-prune, and the downstream analysis inherits that residual error.
- The staged refinement adds machinery and cost. Running a coarse global pass followed by selective context- and flow-sensitive refinements is more complex and more expensive than a single-sensitivity analysis; the design keeps it affordable by refining only over-approximations, but it is heavier than the simplest approaches and its cost scales with how many variables need refinement.
- It depends on lifting and pointer analysis being sound enough. Manta operates on an LLVM intermediate representation lifted from the binary and on a pointer analysis; errors or imprecision in lifting stripped machine code or in the points-to map propagate into the type inference, and binary lifting is itself a hard, imperfect step.
- Recovered types are structural, not semantic ground truth. Type inference reconstructs types from usage, so it recovers what the code’s behavior implies rather than the programmer’s original declarations; unusual coding patterns, heavy pointer casting, or optimized code can yield types that differ from the source intent even when they are consistent with the binary.
- Evaluation centers on indirect calls, dependencies, and firmware bug-finding. The reported gains — the precision and recall, the 63.9 percent extra target pruning, the 61.1 percent slicing similarity, and the 86 confirmed vulnerabilities — are for these specific tasks and targets; the approach’s value on other analyses, architectures, or heavily obfuscated binaries is beyond what the paper measures.
The evaluation
Inferring types to assist binary bug detection
| Property | Low-precision inference | High-precision inference | Manta (hybrid-sensitive) |
|---|---|---|---|
| How types are inferred | |||
| Precision of inferred types | |||
| Recall (variables typed) | |||
| Pruning of infeasible indirect-call targets | |||
| Downstream impact | Limited | Limited |
Check yourself
Check your understanding
1. Why does the loss of type information make binary bug detection imprecise?
2. Why do existing binary type inference approaches fail to help, and what is Manta's key insight?
3. How does Manta orchestrate its analyses of different sensitivity?
4. What did Manta achieve, and what should temper how the results are read?
Sources
The paper is “Manta: Hybrid-Sensitive Type Inference Toward Type-Assisted Bug Detection for Stripped Binaries” by Chengfeng Ye, Yuandao Cai, Anshunkang Zhou, Heqing Huang, Hao Ling, and Charles Zhang (The Hong Kong University of Science and Technology, with Heqing Huang at City University of Hong Kong), published at ASPLOS 2024 (Volume 4), DOI 10.1145/3622781.3674177.
This explainer is written from the full text of the authors’ copy of the paper. Everything above comes from the paper’s own sections: the introduction motivating type-assisted static binary bug detection for source-less software such as firmware and industrial control systems, and identifying the loss of type information during compilation as the main reason binary analysis is imprecise, since types are used to prune infeasible indirect-call targets and data dependencies; the problem statement that existing binary type inference either over-approximates (flow- and context-insensitive analysis unifying all type hints indiscriminately, giving high recall but low precision) or leaves many types unknown (flow- and context-sensitive analysis propagating hints carefully, giving high precision but low recall); the observation that a hybrid combination of sensitivities can complement each other (a precise analysis refining over-approximated types, a coarse analysis capturing hints the precise one ignores); the Manta pipeline (lifting the binary to LLVM IR, pointer analysis producing a points-to map, a global flow- and context-insensitive pass producing an over-approximated initial type map, then context-sensitive and flow-sensitive refinement of the over-approximated types, followed by type-assisted analysis that prunes infeasible indirect-call targets and data dependencies); and the evaluation reporting 78.7 percent precision and 97.2 percent recall (about 32.7 percent better than prior inference on average), 63.9 percent more infeasible indirect-call targets pruned than existing type analysis, program slicing on binaries with 61.1 percent similarity to source-level slicing, and 86 new developer-confirmed vulnerabilities in popular IoT firmware with 64 assigned CVE or PSV identifiers.