native - 1-3-7/disrobe GitHub Wiki

Native (PE / ELF / Mach-O / NE)

disrobe includes native decompilation for x86-64, AArch64, ARM32, and MIPS32, alongside symbol recovery, disassembly, identification, patching, fingerprinting, and diffing. It can export recovered information to Ghidra and IDA, and drive Ghidra headlessly. Architecture-specific output and limits are described in the decompile guide.

Two adjacent surfaces have their own pages: native decompile for the in-tree decompiler and the Ghidra backend, and native unpacking for packers, protectors, and bytecode-VM devirtualization.

At a glance

Surface Support
Containers PE, ELF, Mach-O, Windows 3.x NE, OS/2 1.x NE, plus a flat code blob with --raw
Architectures x86 / ARM / RISC-V / MIPS / PowerPC / SPARC / eBPF / AVR
Debug formats DWARF, PDB, STABS
Demangling Rust and C++ symbols, with the C++ class hierarchy recovered from RTTI and vtable layout
Function discovery Call-target and prologue scanning on stripped input, basic-block partition, whole-program call graph
Identification Compilers, packers, protectors, installers, linkers, and code-signing, each finding routed to the pass that handles it
Authenticode Digest recomputation and PKCS#7 chain walk to a single verdict from nine outcomes
Queryable IR functions, calls-to, xrefs-to, string-decoders, complexity-over, capability, plus address- and value-keyed xrefs-to 0x<rva>, const-users and field-users on x86 and x86-64 images
Function-scoped output native disasm and native decompile for one function (--function <rva>) or one byte range (--range <start>:<end>), bounded by .pdata unwind ranges or function discovery, with RIP-relative constant loads annotated
Capabilities MITRE ATT&CK technique and MBC ID mapping, with matching instruction offsets as per-rule evidence
String recovery Static scan plus decoder execution through the in-house x86 emulator; scoped x86-64 PE listings annotate immediate-index accessor calls with recovered literals
Editing Byte patch and nop-range at a virtual address, wildcarded signature generation, cross-build function diff
Object models Delphi and C++Builder classes with parents, published properties, fields, methods, dynamic method handlers and interfaces, enumeration members, string literals, DFM form resources, and compiler release identification
Forensics Entropy map (text / JSON / SVG), crypto and FLIRT signatures, import/export graph, CycloneDX SBOM

Commands

disrobe native symbols app.exe --out symbols.json            # symbols, sections, imports, debug info, RTTI

disrobe native disasm app.exe --out app.asm                   # recovered per-function listing
disrobe native disasm app.exe --emit cfg-dot --out cfg.dot    # per-function basic-block CFG
disrobe native disasm app.exe --emit json --out disasm.json   # structured instruction stream
disrobe native disasm --raw shellcode.bin --base 0x1000 --bits 64 --syntax intel   # linear sweep
disrobe native disasm app.exe --function 0x1040               # one function, constants annotated
disrobe native disasm app.exe --range 0x1040:0x10a3 --json    # one byte range as a JSON document
disrobe native decompile app.exe --function 0x1040            # one function to C on stdout
disrobe native callgraph app.exe --out callgraph.dot          # whole-program call graph

disrobe native patch app.exe --at 0x1400 --bytes 0x90,0x90 --out patched.exe   # rewrite + revalidate
disrobe native patch app.exe --nop-range 0x1400:0x1410 --out patched.exe        # nop a span
disrobe native sigmaker app.exe --at 0x1400                                     # wildcarded signature
disrobe native diff old.exe new.exe                                             # match functions across builds

disrobe native identify app.exe --out identity.json           # compiler, packer, protector, signature verdict
disrobe native decompile app.exe --neutralize skip --out out/ # also write the anti-debug patch plan (neutralization.json)
disrobe native decompile app.exe --neutralize apply --out out/ # write out/app.neutralized.exe with every planned patch

