disrobe: static software recovery

disrobe

Recover source, unpack payloads, and trace the evidence back to the input.

disrobe is a Rust command-line suite for examining compiled and obfuscated software. It decompiles supported bytecode and native code, reverses source obfuscation, extracts frozen applications and embedded frontends, and unpacks supported packers, archives, and firmware containers. Recovery reports identify the input, the stages that ran, the artifacts they produced, and the conditions that prevented further recovery.

The default path never executes the sample. One opt-in PyArmor v6/v7 fallback does execute it and requires --allow-dynamic; use that path only inside an isolated sandbox. Start with the forensics and malware-safety posture when handling untrusted material.

Watch or download the full video · Read the transcript. Twenty CLI commands cover unpacking, source recovery, Android resources, indicators, reports, artifact verification, and project setup in 2 minutes 12 seconds. The transcript includes the recorded build's complete command inventory.

Try it in your browser: the disrobe playground. Decompile a .pyc, scan a pickle for malicious reduce callables, and summarize a .wasm module, all client-side, with the core passes compiled to WebAssembly. Nothing is uploaded.

The live catalog spans 15 ecosystems: Python, JavaScript and TypeScript, WebAssembly, JVM and Android, .NET, native PE/ELF/Mach-O/NE, Go, Lua, PHP, Ruby, Erlang and Elixir on BEAM, Swift and Objective-C, ActionScript 3, mobile runtimes, and shell languages. The implemented native-packer tier currently lists Donut, sRDI, UPX, ASPack, Petite, MPRESS, FSG, PECompact, Yoda's Crypter, NSPack, MEW, kkrunchy. Run disrobe catalog [ecosystem] for the per-family recovery tier compiled into your binary.

Guarantees and boundaries

  • No model runs in the recovery path. Metadata bundles are deterministic structured data for downstream tools.
  • Output ordering and serialization are checked by hashing three fixture recoveries across Linux, macOS, Windows, and batch runs with one and four workers.
  • The main CLI ships as one Rust binary. In-house paths launch no external program. Commands with optional backends invoke an installed tool only when you name it with --backend.
  • The shared artifact layer can store recovered state in a content-addressed .dr envelope with an rkyv payload, postcard sidecar, and BLAKE3 root. Chain runs record topology and per-stage provenance separately.
  • Python's normalized opcode-structure agreement is 95.18% on a fixed 574-module CPython 3.14 core population (17396 of 18276 code objects), excluding idlelib and turtledemo. Its pinned 200-module subset reaches 97.66% (6139 of 6286). See the comparison rules.

Start with an artifact

After installation, replace sample.bin with the file you want to examine:

disrobe auto sample.bin --out recovered/ --capture-stages
disrobe context --out recovered/
disrobe report recovered/ --format html > recovery.html

auto selects recovery passes from the file's bytes. context summarizes their outcomes; the HTML report brings the artifact inventory, evidence, and recovery limits into one document. --capture-stages retains intermediate bytes so you can inspect a layer or pass it to another tool. The quickstart explains the output and direct commands.

Your taskUseful starting point
Triage a packed or obfuscated sampleAutomatic recovery, followed by recon and indicator extraction over the recovered files
Document a forensic examinationRun reports with input digests, artifact locations, stage outcomes, and provenance
Audit a closed binary or investigate a vulnerabilityNative recovery, queryable IR and capabilities, and the relevant language guide
Recover your own lost sourceDirect decompilation of a supported .pyc, .jar, .dll, or JavaScript bundle; start with the quickstart examples
Build recovery into another toolRust library APIs, typed Python bindings, daemon protocols, metadata bundles, or the browser playground

Choose a recovery path

disrobe passes lists direct command families and the automatic recovery routes included in the current build, with their ecosystems and support tiers. Direct commands also provide recon, taint analysis, and optional external decompilers. Electron and Tauri frontends use webview.carve; Wails can use Go's embedded-filesystem route. Use disrobe --help for commands and disrobe catalog [ecosystem] for recognized families.

In-house recovery remains available without optional toolchains. JVM, Android, .NET, and native commands can also use installed tools such as CFR, Vineflower, jadx, ILSpy, de4dot, or Ghidra where their command-specific backend policy allows it. disrobe doctor reports what is installed; it does not make an unavailable backend part of an in-house result.

Measured recovery

Every figure below comes from a committed test gate or a local measurement harness. strong figures are graded against an independent oracle; coverage-self-reported figures state the inspected population and count disrobe's own output. The full per-value sourcing lives in xtask/data/recovery.json.

Measured recovery by ecosystem

Open the full-size recovery chart.

Colour identifies the comparison method. A filled mark denotes a CI gate on its documented triggers; a hollow mark denotes a local measurement. Each descriptor lists its inputs and prerequisites.

EcosystemMeasuredOracle
Python bytecodeNormalized opcode-structure agreement: 95.18% across the fixed 574-module core population (17396 of 18276); 97.66% on the pinned corpus (6139 of 6286). Whole-module agreement, where every compared code object matches: 130 of 200 modulesCPython 3.14.5 compilation and normalized opcode comparison; jump targets and most operands are excluded
WebAssembly1034 of 1034 opcodes lowered across the 38 parseable modules (133 of 133 functions), counted against an inventory wasm-tools produced rather than one disrobe produced; 57 of 57 execution-eligible functions equivalentexternal opcode inventory for coverage, wasmtime differential for execution
JVM classfile131 of 131 methods recompile error-freereal javac
Android (Dalvik)100% of the verifier-presented classes in the committed dex corpus pass the JVM verifier (118 of 118). A further 37 of 155 classes are link-skipped and never reach the verifier, because they reference supertypes the harness does not bundle, so those are ungraded rather than passing-Xverify:all over assembled jar
Ruby YARVgreeter 100%, megafile 100% original opcode-name multiset recallrecompile on MRI; ordering, operands and additional opcodes are excluded from this score
PyArmor72 / 72 manifest-named v8/v9 default-trial wrappers decrypt and decode one complete header-anchored root CodeObjectself-reported structural check; no source, emitted .pyc, semantic, execution, or external comparison
Containers103 formats detected, including a bounded raw-volume-key LUKS1 route; 30 generic routes write member bytes from a committed input that match an independent extractorextraction over the committed corpus, pinned per format; tracked LUKS1 plaintext comparison

Recovery reports include support tiers and per-stage outcomes. Native-virtualized code, runtime-only keys, and RSA-wrapped capsule keys remain detect-only when the required information is absent from the input.

Recovery limits

An unsupported instruction, ambiguous signature, or unavailable runtime key can stop a recovery stage. Its report names the reason and retains the recovered structure available up to that point. Supplying additional artifacts, such as a matching runtime or name map, can make another recovery path available. See reading a result for outcomes and error codes.

Where to start