threatflux-binary-analysis 0.3.1

Parsing and analysis primitives for ELF, PE, Mach-O, Java, and WebAssembly binaries
Documentation

ThreatFlux Binary Analysis

CI Security Crates.io docs.rs MSRV License: MIT

A synchronous Rust library for inspecting the structure of executable and bytecode formats. The core API detects and parses bytes into a common representation of metadata, sections, symbols, imports, and exports. Optional Cargo features add disassembly, control-flow reconstruction, entropy measurements, symbol demangling, JSON support, and graph output.

Upgrading from 0.2.0? See the migration guide.

This is a static-analysis building block, not a malware verdict engine or an execution sandbox. Its security findings, packing indicators, and control-flow results are heuristics that can produce false positives, false negatives, or incomplete output.

What is implemented

Input Cargo feature Current parser output and limits
ELF elf (default) Header metadata, sections, symbols, imports, exports, and selected hardening metadata
PE pe (default) Header metadata, sections, COFF symbols when present, imports, exports, and selected hardening metadata; Authenticode is not verified
Thin Mach-O macho (default) Header metadata, sections, nlist symbols when present, imports, exports, entry point when reported by the image, and selected hardening metadata; fat/universal binaries are unsupported
Java class java (default) Fixed-header checks and a synthetic whole-file section; the version is validated for detection but not exposed, and the constant pool, fields, methods, and attributes are not decoded
JAR java (default) Identifies ZIP archives containing class entries and exposes class-entry names as symbols; class bodies are not decoded
WebAssembly wasm Validated module plus selected section ranges, imports, and exports; the WebAssembly start-function index is not represented as a virtual-address entry point
Other non-empty bytes always A raw binary with one synthetic data section

Valid Java class headers, bounded universal Mach-O headers, and WebAssembly magic are recognized even when the corresponding parser feature is disabled. Parsing such input then returns an unsupported-format error. Empty input is rejected; an unrecognized non-empty input is classified as raw data.

Parser output and optional analyses are separate layers:

  1. BinaryFile::parse detects and parses structural data.
  2. BinaryAnalyzer copies that structural data and invokes only the analysis modules compiled into the crate.
  3. Disassembler, control-flow analysis, entropy analysis, and SecurityAnalyzer are opt-in tools with independent limitations.

See Analysis boundaries before using the crate on untrusted input.

Requirements

  • Rust 1.95.0 or newer
  • A C toolchain when building the Capstone backend

Installation

Default parsers (ELF, PE, thin Mach-O, Java class/JAR):

[dependencies]
threatflux-binary-analysis = "0.3"

Select only the formats and analysis backends you need:

[dependencies]
threatflux-binary-analysis = {
    version = "0.3",
    default-features = false,
    features = ["elf", "disasm-iced", "serde-support"]
}

Feature flags

Feature Default Adds
elf yes ELF parser
pe yes PE parser
macho yes Thin Mach-O parser
java yes Java class and class-containing JAR parser
wasm no WebAssembly parser
disasm-capstone no Capstone backend for x86, x86-64, ARM, AArch64, MIPS, PowerPC
disasm-iced no iced-x86 backend for x86 and x86-64
control-flow no Control-flow and call-graph analysis; also enables Capstone
entropy-analysis no Shannon entropy and packing heuristics
symbol-resolution no Demangling of parsed symbol names
compression no gzip and zlib decompression helper
visualization no DOT export for control-flow graphs
serde-support no Serde derives and JSON helpers

Cargo features are compile-time capabilities. Setting an AnalysisConfig boolean does not enable a missing feature. Version 0.3 defaults to parser-only analysis: every optional runtime switch is false. To run an optional analysis, compile its Cargo feature and set its runtime switch. Requesting a capability that was not compiled returns BinaryError::FeatureNotAvailable.

Quick start

Parse a file and inspect its structural metadata:

use std::fs;
use threatflux_binary_analysis::{BinaryFile, Result};