disrobe query app.exe functions                         # discovered functions, complexity, exports
disrobe query app.exe calls-to malloc                   # call sites to a target
disrobe query app.exe xrefs-to sekret                   # references to a symbol
disrobe query app.exe string-decoders                   # decoder-shaped functions (loops + byte arith)
disrobe query app.exe complexity-over 20                # functions over a cyclomatic threshold
disrobe query app.exe capability network                # instructions tied to a capability
disrobe query app.exe xrefs-to 0x3004                   # every instruction that references an RVA
disrobe query app.exe const-users f32:0.4375            # functions that load or store a constant
disrobe query app.exe field-users 0x10,0x1c             # functions that use every listed [reg+disp]
disrobe capabilities app.exe                            # MITRE ATT&CK + MBC behavior report

disrobe strings app.exe                                 # static strings + decoder-execution recovery

disrobe native entropy app.exe                           # ASCII heat-strip + byte histogram + packed-region runs
disrobe native entropy app.exe --format svg --svg map.svg # dark-theme SVG entropy map with section overlays
disrobe native entropy app.exe --format json --out e.json # the disrobe.native.entropy/v0 document
disrobe native signatures app.exe --out sigs.json        # AES T-tables, SHA/MD5 IV+K, ChaCha20 sigma
disrobe native signatures app.exe --flirt db.sig         # match against an IDA FLIRT database
disrobe native fingerprint app.exe                       # crypto + FLIRT + string-xref sidecar
disrobe native graph app.exe --out imports.dot           # import/export table as Graphviz DOT
disrobe native sbom app.exe --out app.cyclonedx.json     # CycloneDX 1.5 SBOM from cargo-auditable metadata

Coverage and fidelity

Symbol recovery and dumping

native symbols dumps symbols, sections, segments, imports, and debug info from PE, ELF, Mach-O, and 16-bit NE files. It demangles and restores Rust and C++ symbols across x86 / ARM / RISC-V / MIPS / PowerPC / SPARC / eBPF / AVR, reading DWARF, PDB, and STABS debug formats. For C++ binaries it recovers the class hierarchy from the in-memory RTTI and vtable layout: ABI, each class's inheritance kind, direct base classes (virtual flagged), virtual-method slot counts, and detected STL templates.

New Executable structure

The NE parser accepts the little-endian segmented container used by Windows 3.x and OS/2 1.x. It validates the 64-byte header, segment and resource ranges, entry bundles, resident and nonresident names, module references, imported names, relocation records, and non-additive relocation chains. It lowers segment-relative entry addresses, exports, and imported names into the same native model used by behavior, IOC, HTML-report, and native-symbol surfaces. The identify surface classifies both variants. The registered native.ne-structure pass gives detect and auto the validated structure. It does not claim 16-bit disassembly or decompilation.

NE names use an OEM code page rather than UTF-8. Printable ASCII bytes other than backslash render directly. Backslash and every other byte render as \xNN, which keeps the representation unambiguous without guessing a host code page or dropping malformed text.

Disassembly, call graph, and CFG

An in-tree iced-x86 disassembler discovers functions without symbols (call-target and prologue scanning), partitions each into basic blocks, builds the whole-program call graph, and renders the per-function listing or, with --emit cfg-dot, the basic-block CFG. --raw disassembles a flat code blob with no container, and --syntax intel|at&t|nasm|masm selects the dialect for raw output. Each decoded instruction carries its register, memory, and rflags read/write effects, and the native layer can re-encode and relocate instruction blocks (the engine behind native patch). A call into a PLT stub is attributed to the imported symbol the stub forwards to: x86 and x86-64 stubs are decoded for their jmp [slot], AArch64 stubs for their adrp/ldr x17/br x17 sequence, and each GOT slot is matched to its DT_JMPREL relocation. When a synthetic sub_ name and an import or export share an address, the named symbol wins. crates/disrobe-cli/tests/native_calledges_parity.rs requires the call graph of the zig-built Android libraries under corpus/recon/android-audit/native to equal the bl and call sites of llvm-objdump -d on both ABIs.

Patching, signatures, and binary diff

