Skip to content

Repository files navigation

assembler

A Rust disassembler for raw machine code, object-backed binaries, symbol-scoped inspection, and conservative semantic analysis.

Built for low-friction terminal workflows, deterministic verification, and evidence-backed output rather than decompiler theater.

Preview

Pretty output

assembler pretty output

Plain output

assembler plain output

Analysis output

assembler analysis output

What this tool is

assembler is a command-line disassembly frontend implemented in Rust.

It:

  • decodes raw opcode streams and object-backed executable code through Capstone
  • resolves section and symbol context through object
  • renders normalized assembly in either terminal-friendly pretty mode or grep-friendly plain mode
  • emits structured JSON for automation and CI usage
  • optionally runs a conservative semantic analyzer for x86 and x86_64 disassembly

It is intentionally not a decompiler, source reconstructor, symbolic executor, or exploitability oracle.

Design principles

  • Deterministic decoding surface: explicit architecture selection when metadata is insufficient; no silent guessing for ARM/Thumb raw modes
  • Symbol-first workflow: real binary and object-file inspection with symbol and section filtering instead of only byte-stream demos
  • Terminal-safe output: hostile strings are escaped before rendering, and non-interactive output defaults to a stable plain-text layout
  • Conservative analysis: findings are derived from operand semantics, memory addressing, and control-flow evidence—not from mnemonic names or imported APIs
  • Regression-proof development: fixture binaries use exact global_asm! symbols so verification is not hostage to compiler codegen drift

Table of contents

Build and installation

Build from source

cargo build --release

Install locally

cargo install --path .

Requirements

  • Rust toolchain (edition 2024)
  • a C compiler only if you want to build the optional demo target in examples/password-login/

Command model

assembler [FILE] [OPTIONS]
assembler --raw-hex <HEX> --arch <ARCH> [OPTIONS]

The CLI operates in two disjoint input modes:

ModeTriggerDecode source
File modepositional FILEsection and symbol data from object metadata
Raw-byte mode--raw-hex <HEX>direct decode of user-provided bytes

Raw-byte mode requires --arch because there is no container metadata to infer decode mode safely.

Core options

FlagMeaning
--arch <x86|x86-64|arm|thumb|aarch64>force architecture or override file-mode auto detection
--symbol <NAME>restrict file disassembly to one or more symbol names
--section <NAME>restrict file disassembly to one or more section names
--all-sectionsdisassemble every non-empty section instead of executable sections only
--syntax <intel|att>x86/x86_64 syntax selection
--render <auto|pretty|plain>layout selection
--color <auto|always|never>ANSI color control
--output <text|json>human-readable output or structured JSON
--analyzeappend semantic analysis output
--analyze-exit-codereturn exit code 1 when analysis findings exist
--base-address <ADDR>override base address for raw-byte decoding

Important mode constraints

  • --symbol, --section, and --all-sections are file-mode only
  • --base-address is only meaningful for raw-byte decoding
  • --syntax att only affects x86 and x86_64 output
  • ARM object files require explicit --arch arm or --arch thumb

Common workflows

Show CLI help

cargo run -- --help

Decode a minimal x86_64 function from raw bytes

cargo run -- --raw-hex "55 48 89 e5 5d c3" --arch x86-64

Disassemble a single symbol from a binary

cargo run -- ./target/debug/assembler --symbol main

Restrict file output to selected sections

cargo run -- ./target/debug/assembler --section .text --section .init

Force pretty output without ANSI color

cargo run -- --raw-hex "55 48 89 e5 5d c3" --arch x86-64 --render pretty --color never

Force plain output with color

cargo run -- --raw-hex "55 48 89 e5 5d c3" --arch x86-64 --render plain --color always

Run analysis and get machine-readable JSON

cargo run -- ./target/debug/assembler --symbol main --analyze --output json

Gate CI on findings

cargo run -- ./target/debug/assembler --symbol main --analyze --analyze-exit-code

Output formats

Text output

The text renderer has two layouts:

ModeBehavior
prettystructured box layout optimized for interactive reading
plainflat, grep-friendly text optimized for logs, pipes, and captured output
autopretty on TTYs, plain on captured or piped stdout

Color behavior:

ModeBehavior
autoenabled on terminals, disabled when NO_COLOR is present or TERM=dumb
alwaysANSI sequences always emitted
neverANSI disabled entirely

JSON output

--output json emits a stable structured document:

{
"disassembly": {
"target": "...",
"architecture": "X86_64",
"metadata": [["format", "Elf"], ...],
"sections": [...]
},
"analysis": {
"architecture": "X86_64",
"findings": [...],
"notes": [...]
}
}

analysis is omitted when --analyze is not requested.

Semantic analysis

--analyze runs a post-decoding pass that consumes Capstone detail-mode output and reasons over:

  • typed operands
  • access direction
  • stack-relative memory addressing
  • frame setup patterns
  • basic-block control flow and loop back-edges

It does not inspect rendered text for keywords and does not claim exploitability from disassembly alone.

Internal model

CLI
→ DisasmRequest
→ Capstone decode (detail mode)
→ DisassemblyReport
→ analyze()
→ AnalysisReport
→ text or JSON render

Current finding classes

ClassMeaning
potential-stack-buffer-write-riskrepeated indexed writes into stack-local memory with evidence that progression exceeds inferred capacity
possible-out-of-bounds-local-writesingle or loop-driven local write whose offset plus width exceeds inferred frame bounds
suspicious-copy-loopbackward-branch write loop with weak or unrecoverable destination bound evidence
unsafe-stack-frame-writewrite above the local frame through an established frame pointer
stack-pointer-frame-pointer-anomalyindexed write using live stack pointer as base
indirect-write-riskmemory write through a non-stack computed pointer

Analysis engine details

  • frame reconstruction scans prologue instructions until the first backward branch or call, which handles delayed setups better than a fixed instruction window
  • CFG construction builds basic blocks and back-edge relationships instead of relying on flat backward-jump heuristics alone
  • bound matching only promotes a loop bound into a finding when the compared register is the one actually driving the memory write progression
  • finding deduplication keys on (kind, address, section, rationale) so distinct evidence at one address is preserved
  • bounded-loop suppression prevents strongly bounded local loops from being reported as suspicious when the proven bound fits the inferred local capacity

Explicit non-goals

  • no decompilation
  • no source reconstruction
  • no symbolic execution
  • no imported-API danger lists turned into fake findings
  • no exploitability claims beyond observed disassembly evidence

Deterministic fixture corpus

fixtures/ is a dedicated workspace member that builds a separate verification binary containing exact global_asm! symbols.

This is a major part of the project’s engineering discipline: analyzer and renderer regressions are validated against precise assembly programs, not compiler-accidental Rust or C code generation.

What the fixtures provide

  • positive x86_64 fixtures for every current analyzer finding class
  • negative x86_64 fixtures for false-positive resistance
  • AArch64 fixtures for renderer and unsupported-analysis verification
  • linker-retained symbols through extern "C" declarations plus #[used] retention tables

Representative fixture symbols

SymbolExpected result
fixture_stack_local_unbounded_loopstack-buffer risk + out-of-bounds local write + suspicious copy loop
fixture_stack_oob_write_no_loopout-of-bounds local write only
fixture_copy_loop_weak_boundsuspicious loop / weak-bound behavior without overclaiming stronger proof
fixture_frame_adjacent_writeunsafe stack-frame write
fixture_indirect_indexed_storeindirect write risk
fixture_indexed_rsp_writestack-pointer / frame-pointer anomaly
fixture_bounded_local_loopzero findings
fixture_compare_only_no_writezero findings
fixture_frame_setup_no_risky_writezero findings
fixture_frame_write_no_setupzero findings
fixture_aarch64_basic_functionzero findings + unsupported-analysis note

Fixture workflow

cargo build -p fixtures
cargo test --test fixtures
# inspect one positive fixture manually
cargo run -- ./target/debug/fixtures --symbol fixture_stack_local_unbounded_loop --analyze --output json

