DROIDSAW — Rust utility // Lib.rs
3 releases<br>(stable)
Uses new Rust 2024
2.0.0
Jun 11, 2026
1.0.0
May 25, 2026
0.1.0
Apr 14, 2026
#17 in Android
BSD-3-Clause
1.5MB
23K
SLoC
droidsaw
Art by pmjv_prahou. A gift to the last mage of the Open clan.
droidsaw takes a DEX file or Hermes bundle apart and puts it back together byte-for-byte — 5,767 DEX files from F-Droid recovered bit-identically under preservation mode, Hermes bytecode round-trip verified on v84, v96, v98, and v99. That test fails loudly when the format model has a hole: a string-table offset wrong by one, an alignment requirement missed, a padding byte forgotten — the re-emitted bytes diverge and the test says where. Most Android RE tooling works per-layer; droidsaw traces a JS value through the React Native bridge into Java as a single taint path. Hand it an APK and it unpacks the container, decompiles every layer, and can pipe the output through Semgrep and TruffleHog in the same pass (audit --mode=full). CLI and MCP server share one command surface. Pure Rust, BSD-3-Clause.
Install
cargo install droidsaw # the CLI<br>cargo install droidsaw --features mcp # also installs the droidsaw-mcp server
cargo fetches the droidsaw-* library crates from crates.io and compiles everything locally. The<br>only prerequisites are a Rust toolchain (rustup) and a C compiler (cc/clang).<br>semgrep and trufflehog are optional (for audit --mode=full, --mode=semgrep, or --mode=trufflehog); YARA is built in. No<br>Java/Android SDK is needed.
Installing from automation or CI? Use cargo install --locked droidsaw — it builds against the<br>dependency versions pinned in the publish-time lockfile, so the same release installs reproducibly.
For fleet or CI installs, prefer the cargo auditable<br>variant — it embeds the full crate dependency list inside the binary itself (a compressed JSON<br>record in a dedicated linker section), so the artifact stays auditable long after the build<br>environment is gone:
cargo install cargo-auditable cargo-audit # one-time tooling<br>cargo auditable install --locked droidsaw # binary carries its own dependency inventory<br>cargo audit bin $(which droidsaw) # audit the installed artifact, no manifest needed
cargo audit bin reads the embedded inventory and checks it against the RustSec advisory<br>database — the binary answers "what exactly is in you?" years later, with nothing but the file.
Working on droidsaw itself? Check out the droidsaw repo and the five droidsaw-* library repos as<br>siblings, then build droidsaw/ — its dev-loop [patch.crates-io] wires in the local ../droidsaw-*<br>crates instead of crates.io.
Quickstart
droidsaw info app.apk # layer summary: bytecode + manifest + signing<br>droidsaw audit app.apk --mode=basic # fast hermetic audit (no subprocesses)<br>droidsaw audit app.apk --mode=basic --format sarif # SARIF 2.1.0 for code scanning<br>droidsaw manifest app.apk | jq '.manifest.exported_components'<br>droidsaw decompile app.apk com.example.Foo # DEX class → Java<br>droidsaw decompile app.apk 42 # Hermes function index → JS<br>droidsaw xrefs app.apk --search 'api_secret|Bearer' # who references this string?
Every command composes with jq; worked per-audience flows are in Playbooks below.
Inputs & output
Inputs: APK, XAPK, .hbc, .dex. Hermes bundles and DEX files are extracted from an APK automatically; co-located split_config.*.apk siblings are auto-merged (--no-auto-splits to disable).
Output: stdout is one JSON object, array, or NDJSON stream — nothing else. The documented plain-text exemptions: --version and --help (clap convention), hbc disassemble (instruction stream for differential tooling), scan trufflehog (raw string feed), and decompile --all --js (concatenated JS). Progress goes to stderr prefixed droidsaw: .
Exit codes: 0 success · 1 reserved for the opt-in audit --fail-on=severity> gate (the audit completed, stdout carries the normal audit output, and at least one emitted finding is at or above the threshold — pure exit-code CI gating, no jq required) · 2 failure. Every failure — including a mistyped flag — is a typed JSON error envelope on stdout:
"error": {<br>"code": "USER_INPUT | PERMISSION | TRANSIENT | CONFIGURATION | INTERNAL",<br>"operation": "audit",<br>"message": "no such file: app.apk",<br>"hint": "verify the path points to a readable APK/DEX/HBC file"
Repeated runs on the same input produce bit-identical output.
What it finds
Patterns surfaced across a corpus of production apps spanning dating, social, fintech, banking, and health:
Key material in the JS heap — private-key operations running in Hermes with no native boundary, no memory zeroing. Visible because droidsaw reads the Hermes layer.
Signing-chain weaknesses at the cryptographic level — ROCA fingerprint (CVE-2017-15361), Fermat-factorable close primes (returns (p, q)), batch-GCD shared-prime recovery across a corpus (Bernstein quasilinear), Wiener-regime exponent fingerprint (e > N^0.75; full recovery).
Data crossing the React Native bridge into sinks it shouldn't...