native patch rewrites bytes at a virtual address (or nops a VA span), maps the VA to a file offset, applies the edit, and revalidates the image. native sigmaker generates a wildcarded byte signature from the function at an address (immediates and displacements masked via the instruction decoder), then uniqueness-tests it across the image. native diff matches functions across two binaries by content, relocation-invariant, and control-flow-graph fingerprints and reports the added, removed, and changed functions. All three work on stripped input through the in-tree function discovery.

Queryable IR and capabilities

disrobe query runs a queryable-IR layer over the disassembled code (functions, calls-to, xrefs-to, string-decoders, complexity-over, capability), symbol-independent and driven by the same function discovery. disrobe capabilities runs a rule engine over that IR and reports matched behaviors mapped to MITRE ATT&CK techniques and Malware Behavior Catalog (MBC) IDs, with the matching instruction offsets as per-rule evidence. Both accept a stripped binary directly or a .dr envelope.

Function-scoped output and address queries

native disasm --function <rva> prints one function of an x86 or x86-64 image to stdout, and --range <start>:<end> prints the instructions decoded from start up to the exclusive end. Every address is an RVA in hexadecimal (the 0x prefix is optional), so listings, query results and selectors use one address space: the image base is printed once and instruction addresses and operands are relative to it. A function's extent comes from its .pdata unwind entry when the PE has one; an unwind entry chained to a parent function (UNW_FLAG_CHAININFO, or an indirect entry) is listed as part of that parent. Functions without unwind data, which includes every leaf function an MSVC build emits and every function of an ELF, Mach-O or 32-bit PE image, are bounded by function discovery. --json writes the disrobe.native.scoped-disassembly/v1 document, whose scope names the function, its range and where the range came from, and whose instructions carry the same text as the listing.

A RIP-relative operand (or an absolute operand in 32-bit code) that reads read-only data is annotated with the value it loads: f32 and f64 scalars, packed float lanes, and 32- and 64-bit integers. A lea of read-only data is annotated when the target is a string of at least two printable characters whose terminator lies within 1 KiB, either ASCII or UTF-8 (ascii, utf8) or UTF-16LE (utf16); strings longer than 200 characters are shortened and marked. Loads from writable data, branch-table and import-slot loads, and immediates are not annotated.

native decompile --function <rva> and --range <start>:<end> decompile one function through the in-tree x86 or x86-64 backend and print C (or --format rust) to stdout, or the disrobe.native.scoped-decompilation/v1 document with --json. The function's direct callees are decompiled with it so that call sites resolve as they do in a whole-image run; a function the whole-image run cannot recover is refused with the same reason (DR-NATIVE-0309).

For a scoped x86-64 PE listing, native disasm also recognizes a constant ecx/rcx index followed by a direct call to an indexed-string accessor when the accessor has enough distinct call sites to establish the shape. The index can be loaded with an immediate mov or set to zero with a self-XOR. The in-process emulator recovers the returned literal under fixed fuel, mapped-image, write-log and aggregate-output bounds, including a shared 32-million-step limit across all candidates and call sites. Listings append a quoted, escaped indexed-string index annotation to the matching call instruction; JSON carries the same records in instructions[].indexed_strings. A bounded recovery that cannot trust an accessor result remains visible in indexed_string_refusals and the text listing with its named DR-NATIVE-0802 through DR-NATIVE-0816 refusal. On a NativeAOT image (builds with the dotnet feature), the same annotation also carries the literals the .NET recovery decodes for accessors that read frozen arrays and allocate through the runtime's array helper, which the generic path cannot serve; those come from disrobe dotnet native-aot's string_accessors and are graded there (docs/src/languages/dotnet.md). The scoped command performs this recovery once per input and never runs the sample natively.

Selections that do not name code are refused by name: an RVA that is not a function start, with the function it falls inside and how far into it (DR-NATIVE-0303); the start of a chained unwind range, with its parent (DR-NATIVE-0307); a range outside an executable section, past the end of one, or not backed by file bytes (DR-NATIVE-0304); an empty range (DR-NATIVE-0305); a scope over 4 MiB (DR-NATIVE-0306); and a malformed RVA (DR-NATIVE-0302).