Fixture authoring rules

  • symbol names use the fixture_ prefix
  • local labels use .L_<fixture_name>_<label>
  • every fixture symbol has an explicit .size directive for reliable symbol-scoped disassembly
  • fixture code lives only in fixtures/, never in production src/
  • fixture verification is Linux/ELF-oriented; AArch64 fixtures are cross-built and disassembled, not executed on the host

Reverse-engineering example

examples/password-login/ contains a small C target compiled to preserve readable machine code.

gcc -O0 -g -fno-inline -fno-builtin -no-pie \
-o examples/password-login/secret_login \
examples/password-login/secret_login.c

Inspect the password check

cargo run -- examples/password-login/secret_login --symbol check_password --render pretty --color never

Analyze the same symbol

cargo run -- examples/password-login/secret_login --symbol check_password --analyze --render plain --color never

This function is intentionally a negative analysis case: it reveals a secret through immediate byte comparisons, but it does not perform the stack-local copy or repeated write behavior required for a memory-safety finding.

See examples/password-login/README.md for the full walkthrough.

Architecture support and limits

TargetRaw bytesFile-backedNotes
x86yesyesIntel syntax default, AT&T optional
x86_64yesyesIntel syntax default, AT&T optional
AArch64yesyesuse --arch aarch64 for raw input
ARMnoyesexplicit --arch arm required
Thumbnoyesexplicit --arch thumb required; bit0 symbol normalization applied

Important limits:

  • raw-byte mode requires --arch
  • ARM object files are not silently guessed as ARM vs Thumb
  • big-endian object files are explicitly rejected
  • semantic analysis currently targets x86 and x86_64 only

Safety and robustness

  • terminal-hostile strings are escaped before rendering
  • pretty output preserves full instruction text without clipping operands or labels
  • raw hex input is capped at 8192 decoded bytes
  • input files must be regular files and are capped at 128 MiB
  • raw mode rejects file-only flags such as --symbol, --section, and --all-sections
  • Thumb symbol addresses are normalized so label mapping and slicing remain stable on real ELF symbols
  • fixture verification uses explicit symbol retention and nm checks instead of assuming the linker kept test-only targets

Verification

cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo build -p fixtures
cargo test
cargo test --test fixtures
bash scripts/smoke.sh

CI additionally performs:

  • x86_64 fixture symbol verification with nm
  • AArch64 cross-build verification with aarch64-linux-gnu-gcc
  • AArch64 fixture symbol verification with aarch64-linux-gnu-nm

Project layout

src/
main.rs entry point and output dispatch
cli.rs CLI model and argument parsing
types.rs shared request/report/instruction data model
disasm.rs Capstone integration, object parsing, symbol resolution
render.rs text rendering, ANSI styling, operand token classification
analysis.rs semantic analyzer, CFG construction, finding model
fixtures/
src/main.rs symbol retention tables and fixture module wiring
src/x86_64.rs exact x86_64 analyzer fixtures
src/aarch64.rs exact AArch64 renderer fixtures
tests/
cli.rs CLI integration tests
fixtures.rs fixture-driven analyzer regression tests
scripts/
smoke.sh quick end-to-end verification
examples/
password-login/ reverse-engineering demo target

Implementation stack

ComponentTechnology
LanguageRust 2024 edition
Decoder backendCapstone via capstone crate
Object parsingobject
CLI parsingclap derive API
Structured outputserde + serde_json
Error handlinganyhow
Terminal layoutunicode-width
Verification fixturesglobal_asm! + ELF .size directives

Summary

assembler is optimized for the real work of low-level inspection:

  • point it at bytes or a binary
  • narrow to the symbol or section you care about
  • get stable, scriptable disassembly output
  • optionally attach conservative semantic findings
  • verify the whole stack against deterministic assembly fixtures

If you want a terminal-native disassembler with explicit architecture handling, disciplined output, and analyzer behavior that is tested against exact machine code instead of wishful abstractions, this repository is built for that workflow.

About

A terminal-first Rust disassembler for raw bytes, binaries, and symbol-focused inspection.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages