-
-
Notifications
You must be signed in to change notification settings - Fork 0
python bindings
disrobe ships a typed Python library that mirrors the full CLI surface. The
importable disrobe module is built from crates/disrobe-python with pyo3
(abi3, Python 3.9+) and wraps the same Rust library the CLI uses. Bytes go in;
a concrete typed report object comes out. Output is deterministic: the same
input produces the same report bytes.
The library does not read or write the filesystem; the caller owns all I/O. Wheels are not published to PyPI; build from source.
git clone https://github.com/1-3-7/disrobe
cd disrobe/bindings/python
pip install maturin
maturin develop --releaseFor a redistributable wheel:
maturin build --release
pip install target/wheels/disrobe-*.whlThe pyproject.toml pins maturin>=1.5,<2.0, sets module-name = "disrobe.disrobe", and points at crates/disrobe-python/Cargo.toml. On
Windows the crate's build.rs searches PYO3_PYTHON, an active
VIRTUAL_ENV, and standard install locations; set PYO3_PYTHON=<path-to-python.exe>
if none is found. A py.typed marker is shipped so pyright and mypy resolve
every attribute from the .pyi stub.
import disrobe
version: str = disrobe.__version__Every analysis function returns a concrete subclass of _Report. The base
carries the full serialization surface every report shares.
| Member | Signature | Notes |
|---|---|---|
raw |
@property -> dict[str, Any] |
Full underlying record; no detail dropped |
to_json |
() -> str |
Compact JSON string |
from_json_str |
classmethod(text: str) -> Self |
Rebuild from a to_json string |
from_obj |
classmethod(obj: dict[str, Any]) -> Self |
Wrap an already-decoded dict |
Reports compare equal when their underlying records are equal (== / !=).
Subclasses _Report. Adds one property:
| Member | Signature | Notes |
|---|---|---|
llm |
@property -> LlmBundle | None |
Populated on LLM-wired passes; None otherwise |
LLM-wired passes: py_decompile, py_disasm, py_deob, pyarmor_detect,
pyarmor_unpack. Functions that build an LLM bundle accept
pack: Pack | None where Pack = Literal["pack-1", "pack-2", "pack-3", "pack-4"].
A TypedDict(total=False) mirroring the disrobe.metadata.llm.v1 on-disk schema.
Keys present depend on which pack was requested.
from disrobe import LlmBundle
from typing import Any
bundle: LlmBundle = {
"schema": "disrobe.metadata.llm.v1",
"schema_version": "1",
"generated_at": "2026-06-16T00:00:00Z",
"tool": {},
"selection": {},
"input": {},
"pipeline": [],
"categories": {},
}| Alias | Values |
|---|---|
Pack |
"pack-1", "pack-2", "pack-3", "pack-4"
|
RoundtripStatus |
"perfect", "semantic", "code-diff", "no-interpreter", "recompile-failed", "skipped"
|
PyarmorUnpackStatus |
"functional", "bcc-partial", "detect-only", "skeleton"
|
ContainerListing |
"enumerated", "requires-extraction", "unreadable"
|
SymbolKind |
"function", "data", "label", "export", "import"
|
InstructionFlow |
"sequential", "call", "indirect-call", "conditional-branch", "unconditional-branch", "indirect-branch", "return", "interrupt"
|
SourceLanguage |
"python", "py", "python3"
|
JsLanguage |
"javascript", "js", "typescript", "ts"
|
ByteLanguage |
"python-bytecode", "pyc", "jvm-class", "class", "dex", "beam", "hermes", "hermes-bundle", "hbc", "wasm"
|
ParseByteLanguage |
"go", "swift", "objc", "objective-c", "kotlin", "ruby", "lua", "php"
|
DisasmByteLanguage |
"ruby", "ruby-bytecode", "yarv", "mruby", "php", "php-bytecode"
|
DecompileLanguage |
"python-bytecode", "pyc", "jvm-class", "class", "java", "kotlin", "lua", "ruby", "php", "php-bytecode", "javascript", "js", "typescript", "ts"
|
| Class | Base | Raised when |
|---|---|---|
DisrobeError |
Exception |
Any binding fails |
UnsupportedLanguage |
DisrobeError |
disasm/parse/compile/decompile for a language with no backing implementation; message includes a hint |
| Category | Function | Returns |
|---|---|---|
| Auto chain | auto(input, *, max_depth=8, path_hint=None) |
ChainReport |
| Generic dispatch | decompile(language, source) |
CanonicalSource |
disasm(language, source) |
str |
|
parse(language, source) |
typed report or dict[str, Any]
|
|
compile(language, source, *, version=None) |
bytes |
|
| Custom pass | register_pass(name, callable) |
None |
register_consumer(name, callable) |
None |
|
registered_passes() |
list[str] |
|
registered_consumers() |
list[str] |
|
unregister(name) |
bool |
|
run_pass(name, data) |
Any |
|
run_chain(names, data) |
Any |
|
emit(name, result, **context) |
Any |
|
| Analysis | strings_extract(data, *, min_len=4, decode=True) |
StringsReport |
ioc_extract(data) |
IocReport |
|
behavior_analyze(data) |
BehaviorReport |
|
identify(data) |
IdentifyReport |
|
secret_scan(data) |
SecretScanReport |
|
capabilities(binary_bytes) |
Capabilities |
|
extract(data, out_dir) |
ExtractionResult |
|
extract_recursive(data, *, source_label='inline', max_depth=8) |
OverlayReport |
|
yara_parse(ruleset_source) |
YaraReport |
|
yara_generate(data, *, name=None) |
YaraReport |
|
| Native | native_symbols(data) |
SymbolsReport |
native_disasm(data) |
DisasmPayload |
|
native_callgraph(data) |
CallGraph |
|
native_imports_dot(data) |
str |
|
native_entropy(data) |
EntropyReport |
|
native_sbom(data) |
SbomReport |
|
native_fingerprint(data, *, flirt=None) |
FingerprintReport |
|
native_signatures(data, *, flirt=None) |
SignatureReport |
|
native_sigmaker(data, at) |
SigmakerReport |
|
native_diff(a, b) |
DiffReport |
|
native_patch(data, *, at, replacement=None, nop_start=None, nop_end=None) |
tuple[bytes, PatchReport] |
|
native_format(binary_bytes) |
NativeFormat |
|
native_detect(binary_bytes) |
DetectionList |
|
native_probe_backends() |
BackendList |
|
native_deobfuscate(code, *, bits=64, base=0, entry=0) |
NativeDeobfuscation |
|
| Query IR | query_functions(dr_bytes) |
FunctionList |
query_calls_to(dr_bytes, target) |
QueryReport |
|
query_xrefs_to(dr_bytes, symbol) |
QueryReport |
|
query_string_decoders(dr_bytes) |
QueryReport |
|
query_complexity_over(dr_bytes, threshold) |
QueryReport |
|
query_capability_sites(dr_bytes, capability) |
QueryReport |
|
query_call_graph(dr_bytes) |
CallGraph |
|
| Envelope | envelope_create(payload, *, source_label='inline', produced_by=None, detected_format=None) |
bytes |
envelope_verify(envelope_bytes) |
EnvelopeReport |
|
| LLM renders | agents_md(result) |
str |
skill_md(result) |
str |
|
provenance(result) |
Provenance |
|
| Python decompile | py_decompile(pyc_bytes, *, roundtrip=False, pack=None) |
PyDecompileReport |
py_disasm(pyc_bytes, *, pack=None) |
PyDisasmReport |
|
| Python deobfuscate | py_deob(source, *, cleanup=True, pack=None) |
PyDeobReport |
py_deob_detect(source) |
PyDeobDetection |
|
py_deob_list_passes() |
list[ObfuscatorPass] |
|
py_deob_detect_pass(source, pass_id) |
PyDeobDetection |
|
| PyArmor | pyarmor_detect(source, *, pack=None) |
PyarmorDetection |
pyarmor_unpack(wrapper_bytes, *, pack=None) |
PyarmorUnpack |
|
pyarmor_classify(source, payload) |
PyarmorClassification |
|
| PyInstaller | pyinstaller_extract(image_bytes) |
PyInstallerArchive |
pyinstaller_entry_bytes(image_bytes, entry_name) |
bytes |
|
| Nuitka | nuitka_detect(image_bytes) |
NuitkaDetection |
nuitka_extract(image_bytes) |
NuitkaExtraction |
|
| Hermes | hermes_disasm(bundle_bytes) |
HermesDisassembly |
hermes_lift(bundle_bytes) |
HermesLift |
|
hermes_info(bundle_bytes) |
HermesInfo |
|
| Mach-O | macho_dump(macho_bytes) |
MachoReport |
swift_analyze(macho_bytes) |
SwiftReport |
|
| JVM / Android | jvm_parse_class(class_bytes) |
JvmClass |
jvm_parse_dex(dex_bytes) |
DexFileReport |
|
jvm_decompile_class(class_bytes) |
JvmDecompiledClass |
|
jvm_detect(class_bytes) |
DetectionList |
|
jvm_backends() |
JvmBackends |
|
apk_resources(apk_bytes) |
ApkResources |
|
| .NET | dotnet_parse_pe(pe_bytes) |
DotnetPe |
dotnet_parse_metadata(pe_bytes) |
DotnetMetadata |
|
dotnet_detect(pe_bytes) |
DotnetDetection |
|
dotnet_analyze(pe_bytes) |
DotnetAnalysis |
|
dotnet_decompile(pe_bytes) |
DotnetDecompilation |
|
dotnet_recover_decoders(pe_bytes) |
DotnetDecoders |
|
dotnet_backends() |
BackendList |
|
| WebAssembly | wasm_analyze(wasm_bytes) |
WasmAnalysis |
wasm_detect(wasm_bytes) |
WasmDetection |
|
| JavaScript | js_detect(js_source) |
JsDetection |
js_unminify(js_source) |
JsUnminify |
|
js_unbundle(js_source, *, bundler=None) |
JsUnbundle |
|
| Lua | lua_detect(bytecode) |
LuaDetection |
lua_decompile(bytecode) |
LuaDecompilation |
|
lua_deobfuscate(source, *, authorize=False, strict=False) |
LuaDeobfuscation |
|
| Go | go_analyze(binary_bytes) |
GoAnalysis |
go_symbols(binary_bytes) |
GoSymbols |
|
go_pclntab(binary_bytes) |
GoPclntab |
|
go_garble(binary_bytes) |
GarbleReport |
|
| Ruby | ruby_detect(ruby_bytes, *, source_path=None) |
RubyDetection |
ruby_decompile(ruby_bytes, *, source_path=None) |
RubyAnalysis |
|
| PHP | php_detect(php_bytes) |
PhpDetection |
php_scan(php_bytes) |
PhpScan |
|
php_decode(php_bytes, *, max_depth=None) |
PhpDecode |
|
| Shell | batch_deobfuscate(script, *, args=None) |
BatchDeobReport |
powershell_detect(script) |
PowershellDetection |
|
powershell_deobfuscate(script) |
PowershellDeobfuscation |
|
| Containers | container_detect(container_bytes) |
ContainerDetection |
container_members(container_bytes) |
ContainerMembers |
|
| Pickle | pickle_disasm(pickle_bytes) |
str |
pickle_decompile(pickle_bytes) |
PickleDecompilation |
|
pickle_safety(pickle_bytes) |
PickleSafety |
|
pickle_trace(pickle_bytes) |
PickleTrace |
|
pickle_polyglot(file_bytes) |
PicklePolyglot |
|
pickle_ml_detect(file_bytes) |
PickleMlReport |
import disrobe
from disrobe import ChainReport
with open("sample.bin", "rb") as fh:
chain: ChainReport = disrobe.auto(fh.read(), max_depth=8)
spec: str | None = chain.spec
pass_count: int = chain.pass_count
terminated: bool = chain.terminated
full_plan: dict[str, object] = chain.rawauto runs the chain detector against raw bytes and returns a ChainReport
carrying the full chain.json plan the CLI produces; it does not write stage
outputs to disk. max_depth must be 1-16; out-of-range values raise
DisrobeError. The registered pass tree covers pyarmor, pyinstaller, nuitka,
py-decompile, py-deob, container, js, jvm, dotnet, wasm, mobile, swift-objc,
and the native packer detector.
| Property | Type |
|---|---|
spec |
str | None |
pass_count |
int |
terminated |
bool |
Language-keyed entry points that fan out to the per-language passes.
def decompile(language: str, source: str | bytes) -> CanonicalSource: ...Wired families: python/pyc (py-decompile), jvm-class/class/java/kotlin
(JVM lifter), lua (register lifter), ruby (YARV/mruby recovery),
php/php-bytecode (eval-chain peel/op-array skeleton),
javascript/js/typescript/ts (unminify). Binary-only targets (go,
swift, wasm) have no single source body; call their structural binding or
parse instead.
import disrobe
from disrobe import CanonicalSource
with open("module.pyc", "rb") as fh:
recovered: CanonicalSource = disrobe.decompile("python-bytecode", fh.read())
source: str | None = recovered.source
language: str | None = recovered.language
produced_by: str | None = recovered.produced_by
confidence: float | None = recovered.confidence| Property | Type |
|---|---|
source |
str | None |
language |
str | None |
produced_by |
str | None |
confidence |
float | None |
def disasm(language: str, source: str | bytes) -> str: ...Returns a rendered instruction listing as text. Wired: python/pyc,
jvm-class/class, dex, beam, hermes, wasm,
ruby/yarv/mruby, and php/php-bytecode. For Lua use
decompile('lua', ...) or parse('lua', ...) instead.
def parse(language: str, source: str | bytes) -> (
dict[str, Any]
| GoAnalysis
| SwiftReport
| JvmClass
| RubyAnalysis
| LuaDecompilation
| PhpDecode
| JsUnminify
): ...Returns a typed report for structural-recovery languages: go -> GoAnalysis,
swift/objc/objective-c -> SwiftReport, kotlin -> JvmClass,
ruby -> RubyAnalysis, lua -> LuaDecompilation, php -> PhpDecode,
javascript/js/typescript/ts -> JsUnminify. Container and bytecode
formats (pyc, jvm-class, dex, wasm, hermes, beam) return a nested
dict[str, Any] because their full parse records have no single typed shape.
import disrobe
from typing import Any
with open("Hello.class", "rb") as fh:
parsed: dict[str, Any] = disrobe.parse("jvm-class", fh.read())
method_count: int = len(parsed["methods"])def compile(language: str, source: str, *, version: str | None = None) -> bytes: ...Implemented for Python only; returns raw marshal.dumps bytes (no .pyc
header) via the host interpreter. lua, ruby, and php raise
UnsupportedLanguage with a hint pointing at the CLI subcommand or toolchain.
import disrobe
blob: bytes = disrobe.compile("python", "x: int = 1 + 2\n")
listing: str = disrobe.disasm("python", "x: int = 1 + 2\n")Register and compose named passes and output consumers in the host process.
from typing import Any
import disrobe
from disrobe import Pass, OutputConsumer
def my_pass(data: Any) -> Any:
return data[::-1]
def my_consumer(result: Any, **context: Any) -> Any:
print(result, context)
disrobe.register_pass("reverse", my_pass)
disrobe.register_consumer("print", my_consumer)
names: list[str] = disrobe.registered_passes()
consumer_names: list[str] = disrobe.registered_consumers()
output: Any = disrobe.run_pass("reverse", b"hello")
chained: Any = disrobe.run_chain(["reverse", "reverse"], b"hello")
disrobe.emit("print", chained, source="example")
removed: bool = disrobe.unregister("reverse")Both are @runtime_checkable protocols.
| Protocol | Signature |
|---|---|
Pass |
__call__(self, data: Any) -> Any |
OutputConsumer |
__call__(self, result: Any, **context: Any) -> Any |
def strings_extract(data: bytes, *, min_len: int = 4, decode: bool = True) -> StringsReport: ...Extracts ASCII and UTF-16 strings from a binary blob.
def ioc_extract(data: bytes) -> IocReport: ...Harvests indicators of compromise from bytes and recovered strings.
def behavior_analyze(data: bytes) -> BehaviorReport: ...Behavioral summary by category with MITRE ATT&CK IDs.
def identify(data: bytes) -> IdentifyReport: ...Compiler/linker/packer/protector/installer fingerprint.
def secret_scan(data: bytes) -> SecretScanReport: ...Leaked-credential scan over raw bytes.
def capabilities(binary_bytes: bytes) -> Capabilities: ...Capability rule-set matches for a native binary.
def extract(data: bytes, out_dir: str) -> ExtractionResult: ...Carves container/firmware members to out_dir.
def extract_recursive(
data: bytes, *, source_label: str = "inline", max_depth: int = 8
) -> OverlayReport: ...Recursive multi-magic carve; classifies every chunk by entropy and nesting.
def yara_parse(ruleset_source: str) -> YaraReport: ...
def yara_generate(data: bytes, *, name: str | None = None) -> YaraReport: ...Parse a YARA ruleset AST or generate a candidate rule from a binary blob.
import disrobe
from disrobe import (
StringsReport, IocReport, BehaviorReport, IdentifyReport,
SecretScanReport, Capabilities, OverlayReport, YaraReport,
)
with open("suspect.bin", "rb") as fh:
data: bytes = fh.read()
strings: StringsReport = disrobe.strings_extract(data, min_len=6)
string_count: int = strings.string_count
iocs: IocReport = disrobe.ioc_extract(data)
indicator_count: int = iocs.indicator_count
behavior: BehaviorReport = disrobe.behavior_analyze(data)
category_count: int = behavior.category_count
ident: IdentifyReport = disrobe.identify(data)
fmt: str | None = ident.format
finding_count: int = ident.finding_count
caps: Capabilities = disrobe.capabilities(data)
match_count: int = caps.match_count
overlay: OverlayReport = disrobe.extract_recursive(data, max_depth=4)
chunks_total: int | None = overlay.chunks_total
bytes_carved: int | None = overlay.bytes_carved
rule: YaraReport = disrobe.yara_generate(data, name="suspect")
rule_count: int = rule.rule_count| Class | Notable typed accessors |
|---|---|
StringsReport |
string_count: int |
IocReport |
indicator_count: int |
BehaviorReport |
category_count: int |
IdentifyReport |
format: str | None, finding_count: int
|
SecretScanReport |
finding_count: int |
Capabilities |
match_count: int, format: str | None
|
ExtractionResult |
kind: str | None, entry_count: int, integrity_violation_count: int
|
OverlayReport |
max_depth: int | None, nodes_visited: int | None, chunks_total: int | None, bytes_carved: int | None
|
YaraReport |
rule_count: int |
| Function | Returns | Notes |
|---|---|---|
native_symbols(data) |
SymbolsReport |
Symbols, sections, imports, debug info |
native_disasm(data) |
DisasmPayload |
Full disassembly: functions, stream, symbols |
native_callgraph(data) |
CallGraph |
Whole-program call graph |
native_imports_dot(data) |
str |
GraphViz DOT of the import graph |
native_entropy(data) |
EntropyReport |
Sliding-window Shannon entropy map |
native_sbom(data) |
SbomReport |
CycloneDX 1.5 SBOM from cargo-auditable section |
native_fingerprint(data, *, flirt=None) |
FingerprintReport |
Crypto-constant + FLIRT + string-xref sidecar |
native_signatures(data, *, flirt=None) |
SignatureReport |
Crypto-primitive signatures and FLIRT matches |
native_sigmaker(data, at) |
SigmakerReport |
Wildcarded byte signature for a VA |
native_diff(a, b) |
DiffReport |
Function-level diff of two binaries |
native_patch(data, *, at, ...) |
tuple[bytes, PatchReport] |
Rewrite bytes and revalidate |
native_format(binary_bytes) |
NativeFormat |
Format: kind, bitness, subsystem |
native_detect(binary_bytes) |
DetectionList |
Packer/protector detection hits |
native_probe_backends() |
BackendList |
Probe for installed external tools |
native_deobfuscate(code, *, bits=64, base=0, entry=0) |
NativeDeobfuscation |
x86 OLLVM/Tigress deflattening |
import disrobe
from disrobe import (
SymbolsReport, DisasmPayload, CallGraph, EntropyReport,
SbomReport, FingerprintReport, SignatureReport, SigmakerReport,
DiffReport, PatchReport, NativeFormat, DetectionList,
BackendList, NativeDeobfuscation,
)
with open("binary.elf", "rb") as fh:
data: bytes = fh.read()
syms: SymbolsReport = disrobe.native_symbols(data)
symbol_count: int = syms.symbol_count
section_count: int = syms.section_count
import_count: int = syms.import_count
disasm_payload: DisasmPayload = disrobe.native_disasm(data)
instruction_count: int = disasm_payload.instruction_count
source_hash: str | None = disasm_payload.source_hash
entropy: EntropyReport = disrobe.native_entropy(data)
mean: float | None = entropy.mean
sig: SigmakerReport = disrobe.native_sigmaker(data, at=0x1000)
ida_pattern: str | None = sig.ida_pattern
patched_bytes: bytes
patch_report: PatchReport
patched_bytes, patch_report = disrobe.native_patch(data, at=0x1234, nop_start=0x1234, nop_end=0x1240)
revalidated: bool = patch_report.revalidated
deob: NativeDeobfuscation = disrobe.native_deobfuscate(data, bits=64, base=0x400000)
recovered_blocks: int | None = deob.recovered_blocks
fully_recovered: bool = deob.fully_recovered| Class | Notable typed accessors |
|---|---|
SymbolsReport |
symbol_count: int, section_count: int, import_count: int
|
DisasmPayload |
instruction_count: int, symbol_count: int, source_hash: str | None
|
CallGraph |
node_count: int, edge_count: int
|
EntropyReport |
window_count: int, mean: float | None, min: float | None, max: float | None
|
SbomReport |
component_count: int, bom_format: str | None, spec_version: str | None
|
FingerprintReport |
crypto_hit_count: int |
SignatureReport |
signature_count: int |
SigmakerReport |
ida_pattern: str | None, byte_count: int
|
DiffReport |
added: int, removed: int, changed: int
|
PatchReport |
at: int | None, bytes_written: int | None, revalidated: bool
|
NativeFormat |
kind: str | None, bits: int | None, subsystem: str | None
|
DetectionList |
count: int |
BackendList |
count: int, available_count: int
|
NativeDeobfuscation |
bits: int | None, recovered_blocks: int | None, original_blocks: int | None, fully_recovered: bool
|
The query functions operate on a Disasm-rung .dr envelope (raw bytes). See
Editable IR objects for how to produce and consume .dr
envelopes programmatically.
| Function | Returns | Notes |
|---|---|---|
query_functions(dr_bytes) |
FunctionList |
All recovered functions |
query_calls_to(dr_bytes, target) |
QueryReport |
Call sites targeting a symbol name |
query_xrefs_to(dr_bytes, symbol) |
QueryReport |
Data/code cross-references to a symbol |
query_string_decoders(dr_bytes) |
QueryReport |
Functions with string-decode patterns |
query_complexity_over(dr_bytes, threshold) |
QueryReport |
Functions with cyclomatic complexity above threshold |
query_capability_sites(dr_bytes, capability) |
QueryReport |
Sites exercising a named capability |
query_call_graph(dr_bytes) |
CallGraph |
Whole-program call graph from IR |
import disrobe
from disrobe import FunctionList, QueryReport, CallGraph
with open("module.dr", "rb") as fh:
dr: bytes = fh.read()
functions: FunctionList = disrobe.query_functions(dr)
fn_count: int = functions.count
callers: QueryReport = disrobe.query_calls_to(dr, "malloc")
match_count: int = callers.match_count
complex_fns: QueryReport = disrobe.query_complexity_over(dr, threshold=20)
graph: CallGraph = disrobe.query_call_graph(dr)
edge_count: int = graph.edge_count| Class | Notable typed accessors |
|---|---|
FunctionList |
kind: str | None, count: int
|
QueryReport |
kind: str | None, match_count: int
|
CallGraph |
node_count: int, edge_count: int
|
envelope_create wraps a payload as a Raw-rung .dr envelope and returns the
encoded bytes. envelope_verify decodes and verifies, returning an
EnvelopeReport.
import disrobe
from disrobe import EnvelopeReport
envelope: bytes = disrobe.envelope_create(
b"payload",
source_label="inline",
produced_by="my-tool",
detected_format="elf64",
)
report: EnvelopeReport = disrobe.envelope_verify(envelope)
ok: bool = report.verified
root_hash: str | None = report.root_hash
rung: str | None = report.rung
hot_bytes: int | None = report.hot_bytes
cold_bytes: int | None = report.cold_bytes
version: int | None = report.version| Property | Type |
|---|---|
verified |
bool |
rung |
str | None |
version |
int | None |
hot_bytes |
int | None |
cold_bytes |
int | None |
root_hash |
str | None |
The sidecar DrEnvelope TypedDict (bindings/python/dr-envelope.pyi) mirrors
the raw on-disk header shape: magic, version, rung, flags, hot_len,
cold_len, root_hash.
agents_md and skill_md render the AGENTS.md and SKILL.md reconstruction
briefs for a report from an LLM-enabled pass (or a bare bundle dict), returning
a str. provenance extracts tool/selection/input metadata as a typed
Provenance. Passing a report whose llm slot is None raises DisrobeError.
import disrobe
from disrobe import PyDecompileReport, Provenance
with open("module.pyc", "rb") as fh:
report: PyDecompileReport = disrobe.py_decompile(fh.read(), pack="pack-2")
agents_brief: str = disrobe.agents_md(report)
skill_brief: str = disrobe.skill_md(report)
prov: Provenance = disrobe.provenance(report)
generated_at: str | None = prov.generated_at
schema: str | None = prov.schema
schema_version: str | None = prov.schema_version| Property | Type |
|---|---|
schema |
str | None |
schema_version |
str | None |
generated_at |
str | None |
See also Python decompiler for the full decompiler design.
Decompiles a .pyc (with header) to source. 92.76% of CPython 3.14 stdlib
code objects recompile to bytecode-equivalent output (5831 of 6286, CI floor
90%). Legacy CPython 1.0-3.7: 79.6% proven-correct (CI floor 152 of 191;
166 of 191 with the full interpreter zoo present).
import disrobe
from disrobe import PyDecompileReport, RoundtripStatus
with open("module.pyc", "rb") as fh:
report: PyDecompileReport = disrobe.py_decompile(fh.read(), roundtrip=True)
source: str | None = report.source
marshal_version: str | None = report.marshal_version
decompile_version: str | None = report.decompile_version
recovered_directly: bool = report.recovered_directly
fallback_reason: str | None = report.fallback_reason
status: RoundtripStatus | None = report.roundtrip_status
roundtrip_detail: str | None = report.roundtrip_detail
interpreter_path: str | None = report.interpreter_path
interpreter_version: str | None = report.interpreter_version
if status == "perfect":
print("recompiled bytecode matched")Round-tripping (when roundtrip=True) shells out to a matching host
interpreter; it is the one binding that may run an external python.
import disrobe
from disrobe import PyDisasmReport
with open("module.pyc", "rb") as fh:
result: PyDisasmReport = disrobe.py_disasm(fh.read())
marshal_version: str | None = result.marshal_version
instruction_count: int = result.instruction_count
text: str | None = result.textimport disrobe
from disrobe import ObfuscatorPass, PyDeobDetection, PyDeobReport
obfuscated: str = "exec(__import__('base64').b64decode('cHJpbnQoMSk='))\n"
deob: PyDeobReport = disrobe.py_deob(obfuscated, cleanup=True)
peeled_source: str | None = deob.peeled_source
cleanup_source: str | None = deob.cleanup_source
layer_count: int = deob.layer_count
detection: PyDeobDetection = disrobe.py_deob_detect(obfuscated)
match_count: int = detection.match_count
passes: list[ObfuscatorPass] = disrobe.py_deob_list_passes()
first_id: str | None = passes[0].id if passes else None
per_pass: PyDeobDetection = disrobe.py_deob_detect_pass(obfuscated, "base64-exec")py_deob_detect_pass raises DisrobeError for an unknown pass_id.
| Class | Notable typed accessors |
|---|---|
PyDecompileReport |
source, marshal_version, decompile_version, recovered_directly, fallback_reason, roundtrip_status, roundtrip_detail, interpreter_path, interpreter_version, llm
|
PyDisasmReport |
marshal_version: str | None, instruction_count: int, text: str | None, llm
|
PyDeobReport |
peeled_source: str | None, cleanup_source: str | None, layer_count: int, llm
|
PyDeobDetection |
match_count: int, llm
|
ObfuscatorPass |
id: str | None |
Parses a PyArmor wrapper from source text.
import disrobe
from disrobe import PyarmorDetection
detection: PyarmorDetection = disrobe.pyarmor_detect(open("wrapped.py").read())
version: str | None = detection.version
protection: str | None = detection.protection
confidence: str | None = detection.confidence
serial: str | None = detection.serial
python_major: int | None = detection.python_major
python_minor: int | None = detection.python_minor
payload_offset: int | None = detection.payload_offset
payload_size: int | None = detection.payload_sizeStatically unpacks a PyArmor wrapper image. 72 of 72 PyArmor samples (v6-v9)
recover. The bindings expose only the static path; there is no
--allow-dynamic equivalent.
import disrobe
from disrobe import PyarmorUnpack, PyarmorUnpackStatus
with open("wrapper.pyc", "rb") as fh:
unpacked: PyarmorUnpack = disrobe.pyarmor_unpack(fh.read())
status: PyarmorUnpackStatus | None = unpacked.status
pyarmor_version: str | None = unpacked.pyarmor_version
protection_kind: str | None = unpacked.protection_kind
plaintext_len: int | None = unpacked.plaintext_len
digest: str | None = unpacked.plaintext_blake3_hex
bcc_blob_count: int | None = unpacked.bcc_blob_count
inner_cipher_recovered_co: int | None = unpacked.inner_cipher_recovered_coimport disrobe
from disrobe import PyarmorClassification
with open("payload.bin", "rb") as fh:
payload: bytes = fh.read()
classification: PyarmorClassification = disrobe.pyarmor_classify(open("wrapped.py").read(), payload)
script_type: str | None = classification.script_type
bootstrap_import: str | None = classification.bootstrap_import
disposition: str | None = classification.disposition
rft_enabled: bool = classification.rft_enabled
ecc_enabled: bool = classification.ecc_enabledThe sidecar PyarmorDetection TypedDict (bindings/python/pyarmor-detection.pyi)
names the confidence and protection Literal values used in the raw dict.
| Class | Notable typed accessors |
|---|---|
PyarmorDetection |
version, protection, confidence, serial, python_major, python_minor, payload_offset, payload_size, llm
|
PyarmorUnpack |
status, pyarmor_version, protection_kind, plaintext_len, plaintext_blake3_hex, bcc_blob_count, inner_cipher_recovered_co, llm
|
PyarmorClassification |
script_type, bootstrap_import, disposition, rft_enabled, ecc_enabled
|
import disrobe
from disrobe import PyInstallerArchive
with open("app.exe", "rb") as fh:
image: bytes = fh.read()
archive: PyInstallerArchive = disrobe.pyinstaller_extract(image)
entry_count: int = archive.entry_count
encrypted: bool = archive.encrypted
encryption_key_present: bool = archive.encryption_key_present
python_major: int | None = archive.python_major
python_minor: int | None = archive.python_minor
entries: list[dict[str, object]] = archive.raw["entries"]
main_payload: bytes = disrobe.pyinstaller_entry_bytes(image, str(entries[0]["name"]))import disrobe
from disrobe import NuitkaDetection, NuitkaExtraction
with open("app.exe", "rb") as fh:
image: bytes = fh.read()
det: NuitkaDetection = disrobe.nuitka_detect(image)
flavor: str | None = det.flavor
version: str | None = det.version
wheel_marker: str | None = det.wheel_marker
onefile_payload_offset: int | None = det.onefile_payload_offset
onefile_payload_compressed: bool = det.onefile_payload_compressed
extraction: NuitkaExtraction = disrobe.nuitka_extract(image)
variant: str | None = extraction.variantThe sidecar FreezerManifest TypedDict (bindings/python/freezer-manifest.pyi)
describes the manifest schema for cx-freeze/py2exe/shiv/pex/py-oxidizer/briefcase
freezer families; reach it via report.raw.
| Class | Notable typed accessors |
|---|---|
PyInstallerArchive |
entry_count: int, encrypted: bool, encryption_key_present: bool, python_major: int | None, python_minor: int | None, llm
|
NuitkaDetection |
flavor: str | None, version: str | None, wheel_marker: str | None, onefile_payload_offset: int | None, onefile_payload_compressed: bool, llm
|
NuitkaExtraction |
variant: str | None, llm
|
All 8 functions in the committed hermesc-built HBC v96 sample lift at 100% op-coverage with 0 fallback ops. 122,633 functions lift with no failure on a production React Native bundle.
import disrobe
from disrobe import HermesDisassembly, HermesLift, HermesInfo
with open("index.android.bundle", "rb") as fh:
bundle: bytes = fh.read()
disasm_result: HermesDisassembly = disrobe.hermes_disasm(bundle)
function_count: int = disasm_result.function_count
identifier_count: int = disasm_result.identifier_count
string_count: int = disasm_result.string_count
lift: HermesLift = disrobe.hermes_lift(bundle)
function_surface_count: int = lift.function_surface_count
info: HermesInfo = disrobe.hermes_info(bundle)
version: int | None = info.version
header_size: int | None = info.header_size| Class | Notable typed accessors |
|---|---|
HermesDisassembly |
function_count: int, identifier_count: int, string_count: int, llm
|
HermesLift |
function_surface_count: int, string_count: int, identifier_count: int, llm
|
HermesInfo |
version: int | None, function_count: int | None, string_count: int | None, header_size: int | None, llm
|
import disrobe
from disrobe import MachoReport, SwiftReport
with open("universal.dylib", "rb") as fh:
data: bytes = fh.read()
macho: MachoReport = disrobe.macho_dump(data)
kind: str | None = macho.kind
fat_entry_count: int = macho.fat_entry_count
slice_count: int = macho.slice_count
swift: SwiftReport = disrobe.swift_analyze(data)
container: str | None = swift.container
swift_fat_entry_count: int = swift.fat_entry_count
swift_slice_count: int = swift.slice_count| Class | Notable typed accessors |
|---|---|
MachoReport |
kind: str | None, fat_entry_count: int, slice_count: int, llm
|
SwiftReport |
container: str | None, fat_entry_count: int, slice_count: int, llm
|
93.1% of JVM methods recompile error-free under javac (CI floor 122 of 131;
128 of 131 measured with JDK 25). 99% of committed DEX classes pass -Xverify:all.
import disrobe
from disrobe import (
JvmClass, DexFileReport, JvmDecompiledClass,
DetectionList, JvmBackends, ApkResources,
)
with open("Hello.class", "rb") as fh:
cls: JvmClass = disrobe.jvm_parse_class(fh.read())
major_version: int | None = cls.major_version
minor_version: int | None = cls.minor_version
method_count: int = cls.method_count
field_count: int = cls.field_count
constant_pool_count: int = cls.constant_pool_count
with open("classes.dex", "rb") as fh:
dex: DexFileReport = disrobe.jvm_parse_dex(fh.read())
class_count: int = dex.class_count
dex_method_count: int = dex.method_count
with open("Hello.class", "rb") as fh:
decompiled: JvmDecompiledClass = disrobe.jvm_decompile_class(fh.read())
source: str | None = decompiled.source
fully_lifted_methods: int = decompiled.fully_lifted_methods
fallback_methods: int = decompiled.fallback_methods
detections: DetectionList = disrobe.jvm_detect(open("obf.class", "rb").read())
detection_count: int = detections.count
backends: JvmBackends = disrobe.jvm_backends()
jvm_count: int = backends.jvm_count
android_count: int = backends.android_count
with open("app.apk", "rb") as fh:
apk: ApkResources = disrobe.apk_resources(fh.read())
package: str | None = apk.package
manifest_xml: str | None = apk.manifest_xml
resource_entry_count: int = apk.resource_entry_count
certificate_count: int = apk.certificate_count
dex_count: int = apk.dex_countjvm_backends and dotnet_backends probe the host for installed external
tools but never shell out to them. Counts are informational only.
| Class | Notable typed accessors |
|---|---|
JvmClass |
major_version: int | None, minor_version: int | None, method_count: int, field_count: int, constant_pool_count: int, llm
|
DexFileReport |
string_count: int, type_count: int, class_count: int, method_count: int, llm
|
JvmDecompiledClass |
source: str | None, method_count: int, field_count: int, fully_lifted_methods: int, fallback_methods: int
|
DetectionList |
count: int |
JvmBackends |
jvm_count: int, android_count: int, llm
|
ApkResources |
package: str | None, manifest_xml: str | None, resource_entry_count: int, certificate_count: int, dex_count: int, llm
|
import disrobe
from disrobe import (
DotnetPe, DotnetMetadata, DotnetDetection,
DotnetAnalysis, DotnetDecompilation, DotnetDecoders, BackendList,
)
with open("Sample.dll", "rb") as fh:
pe_bytes: bytes = fh.read()
pe: DotnetPe = disrobe.dotnet_parse_pe(pe_bytes)
bitness: str | None = pe.bitness
machine: int | None = pe.machine
section_count: int = pe.section_count
entry_point_rva: int | None = pe.entry_point_rva
metadata: DotnetMetadata = disrobe.dotnet_parse_metadata(pe_bytes)
version: str | None = metadata.version
major_runtime_version: int | None = metadata.major_runtime_version
stream_count: int = metadata.stream_count
detection: DotnetDetection = disrobe.dotnet_detect(pe_bytes)
primary: str | None = detection.primary
match_count: int = detection.match_count
analysis: DotnetAnalysis = disrobe.dotnet_analyze(pe_bytes)
pe_bitness: str | None = analysis.pe_bitness
native_aot: bool = analysis.native_aot
primary_protector: str | None = analysis.primary_protector
opcode_spec_coverage_pct: int | None = analysis.opcode_spec_coverage_pct
decompilation: DotnetDecompilation = disrobe.dotnet_decompile(pe_bytes)
module_name: str | None = decompilation.module_name
methods_decompiled: int | None = decompilation.methods_decompiled
methods_bodyless: int | None = decompilation.methods_bodyless
methods_failed: int | None = decompilation.methods_failed
decoders: DotnetDecoders = disrobe.dotnet_recover_decoders(pe_bytes)
pure_decoders_found: int | None = decoders.pure_decoders_found
constants_recovered: int = decoders.constants_recovered
backend_list: BackendList = disrobe.dotnet_backends()
available_count: int = backend_list.available_count| Class | Notable typed accessors |
|---|---|
DotnetPe |
bitness: str | None, machine: int | None, section_count: int, entry_point_rva: int | None, llm
|
DotnetMetadata |
version: str | None, major_runtime_version: int | None, stream_count: int, llm
|
DotnetDetection |
primary: str | None, match_count: int, llm
|
DotnetAnalysis |
pe_bitness: str | None, clr_runtime_version: str | None, native_aot: bool, primary_protector: str | None, opcode_spec_coverage_pct: int | None, llm
|
DotnetDecompilation |
module_name: str | None, methods_decompiled: int | None, methods_bodyless: int | None, methods_failed: int | None, llm
|
DotnetDecoders |
pure_decoders_found: int | None, constants_recovered: int, llm
|
100% op-coverage on 94 functions across 30 parseable corpus modules. 24 of 24 execution-eligible functions are execution-equivalent under wasmtime.
import disrobe
from disrobe import WasmAnalysis, WasmDetection
with open("module.wasm", "rb") as fh:
wasm_bytes: bytes = fh.read()
analysis: WasmAnalysis = disrobe.wasm_analyze(wasm_bytes)
import_count: int = analysis.import_count
export_count: int = analysis.export_count
func_count: int | None = analysis.func_count
code_size_bytes: int | None = analysis.code_size_bytes
has_dwarf: bool = analysis.has_dwarf
detection: WasmDetection = disrobe.wasm_detect(wasm_bytes)
obfuscator: str | None = detection.obfuscator
confidence: float | None = detection.confidence
has_name_section: bool = detection.has_name_section
function_count: int | None = detection.function_count| Class | Notable typed accessors |
|---|---|
WasmAnalysis |
import_count: int, export_count: int, func_count: int | None, code_size_bytes: int | None, has_dwarf: bool, llm
|
WasmDetection |
obfuscator: str | None, confidence: float | None, has_name_section: bool, function_count: int | None, llm
|
Supports 11 bundlers: webpack4, webpack5/webpack, vite, rollup,
rolldown, esbuild, turbopack, bun, browserify, parcel,
systemjs. An unrecognised hint string raises DisrobeError.
import disrobe
from disrobe import JsDetection, JsUnminify, JsUnbundle
source: str = open("main.js").read()
detection: JsDetection = disrobe.js_detect(source)
family: str | None = detection.family
confidence: float | None = detection.confidence
marker_count: int = detection.marker_count
unminified: JsUnminify = disrobe.js_unminify(source)
recovered_source: str | None = unminified.source
bundle_source: str = open("bundle.js").read()
unbundled: JsUnbundle = disrobe.js_unbundle(bundle_source)
module_count: int = unbundled.module_count
bundler: str | None = unbundled.bundler
unbundled_hinted: JsUnbundle = disrobe.js_unbundle(bundle_source, bundler="webpack5")| Class | Notable typed accessors |
|---|---|
JsDetection |
family: str | None, confidence: float | None, marker_count: int, llm
|
JsUnminify |
source: str | None, llm
|
JsUnbundle |
module_count: int, bundler: str | None, llm
|
Detects, decompiles, and deobfuscates 11 Lua obfuscator families. IronBrew2 2.7.0 is reversed against real committed output with a Lua execution differential.
import disrobe
from disrobe import LuaDetection, LuaDecompilation, LuaDeobfuscation
with open("chunk.luac", "rb") as fh:
bytecode: bytes = fh.read()
det: LuaDetection = disrobe.lua_detect(bytecode)
lua_format: str | None = det.format
decompiled: LuaDecompilation = disrobe.lua_decompile(bytecode)
decompiled_source: str | None = decompiled.source
fidelity: str | None = decompiled.fidelity
warning_count: int = decompiled.warning_count
deob: LuaDeobfuscation = disrobe.lua_deobfuscate(open("obf.lua").read(), authorize=True)
obfuscator: str | None = deob.obfuscator
deobfuscated: str | None = deob.deobfuscated
fully_recovered: bool = deob.fully_recovered
passes_run_count: int = deob.passes_run_count
recovered_string_count: int = deob.recovered_string_count| Class | Notable typed accessors |
|---|---|
LuaDetection |
format: str | None, llm
|
LuaDecompilation |
source: str | None, fidelity: str | None, warning_count: int, llm
|
LuaDeobfuscation |
obfuscator: str | None, deobfuscated: str | None, fully_recovered: bool, passes_run_count: int, recovered_string_count: int, llm
|
85%+ type-name recovery on stripped go1.26 fixtures; 528 of 528 measured.
import disrobe
from disrobe import GoAnalysis, GoSymbols, GoPclntab, GarbleReport
with open("binary", "rb") as fh:
go_bytes: bytes = fh.read()
analysis: GoAnalysis = disrobe.go_analyze(go_bytes)
image_kind: str | None = analysis.image_kind
pclntab_version: str | None = analysis.pclntab_version
buildversion: str | None = analysis.buildversion
ptr_size: int | None = analysis.ptr_size
symbols: GoSymbols = disrobe.go_symbols(go_bytes)
version_label: str | None = symbols.version_label
function_count: int = symbols.function_count
source_file_count: int = symbols.source_file_count
package_count: int = symbols.package_count
pclntab: GoPclntab = disrobe.go_pclntab(go_bytes)
version: str | None = pclntab.version
func_count: int | None = pclntab.func_count
garble: GarbleReport = disrobe.go_garble(go_bytes)
quality: str | None = garble.quality
detection_score: int | None = garble.detection_score
seed_recoverable: bool = garble.seed_recoverable
seed_hash: str | None = garble.seed_hash
recovered_string_count: int = garble.recovered_string_count| Class | Notable typed accessors |
|---|---|
GoAnalysis |
image_kind: str | None, pclntab_version: str | None, buildversion: str | None, ptr_size: int | None, llm
|
GoSymbols |
version_label: str | None, function_count: int, source_file_count: int, package_count: int, llm
|
GoPclntab |
version: str | None, ptr_size: int | None, func_count: int | None, image_kind: str | None, llm
|
GarbleReport |
quality: str | None, detection_score: int | None, seed_recoverable: bool, seed_hash: str | None, recovered_string_count: int, llm
|
import disrobe
from disrobe import RubyDetection, RubyAnalysis
with open("hello.rb.enc", "rb") as fh:
ruby_bytes: bytes = fh.read()
det: RubyDetection = disrobe.ruby_detect(ruby_bytes, source_path="hello.rb.enc")
flavor: str | None = det.flavor
analysis: RubyAnalysis = disrobe.ruby_decompile(ruby_bytes, source_path="hello.rb.enc")
ruby_flavor: str | None = analysis.flavor
source_path: str | None = analysis.source_path
input_len: int | None = analysis.input_len| Class | Notable typed accessors |
|---|---|
RubyDetection |
flavor: str | None, llm
|
RubyAnalysis |
flavor: str | None, source_path: str | None, input_len: int | None, llm
|
import disrobe
from disrobe import PhpDetection, PhpScan, PhpDecode
with open("obfuscated.php", "rb") as fh:
php_bytes: bytes = fh.read()
det: PhpDetection = disrobe.php_detect(php_bytes)
kind: str | None = det.kind
confidence: str | None = det.confidence
open_tag_offset: int | None = det.open_tag_offset
has_halt_compiler: bool = det.has_halt_compiler
scan: PhpScan = disrobe.php_scan(php_bytes)
hit_count: int = scan.hit_count
family_count: int = scan.family_count
decoded: PhpDecode = disrobe.php_decode(php_bytes, max_depth=10)
php_source: str | None = decoded.source
layer_count: int = decoded.layer_count
residual_eval: bool = decoded.residual_eval| Class | Notable typed accessors |
|---|---|
PhpDetection |
kind: str | None, confidence: str | None, open_tag_offset: int | None, has_halt_compiler: bool, llm
|
PhpScan |
hit_count: int, family_count: int, llm
|
PhpDecode |
source: str | None, layer_count: int, residual_eval: bool, llm
|
import disrobe
from disrobe import BatchDeobReport, PowershellDetection, PowershellDeobfuscation
batch_script: str = open("dropper.bat").read()
batch_result: BatchDeobReport = disrobe.batch_deobfuscate(batch_script, args=["/run"])
output: str | None = batch_result.output
embedded_payload_count: int = batch_result.embedded_payload_count
decrypted_stage_count: int = batch_result.decrypted_stage_count
commands_emulated: int | None = batch_result.commands_emulated
ps_script: str = open("obf.ps1").read()
ps_det: PowershellDetection = disrobe.powershell_detect(ps_script)
obfuscator: str | None = ps_det.obfuscator
ps_confidence: float | None = ps_det.confidence
marker_count: int = ps_det.marker_count
ps_deob: PowershellDeobfuscation = disrobe.powershell_deobfuscate(ps_script)
ps_output: str | None = ps_deob.output
level: str | None = ps_deob.level
transformation_count: int = ps_deob.transformation_count| Class | Notable typed accessors |
|---|---|
BatchDeobReport |
output: str | None, embedded_payload_count: int, decrypted_stage_count: int, commands_emulated: int | None, llm
|
PowershellDetection |
obfuscator: str | None, confidence: float | None, marker_count: int, llm
|
PowershellDeobfuscation |
output: str | None, level: str | None, transformation_count: int, llm
|
98 container families detected and extracted in-tree. See container docs for the full family list.
import disrobe
from disrobe import ContainerDetection, ContainerMembers, ContainerListing
with open("archive.zip", "rb") as fh:
container_bytes: bytes = fh.read()
det: ContainerDetection = disrobe.container_detect(container_bytes)
detected: bool = det.detected
kind: str | None = det.kind
is_zip_family: bool = det.is_zip_family
members: ContainerMembers = disrobe.container_members(container_bytes)
fmt: str | None = members.format
size: int | None = members.size
listing: ContainerListing | None = members.listing
entry_count: int = members.entry_count| Class | Notable typed accessors |
|---|---|
ContainerDetection |
detected: bool, kind: str | None, is_zip_family: bool, llm
|
ContainerMembers |
format: str | None, size: int | None, listing: ContainerListing | None, entry_count: int, llm
|
Nothing is ever unpickled; the VM is symbolic.
import disrobe
from disrobe import (
PickleDecompilation, PickleSafety, PickleTrace,
PicklePolyglot, PickleMlReport,
)
with open("model.pkl", "rb") as fh:
pkl: bytes = fh.read()
listing: str = disrobe.pickle_disasm(pkl)
decompilation: PickleDecompilation = disrobe.pickle_decompile(pkl)
pkl_source: str | None = decompilation.source
safety: PickleSafety = disrobe.pickle_safety(pkl)
severity: str | None = safety.severity
finding_count: int = safety.finding_count
import_count: int = safety.import_count
reduce_count: int | None = safety.reduce_count
trace: PickleTrace = disrobe.pickle_trace(pkl)
protocol: int | None = trace.protocol
memo_count: int | None = trace.memo_count
max_stack_depth: int | None = trace.max_stack_depth
global_ref_count: int = trace.global_ref_count
trace_reduce_count: int | None = trace.reduce_count
polyglot: PicklePolyglot = disrobe.pickle_polyglot(pkl)
is_pickle: bool = polyglot.is_pickle
is_polyglot: bool = polyglot.is_polyglot
kind_count: int = polyglot.kind_count
with open("model.pt", "rb") as fh:
ml_report: PickleMlReport = disrobe.pickle_ml_detect(fh.read())
fmt: str | None = ml_report.format
framing: str | None = ml_report.framing
embedded_count: int = ml_report.embedded_count| Class | Notable typed accessors |
|---|---|
PickleDecompilation |
source: str | None, llm
|
PickleSafety |
severity: str | None, finding_count: int, import_count: int, reduce_count: int | None, llm
|
PickleTrace |
protocol: int | None, memo_count: int | None, max_stack_depth: int | None, global_ref_count: int, reduce_count: int | None, llm
|
PicklePolyglot |
is_pickle: bool, is_polyglot: bool, kind_count: int, llm
|
PickleMlReport |
format: str | None, framing: str | None, embedded_count: int, llm
|
CodeObject, Instruction, and Symbol let you load a Disasm-rung .dr
envelope, modify it in Python, and write a fresh integrity-hashed .dr.
from disrobe import Instruction, InstructionFlow
instr: Instruction = Instruction(
offset=0,
mnemonic="mov",
operands=["rax", "rbx"],
bytes=b"\x48\x89\xd8",
)
instr.branch_target = None
flow: InstructionFlow = instr.flow
text: str = instr.text()| Member | Type | Notes |
|---|---|---|
offset |
int |
Mutable |
mnemonic |
str |
Mutable |
operands |
list[str] |
Mutable |
bytes |
bytes |
Mutable |
branch_target |
int | None |
Mutable |
flow |
@property InstructionFlow |
Read-only |
text() |
-> str |
Rendered disassembly line |
from disrobe import Symbol, SymbolKind
sym: Symbol = Symbol(address=0x1000, name="entry", kind="function")
sym.name = "main"
sym.kind = "export"| Member | Type | Notes |
|---|---|---|
address |
int |
Mutable |
name |
str |
Mutable |
kind |
SymbolKind |
Mutable |
import disrobe
from disrobe import CodeObject, Instruction, Symbol
with open("module.dr", "rb") as fh:
co: CodeObject = CodeObject.from_dr(fh.read())
instruction_count: int = co.instruction_count
symbol_count: int = co.symbol_count
source_hash: str = co.source_hash
produced_by: str = co.produced_by
instrs: list[Instruction] = co.instructions
syms: list[Symbol] = co.symbols
metadata: dict[str, str] = co.metadata
capabilities: list[str] = co.capabilities
llm_bundle: dict[str, object] | None = co.llm
new_sym: Symbol = Symbol(address=0x2000, name="renamed_fn", kind="function")
co.add_symbol(new_sym)
co.set_metadata("analysis", "patched")
co.add_capability("NETWORK_CONNECT", 1)
fresh_dr: bytes = co.to_dr()
with open("module_patched.dr", "wb") as fh:
fh.write(fresh_dr)CodeObject.from_dr parses a Disasm-rung .dr envelope. to_dr produces a
fresh envelope with a recomputed integrity hash. The set_instructions and
set_symbols methods replace the full list; add_instruction / add_symbol
append. set_metadata(key, value) sets a single string key; clear_metadata
resets all. set_llm(sidecar) attaches or removes the LLM sidecar dict.
| Member | Signature | Notes |
|---|---|---|
from_dr |
staticmethod(dr_bytes: bytes) -> CodeObject |
Parse existing envelope |
instructions |
@property -> list[Instruction] |
|
set_instructions |
(instructions: list[Instruction]) -> None |
Replace |
add_instruction |
(instruction: Instruction) -> None |
Append |
symbols |
@property -> list[Symbol] |
|
set_symbols |
(symbols: list[Symbol]) -> None |
Replace |
add_symbol |
(symbol: Symbol) -> None |
Append |
instruction_count |
@property -> int |
|
symbol_count |
@property -> int |
|
source_hash |
str |
Mutable attribute |
produced_by |
str |
Mutable attribute |
metadata |
@property -> dict[str, str] |
|
set_metadata |
(key: str, value: str) -> None |
|
clear_metadata |
() -> None |
|
capabilities |
@property -> list[str] |
|
add_capability |
(name: str, major: int) -> None |
|
llm |
@property -> dict[str, Any] | None |
|
set_llm |
(sidecar: dict[str, Any] | None) -> None |
|
to_dr |
() -> bytes |
Produce fresh integrity-hashed envelope |
- No file or directory handling: no
--outtrees, no--capture-stages, no container extraction to disk.autoreturns the plan document only. - External backend tools (
jvm_backends,dotnet_backends,native_probe_backends) are probed for availability but never executed. - AS3, Flutter, BEAM (beyond
disasm/parse), and the freezer family beyond PyInstaller/Nuitka have no dedicated bindings in this release. - No SARIF/NDJSON emitters and no
servedaemon; drive the CLI or daemon directly.
This wiki is generated from docs/src in the disrobe repository by scripts/wiki_sync.py. Edit the docs there, not the wiki pages here.
Getting started
Architecture
- Overview
- The five-rung IR ladder
- Passes and the capability model
- The chain runner
- The .dr envelope
- LLM sidecar and provenance
Reverse-engineering toolkit
Language and format guides
- Python
- JavaScript / TypeScript
- WebAssembly
- JVM and Android
- .NET / CIL
- Native (PE / ELF / Mach-O)
- Go
- Lua
- PHP
- Ruby
- BEAM (Erlang / Elixir)
- Swift / Objective-C
- ActionScript 3 / Flash
- Mobile (Hermes / Flutter)
- Python pickle
- Shell / PowerShell
- Containers and archives
Reference
- CLI overview
- Global flags
- Command reference
- Project configuration
- Batch directory processing
- Run reports
- Analysis-depth commands
- Diff and guard tooling
- The daemon: HTTP, gRPC, LSP, MCP
- Use it as a library
- Python bindings
- The browser playground
- Forensics and malware-safety posture
- Threat model
Integrations
Project