Three disrobe query forms read an x86 or x86-64 image directly (not a .dr envelope) and search every function found in .pdata or by discovery:

  • xrefs-to 0x<rva> lists every instruction that references the address: a direct call or jump to it, a RIP-relative or absolute operand that reads, writes or takes its address, or an immediate equal to its virtual address. Each site carries its kind and the function that holds it.
  • const-users f32:<v>|f64:<v>|u32:<v>|u64:<v> lists the functions that load the value from read-only data or use it as an immediate. Values match by bit pattern and width, so f32:1 and u32:0x3f800000 find the same sites; f32 and f64 also take 0x-prefixed bits.
  • field-users 0x<disp>[,0x<disp>...] lists the functions that use every listed displacement in a [reg+disp] operand whose base is neither the stack pointer nor the instruction pointer, which finds the constructor and the readers of a record whose type is unknown. Up to 64 displacements; a negative one is written -0x8, after -- when it comes first (disrobe query app.exe -- field-users -0x8).

Each query stops after 10,000 sites and says so (truncated), and refuses an image whose functions span more than 256 MiB (DR-NATIVE-0313). Malformed values are refused with DR-NATIVE-0311 and DR-NATIVE-0312.

Emulation-driven string recovery

Beyond a static scan, disrobe strings locates decoder-shaped functions and drives each through the in-house x86 emulator, recovering the plaintext that only exists after the decoder runs. Emulation-recovered strings are deduplicated against the static set and reported with the decoder and buffer addresses, so a single-byte or multi-byte XOR/sub stack-string scheme yields the decoded text without executing the sample natively.

Identifying the compiler, packer, and protector

An in-house multi-signal signature engine fingerprints what built and protected a binary through self-consistency-based identification, then routes each finding to the disrobe pass that handles it. It detects compilers and toolchains (Go, Rust, MSVC, GCC, Clang, MinGW, Delphi, Nim, Zig, Crystal, Swift, GHC, .NET, Nuitka), packers (UPX, ASPack, PECompact, FSG, MEW, MPRESS, Petite, NSPack, kkrunchy), protectors (Themida, VMProtect, Enigma, Obsidium, Armadillo, ConfuserEx, .NET Reactor, Eazfuscator), installers (NSIS, Inno Setup, InstallShield, AutoIt, PyInstaller, Electron, Bun), linkers (MSVC link via the Rich header, GNU ld, LLD), and code-signing. Every hit carries a support route: a Go binary points at disrobe go recover, a packed PE at disrobe native unpack, a .NET assembly at disrobe dotnet decompile, an installer at disrobe auto.

A marker counts only where its format puts it. Section-name markers such as .vmp0, .themida or MEW match the PE section table, never the same text inside section data; short packer magics (UPX!, PEC2, FSG!, the DxLib archive header) match only the headers, the entry-point window or the start of the overlay; an NSIS installer needs its 0xDEADBEEF NullsoftInst first header on a 512-byte boundary of the overlay; a .NET hit needs a metadata root whose stream headers declare a #~ or #- table stream; and a signature is reported from the certificate table the security directory points at, so its verdict covers files of any size.

A PE whose code sections declare memory but store no bytes on disk, while the entry point sits in an executable section of near-random content (at least 7.9 bits per byte), is reported as a runtime-decrypted image: its code exists only after the loader decrypts or decompresses it at run time. identify and native identify name it with the entry section, its entropy and every unbacked code section, and route it to runtime-only; native decompile and native disasm refuse it with DR-NATIVE-0225 and the same evidence (native disasm --raw still sweeps the bytes as they are on disk), and auto records the refusal instead of a pseudo-source sweep. disrobe never runs the image to obtain the code. When the stub belongs to a packer that native unpack recovers, identify names that packer and its unpack route instead of the runtime-only verdict.

Unpacking and byte-level recovery

disrobe native unpack is graded per section against a committed original rather than by whether the output merely parses. Three families ship one packed-and-original pair each, so their figures reproduce from a clean checkout:

Family .text .rdata .data .rsrc Content total
FSG (Hash.exe) 18188 / 18188 2311 / 3988 33212 / 33212 1552 / 4672 55263 / 60060
NSPack (hash.exe) 18188 / 18188 3988 / 3988 33212 / 33212 2333 / 4672 57721 / 60060
Petite (hello.exe) 70796 / 70796 15734 / 18396 456 / 456 none 86986 / 89648

.text and .data are byte-identical for all three, and NSPack's .rdata is byte-identical as well, so the decompressors themselves are exact and the residual is placement and fixups rather than lost payload. The content denominator is the original's own section span and is asserted by equality, so a recovery that emits fewer bytes scores worse instead of shrinking what it is measured against. The byte-identical sections are held as a membership list rather than a count, so a section that stops recovering exactly drops out of the list instead of being masked by another section improving. Two spans are reported and they are not interchangeable: the per-section total above counts .rsrc, while an older whole-image gate measures NSPack over .text, .rdata and .data only and reads 99.36% for the same fixture. Relocations are classified as loader-rebuilt and scored separately, because the OS resolves them at load time.

The residuals have specific causes worth knowing when reading a recovered image. FSG's .rdata gap is the import descriptor region: the stub's block-destination table names one aPLib stream per original section, all four are decoded, and the descriptor block is decoded then deliberately withheld from the recovered image because the stub writes it over original .rdata content at run time. NSPack's .rdata reaches byte identity instead, because its import lookup and address tables are rebuilt from the module record the stub carries rather than left for the loader to resolve. The .rsrc gap is shared across families, at 1552 / 4672 for FSG and 2333 / 4672 for NSPack, and it narrowed once the original resource-directory tree is placed at its own RVA rather than wherever the decompressed image left it; what remains is the part each stub reconstructs at run time. Larger uncommitted vendor samples score lower on the whole-image measure for the same two reasons, and no figure is published for them because those samples are not committed and nothing pins them. kkrunchy and kkrunchy classic recover their payload byte-exactly against committed fixtures.

An absent fixture is never a silent pass. A missing committed fixture always fails the test and names its path. A missing uncommitted vendor sample fails the same way unless tests/optional.toml lists the local packer samples, which it does because CI never has them; the run then writes a not-measured record naming the absent file. A fixture that is present but unreadable is a hard failure in every mode, because that is how a quarantined or truncated sample would otherwise stop grading unnoticed (committed_packer_byte_recovery.rs, fsg_unpack.rs, nspack_byte_recovery.rs, petite_unpack.rs, kkrunchy_unpack.rs).

Runtime anti-analysis checks

disrobe native decompile --neutralize ask|apply|skip plans same-length patches for the runtime checks it finds in a PE: IsDebuggerPresent and CheckRemoteDebuggerPresent call results, PEB BeingDebugged and NtGlobalFlag reads, NtQueryInformationProcess debug-class queries, rdtsc, GetTickCount and QueryPerformanceCounter delta compares, int 2d and icebp traps, and cpuid hypervisor-bit tests. ask confirms each patch on the terminal and is the default there; skip writes the plan and applies nothing, and without the flag a run whose stdin is not a terminal skips without writing a plan; apply takes every patch. The input is never modified. Accepted patches go to <stem>.neutralized.<ext> beside neutralization.json in the output directory, and a declined patch leaves its check reported as detected. Vectored-handler control flow and TLS callbacks are named as refusals and never patched. The packer-stub emulator answers the same checks with pinned values and names each one it answered; the anti-analysis page lists the answers, bounds, and grading.

Authenticode verification

