disrobe: a universal decompiler, deobfuscator, and unpacker

disrobe

One tool to decompile, deobfuscate, and unpack almost anything, deterministically, in a single Rust binary.

disrobe is a universal multi-language decompiler and deobfuscator. It decompiles Python .pyc bytecode, unpacks PyArmor and PyInstaller, reads Nuitka-compiled binaries, decompiles WebAssembly, deobfuscates JavaScript, decompiles .NET / CIL and JVM / Java, recovers Android DEX, and unwraps native PE / ELF / Mach-O packers, all from one static binary built for malware analysis and reverse engineering.

disrobe demo

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.

disrobe reverses the bytecode, packers, freezers, and protectors layered onto compiled and frozen software across 20+ ecosystems: Python, JavaScript/TypeScript, WebAssembly, JVM and Android, .NET, native PE/ELF/Mach-O, Go, Lua, PHP, Ruby, Erlang/Elixir (BEAM), Swift/Objective-C, ActionScript 3, React Native Hermes, Flutter Dart AOT, and the native packer tier layered on top of them (UPX, MPRESS, NSPack, FSG, kkrunchy, MEW, ASPack, PECompact, Petite, Yoda's Crypter). It ships as a single static Rust binary.

Built for forensic and recovery work where reproducibility matters:

  • Deterministic. No model anywhere in the decompile path. The same input produces byte-identical output on every machine and every run, usable as evidence and as a diff baseline.
  • Single static binary. No JVM, no Python runtime, no Docker image required to run the core. Builds from one cargo build --release. Drops into CI headlessly.
  • Content-addressed. Every recovered artifact persists as a .dr envelope: an rkyv hot payload plus a postcard cold sidecar, rooted by a BLAKE3 hash. Cache hits are byte-identical and chains compose offline.
  • Honest. Every Python decompile is recompiled on the matching interpreter and compared opcode-for-opcode: 92.76% per-code-object equivalence on the CPython 3.14 stdlib (5831 of 6286), measured against the interpreter, not the tool's own output. Recovery that is not perfect is labelled SEMANTIC, PARTIAL, or SKELETON rather than presented as ground truth. Commercial-tier packers that disrobe cannot fully unpack are reported as detect-only by design, never faked.

Who this is for

  • Malware analysts and incident responders who receive a packed, frozen, or obfuscated sample and need to read what it does, without executing it.
  • Security researchers auditing a closed binary for interoperability or vulnerability research.
  • Developers recovering their own lost source from a shipped .pyc, .jar, .dll, or bundled .js.
  • Coding agents. Every pass can emit a structured metadata sidecar (--llm) carrying the call graph, type signatures, control-flow shape, capability surface, and decompile provenance, so an LLM can reason about recovered code without re-deriving its structure.

What makes it different

disrobe ships passes for every ecosystem above from a single binary. Where mature FOSS already exists (CFR, Vineflower, jadx, ILSpy, JPEXS, unluac, hermes-dec, Ghidra), disrobe wraps it headlessly behind a unified CLI and adds chain auto-detection, deterministic .dr envelopes, and round-trip verification. Where FOSS coverage is thin (PyArmor v9-pro, the native packer tier, Hermes against a live bundle, Flutter Dart AOT, MicroPython .mpy, PEP 750 t-strings), it is among the few tools handling these statically and offline. Where the field is dominant (Ghidra/IDA/Binary Ninja for native decompilation), disrobe is the unpack, symbol-recovery, and chain-detect layer that feeds them cleaner input.

Measured recovery

Every figure below is produced by a committed test gate or a local measurement harness graded against an independent oracle, never the tool's own output. The full per-value sourcing lives in xtask/data/recovery.json.

Measured recovery by ecosystem

EcosystemMeasuredOracle
Python bytecode92.76% per-code-object equivalence on the CPython 3.14 stdlib (5831 of 6286)recompile on CPython 3.14.5, opcode diff
CPython legacy 1.0-3.7152 of 191 proven-correct (CI floor); 166 of 191 measured locallyrecompile-equivalence or structural token-match
WebAssembly100% op-coverage on the 30 parseable corpus modules; 24 of 24 execution-eligible functions equivalentexecution differential under wasmtime
JVM classfile93.1% of methods recompile error-free (122 of 131 floor; 128 measured)real javac
Android (Dalvik)99% of verifiable classes pass the JVM verifier (102 of 103)-Xverify:all over assembled jar
Ruby YARVgreeter 100%, megafile 85% opcode-multiset equivalencerecompile on MRI
PyArmor72 of 72 real-corpus samples recoveredplaintext-absent oracle
Containers98 formats detected, 98 extracted in-treeper-format byte length

The numbers that are not perfect are labelled SEMANTIC, PARTIAL, or SKELETON, and the information-theoretic walls (native-virtualized code, runtime-only keys, RSA-wrapped capsule keys) are reported as detect-only by design.

How to read these docs