fn main() -> Result<()> {
    let bytes = fs::read("sample.bin")?;
    let binary = BinaryFile::parse(&bytes)?;

    println!("format: {}", binary.format());
    println!("architecture: {}", binary.architecture());
    println!("sections: {}", binary.sections().len());
    println!("imports: {}", binary.imports().len());

    Ok(())
}

Use BinaryAnalyzer when you want the unified AnalysisResult:

use threatflux_binary_analysis::{AnalysisConfig, BinaryAnalyzer, Result};

fn inspect(bytes: &[u8]) -> Result<()> {
    let config = AnalysisConfig {
        enable_disassembly: false,
        enable_control_flow: false,
        enable_call_graph: false,
        enable_cognitive_complexity: false,
        enable_advanced_loops: false,
        enable_entropy: false,
        ..Default::default()
    };

    let result = BinaryAnalyzer::with_config(config).analyze(bytes)?;
    println!("{} / {}", result.format, result.architecture);
    Ok(())
}

The API is synchronous and byte-slice based. BinaryAnalyzer enforces its configured input limit, but bounding a file before reading it, scheduling work, and applying wall-time limits remain caller responsibilities.

Examples

Each example expects a path after --:

cargo run --example basic_analysis -- path/to/file
cargo run --example security_analysis -- path/to/file
cargo run --example disassembly --features disasm-capstone -- path/to/file
cargo run --example control_flow --features control-flow -- path/to/file

Section::size is the virtual or in-memory span, while Section::file_size is the byte count backed by the file at Section::offset. Section::data is only an inline preview for eligible file-backed sections no larger than 1 KiB. Built-in disassembly and graph analysis resolve checked file-backed ranges against BinaryFile's full owned bytes. The current disassembly example prints the preview directly and can therefore be partial; production callers can use Disassembler::disassemble_section or pass a validated byte range to disassemble.

Security and resource boundaries

Treat every parsed file as adversarial:

  • Reject oversized input before reading it. BinaryAnalyzer also enforces AnalysisConfig::max_analysis_size and returns BinaryError::InputTooLarge; direct BinaryFile::parse has no configured size policy.
  • Set AnalysisConfig::max_disassembly_instructions for the maximum high-level disassembly result size. The default is 10,000 instructions.
  • Expect copies. BinaryFile::parse owns a copy of the full input, and format implementations may retain additional owned data.
  • ELF, PE, Mach-O, Java/JAR, and WebAssembly parsing rejects more than 100,000 structural/output records, names longer than 4 KiB, or more than 32 MiB of aggregate owned name bytes per parse. JAR central-directory inspection has the tighter 50,000-entry limit described above.
  • Run hostile inputs in a separate process with OS-level memory, CPU, and wall time limits. The API has no cancellation or timeout mechanism.
  • Do not treat a successful parse, a low heuristic score, or absence of a finding as proof that a file is safe.
  • The default compression helper caps expanded output at 64 MiB. decompress_with_limit accepts an application-specific ceiling.
  • Pattern searches cap aggregate owned result data at 16 MiB by default; configure MatchConfig::max_output_bytes for a different budget.
  • Keep files immutable while they are memory mapped.

Memory-map construction is an unsafe API because callers must keep the backing file stable for the mapping lifetime. Rust's type system reduces many classes of memory errors, but it is not a sandbox or a guarantee that malformed input cannot panic, exhaust resources, or trigger a dependency bug.

Report vulnerabilities privately as described in SECURITY.md.

Documentation

Generate API documentation locally with:

RUSTDOCFLAGS="-D warnings" cargo doc --all-features --no-deps

Development

The supported local contracts are:

make check
make test
make security
make feature-check
make ci

See DEVELOPMENT.md for prerequisites and feature-specific checks. There are currently no Criterion benchmark targets in Cargo.toml; performance-named integration tests are ordinary test targets, not a published benchmark suite.

Contributing

Bug reports, focused fixes, tests, and documentation corrections are welcome. Please read CONTRIBUTING.md and the Code of Conduct first.

License

Licensed under the MIT License.