For a signed PE, identify verifies the Authenticode signature rather than only noting its presence. It recomputes the SHA-1/256/384/512 digest over the Authenticode hash range (the file minus the checksum field, the certificate-table directory entry, and the certificate table itself) and compares it to the digest the signature claims. It walks the PKCS#7/CMS signer chain (RSA PKCS#1 v1.5 and ECDSA P-256/P-384) up to an embedded bundle of nine trusted code-signing roots, and requires the code-signing extended key usage on the leaf certificate. It verifies any RFC 3161 timestamp, both its own signer chain and its message imprint, before trusting that timestamp to extend validity. Without a verified timestamp, certificate validity is judged at the time SOURCE_DATE_EPOCH names, or at the current time when the variable is unset; a value that is not a whole number of seconds fails the command with DR-CLI-0318 rather than falling back to the clock. The outcome is a single verdict, one of Valid, NoSignature, HashMismatch, Expired, SelfSigned, UntrustedChain, WrongKeyUsage, UnsupportedAlgorithm, or MalformedSignature, written into identity.json with the computed and claimed hashes, the certificate chain, and the timestamp. A signer that repeats a certificate in the SignedData certificate set (some cloud signing services emit the signing certificate twice) is read with the set deduplicated, which leaves the signed content and its digest unchanged. The verdicts are graded against real signed PE fixtures carrying injected defects: a flipped .text byte, a forged timestamp, a wrong-EKU leaf, and self-signed and expired chains, each asserted to its expected verdict. The computed digest is cross-checked against osslsigncode where it is installed, and live Microsoft-signed System32 binaries reach Valid against the embedded roots (authenticode_oracle.rs).

Delphi and C++Builder RTTI

For a Delphi or C++Builder binary, the native Delphi analyzer (disrobe_pass_native::delphi::analyze) recovers the published object model from the compiled RTTI. It locates virtual method table anchors across the three ABI eras (pre-2009 32-bit, Delphi 2009+ 32-bit, and 64-bit), accepting a class only when its table is self-consistent, and recovers each class's name, parent, instance size, published properties with the ancestor that introduced each, published methods, published fields with their declared class, dynamic method and message handlers, and implemented interface identifiers. Enumeration member names, ordinal ranges and set element types come from the referenced type records. Compiled-in string literals are recovered from the reference-count header across the pre-2009, code page and 64-bit layouts, accepted only when the declared length lands exactly on the terminator. The unit initialization table is reached by following the entry point stub, giving the unit count and each unit's initialization and finalization address, and is refused unless every address lands in an executable section. Each class is tagged as runtime library or author code from its RTTI unit name, so an analyst can read only the author's classes. The compiler release is named only when independent signals agree: the table layout era, a linked runtime package name, dotted unit scope names, and build toolchain path strings. When they disagree the report says so rather than choosing one. It also decodes the binary DFM form resources (TPF0) back to their textual object ... end representation.

Form decoding is compared byte for byte against form streams and renderings both produced by the Free Pascal RTL converters, over five committed cases covering collections, nested collections, child objects, binary data blocks, value lists, sets, control characters and 64-bit integers; the converter source and a regeneration script ship beside the fixtures. Float rendering is excluded and remains ungraded, because the reference renderer's float format differs from Delphi's. The virtual method table and published table walks are graded against the documented layout rather than against a Delphi-compiled binary, so they catch a regression but not an error shared between the reader and the specification transcription. Each table is rejected whole unless every entry validates, so a wrong layout yields nothing rather than invented names. False-positive controls confirm no class, field, dynamic method, interface, type record or string literal is produced from real kernel32.dll, ntdll.dll, user32.dll or shell32.dll.

Nim, Zig, Crystal, and D binaries

These four compilers erase the source, so recovery works from each binary's own tables rather than from anything resembling the original file. disrobe detects the toolchain, demangles its name scheme, and recovers the symbol and metadata surface the compiler left behind. Where DWARF survives, aggregate members come back with full types, including multi-dimensional array dimensions and const/volatile qualifiers, so a field reads as const u8[4] or u8[2][3] rather than an opaque blob. A stripped D PE, which carries neither DWARF nor a name table, is a wall: the format is identified and nothing further is claimed.

Function bodies are carved and lifted to pseudo-C and pseudo-Rust. The figures below come from committed binaries built by the real compilers, and each count is pinned exactly in body_equivalence.rs, so a change in either direction fails the test.

Binary Toolchain Carved functions Pseudo-C bodies Pseudo-Rust bodies
Nim, C backend, safety-checked, x86-64 ELF nim 2.0.8 183 83 83
Nim --mm:boehm, x86-64 PE nim, mingw-w64 gcc 13.2.0 backend 93 12 12
Nim --mm:arc, orc, refc, markAndSweep, go, none, x86-64 PE nim, mingw-w64 gcc 13.2.0 backend 2 each 1 each 1 each
Zig, safety-checked, x86-64 ELF zig 0.13.0 1356 340 336
Zig ReleaseFast, x86-64 Linux ELF zig 0.16.0 23 9 9
Crystal, LLVM backend, stripped x86-64 PE not recorded in the binary 314 25 25

These counts measure how many bodies the pass emits, not whether they are correct. Two tests grade the output against independent references. body_recovery_oracle.rs compiles every pseudo-C body recovered from the Nim and Zig ELF files, the Crystal PE, and a D PE with a real C compiler (-fsyntax-only -std=c11), and every pseudo-Rust body with rustc --emit=metadata; it requires at least 492 C and 487 Rust bodies to pass. This grade is compile-only: it shows that each body stands alone as valid source, not that it computes what the original did. body_equivalence.rs compiles 14 recovered pseudo-C bodies from Zig 0.16.0, Nim 2.0.8, and Crystal 1.20.2 builds, runs them on at least 115 input rows, and compares each result with a model of the function transcribed from the original source; each binary must also match its build record (source hash, toolchain, and build command). Both tests run in the weekly scheduled CI run on Linux, macOS, and Windows, not on every push.

Entropy map and byte histogram

disrobe native entropy slides a 4 KB window across the file computing Shannon entropy (bits/byte) to locate packed, compressed, or encrypted regions, and renders the profile three ways via --format text|json|svg:

  • text (default): a Unicode heat-strip sparkline (one glyph per 4 KB block, taller = higher entropy), a 16-bucket ASCII byte-frequency bar chart, and a list of contiguous high-entropy runs (entropy >= 7.0 bits/byte) with their file offsets, the candidate packed or encrypted regions. Pass --out <path> to also drop the JSON document.
  • svg: a self-contained neutral-gray entropy map with no external dependency. Each block is a column colored on a calm-gray to amber to red ramp, with PE/ELF/Mach-O section boundaries (parsed from the file's own section table) overlaid as labeled dashed markers and a color legend. Written to ./out/<stem>.entropy.svg or the explicit --svg <path> (which implies SVG rendering). The SVG is byte-stable for a given input (no clock or RNG) and all section names are XML-escaped.
  • json: the machine-readable disrobe.native.entropy/v0 document: per-block entropy, the sparkline/heat-strip strings, the 16-bucket histogram, the detected high-entropy runs, and the section spans.

The reusable rendering logic lives in disrobe_pass_native::entropy_viz (entropy_sparkline, byte_histogram, histogram_ascii_16, high_entropy_runs, render_entropy_svg) so other tools can embed it.

Limits

  • No committed benchmark compares the in-tree decompiler with Ghidra or IDA, so this guide states no ranking against them. What it does state: disrobe hands either one clean, unpacked, symbol-rich input, and can drive Ghidra headlessly. See native decompile for the in-tree decompiler, its two grading levels, and the Ghidra backend.
  • Themida and VMProtect support covers detection and section carving. See native unpacking for the available outputs and recovery limits.
  • Authenticode chain validation walks to an embedded bundle of nine trusted code-signing roots and covers RSA PKCS#1 v1.5 and ECDSA P-256/P-384. Anything outside that lands as UnsupportedAlgorithm or UntrustedChain rather than a pass.
  • The Delphi VMT walk accepts a class only when its VMT is self-consistent, so a partially-overwritten or hand-built table yields no class rather than a guessed one.
  • Function-scoped output and the address- and value-keyed queries decode x86 and x86-64 only. Unwind ranges are read from x64 PE .pdata; ELF .eh_frame ranges are not used, so ELF functions are bounded by discovery alone. const-users matches direct loads of a constant and immediates; a constant reached through a register holding a table's address is not attributed.
⚠️ **GitHub.com Fallback** ⚠️