Skip to content

Releases: ATRAPSLLC/dotscope

v0.9.1

Choose a tag to compare

@BinFlip BinFlip released this 05 Sep 16:10

A dependency release, and the defects that adopting the dependency exposed.
analyssa 0.6.0 reshapes how an exception clause is represented and takes away
the CFG relations SsaFunction used to answer itself — which forced every
analysis to say which graph it wanted, and three of them turned out to have been
reading one that contains no handlers. The rest of the set shares that shape: an
exception clause read through fields that could not say what the clause meant.

Fixed

  • Liveness ran over a graph in which no handler is reachable. The local
    coalescer built its dataflow CFG from terminator edges alone, and nothing
    branches to a handler entry — the runtime dispatches into it. A variable live
    only across a protected region therefore came back dead, and the coalescer was
    free to give its slot to something else. It now solves over the
    exception-aware view; the extra edges only widen liveness, which is a
    may-analysis, so nothing that was correct becomes wrong.

  • SCCP folded values defined in handlers as constants nobody wrote. The same
    graph, the same reason, the opposite direction: constant propagation is rooted
    at the entry and walks forward, so a handler block was simply never visited
    and every value defined in one stayed at Top — the lattice element meaning
    "no definition has reached this yet", which the fold reads as a constant. Both
    SCCP rounds now run over the exception-aware view.

  • A filter clause's handler body resolved to its filter expression. The
    decoder marks the filter's entry block and the handler body's entry block with
    the same handler index, and the filter is laid out first, so the search that
    took the first match answered with the filter block for every catch … when
    clause — a wrong handler_offset in the regenerated exception table. The
    filter is now resolved first and excluded from the handler's own search.

  • Full inlining remapped only an operation's primary destination. An
    operation defining a secondary or flag output would have carried the callee's
    variable id for it into the caller, where it means something else. Latent for
    a CIL front-end, whose operations define one variable each, and repaired
    rather than left to a future one: every definition the operand walk reports is
    now remapped. That disagreement is why analyssa removed the
    single-destination setter this used.

  • One malformed PE resource discarded an assembly's entire metadata. goblin
    walks the resource directory in strict mode by default, so a single bad
    ResourceString in a VS_VERSIONINFO block aborted the whole PE parse and
    took every byte of CIL metadata with it. Nothing in dotscope reads that
    directory — a .NET assembly's own resources live in the managed metadata — so
    it is no longer parsed.

  • CFF dispatcher detection counted a self-loop by hand. It compensated for a
    predecessor relation that dropped self-edges by scanning the block's
    instructions for one. The relation it now asks reports a self-edge like any
    other, so the compensation is gone and predecessor counts agree with what phi
    validation sees.

Changed

  • BREAKING: An exception clause is three optional block ranges, not five
    loose block indices.
    SsaExceptionHandler carries protected_range,
    handler_range and filter_range — each a half-open BlockRange or nothing —
    in place of try_start_block, try_end_block, handler_start_block,
    handler_end_block and filter_start_block. A part can no longer be half of
    itself: a region that began somewhere and ended nowhere was a state the old
    five fields could hold and no check could refuse.

    A CIL filter's extent is now recorded rather than inferred. It is
    [filter_offset, handler_offset) — the blocks between the filter's entry and
    the handler's — so a filter clause finally says where its expression is
    instead of leaving every reader to guess it from the neighbouring parts.

    BlockRange, ClausePart, ClauseLayout, LaidOutHandler, HandlerKind,
    ExceptionBlocks and ExceptionTableError are re-exported from
    dotscope::analysis, so a caller holding a dotscope exception handler has the
    vocabulary it answers in without naming analyssa.

  • BREAKING: SsaBlock::terminator_op is SsaBlock::control_terminator.
    The old name was positional — the block's last instruction, whatever it was —
    while every call site was asking a control-flow question. The rename is
    analyssa's; dotscope's call sites now ask the control question, so a block
    whose last instruction is not a terminator contributes no edges rather than
    edges leaving from an instruction control cannot reach.

  • BREAKING: SsaFunction no longer answers predecessor or successor
    questions.
    block_predecessors and block_successors are gone;
    SsaCfg::from_ssa is the terminator-derived relation and EhCfg::from_ssa
    the exception-aware one, and which one an analysis needs is now a decision it
    has to state.

  • BREAKING: SsaOp::Break carries a BreakpointOp. CIL break is
    SsaOp::Break(BreakpointOp::Breakpoint). BreakpointOp is re-exported from
    dotscope::analysis.

  • BREAKING: ConstValue gained a Symbol variant, so exhaustive matches
    on it need one more arm.
    CIL has no symbol space — every entity is named by
    a metadata token that the type, method and field references already carry — so
    CilTarget::SymbolRef is an uninhabited type and the arm is unreachable by
    construction.

  • Target::handler_kind replaces Target::is_filter_handler. CilTarget
    classifies through the existing ExceptionHandlerFlags::kind, so the
    ECMA-335 §II.25.4.6 bit classification has one definition in the crate rather
    than two that can disagree.

  • UTF-16 and UTF-32 decoding takes the chunks as arrays. Seven sites cut a
    byte slice into fixed-width units by hand: six paired chunks_exact(N) with a
    fallible conversion back to [u8; N], and one indexed the chunk byte by byte.
    Each carried a fallback for a case that cannot arise — a dropped code unit, a
    zero, an error return. as_chunks yields the arrays themselves, so the
    fallbacks and the bounds checks are gone with them.

Dependencies

  • analyssa 0.5.0 → 0.6.0
  • quick-xml 0.41.0 → 0.42.0. Element names and attribute keys are &str
    rather than &[u8], so PermissionSet's XML reader compares them directly
    instead of decoding each one and reporting a UTF-8 error the parser has
    already ruled out.
  • z3 0.20.2 → 0.21.0

v0.9.0

Choose a tag to compare

@BinFlip BinFlip released this 15 Aug 15:24

A security and correctness release. dotscope parses, emulates and rewrites
hostile input, and this release closes the gap between what the resource limits
claimed to enforce and what they actually did, along with a set of
miscompilations in the SSA back end and layout defects in the PE writer.

Security

  • Resource limits are enforced before the work happens, not after. The
    managed-heap ceiling was checked once the object was already materialised;
    unmanaged allocation (localloc, AllocHGlobal, AllocCoTaskMem,
    VirtualAlloc) had no budget at all, and max_unmanaged_bytes and
    max_heap_objects were declared but never read. Allocation
    now runs through a reservation that must succeed first, in-place mutation is
    accounted, and forks inherit the ceiling instead of escaping it.
  • Unbounded and quadratic work on attacker input. Fixed in the inheritance
    walker (a cyclic extends graph caused an uncatchable native stack overflow),
    the x86 traversal (O(n²) to end of file), method-body decoding (disassembled
    past the declared code_size), exception-handler association (O(H²·B) at load
    time), DEFLATE/GZIP/LZMA expansion, and the signature parser (a blob could
    build a ~61 000-deep type whose recursive drop overflowed the stack).
  • Argument validation across the BCL hooks. Negative or oversized lengths
    reaching Marshal.Copy, Stream.SetLength, StringBuilder.set_Length,
    String.PadLeft/PadRight, the BinaryReader readers and the PBKDF2
    constructors reserved usize::MAX, ran multi-billion-iteration loops, or
    drove a ~4.3-billion-round KDF. They now reject the value and raise the .NET
    exception.
  • Emulator forks were not isolated. "Isolated" forks shared one mutable
    runtime state, AppDomain and synthetic-method map while running concurrently.
    Assembly.Load(byte[]) is now bounded by max_loaded_assemblies and
    max_loaded_assembly_bytes, and runtime-loaded assemblies parse with minimal
    validation rather than the full pipeline over hostile bytes.
  • Memory protection flags are enforced on read and write, faulting through a
    new catchable AccessViolationException, and region mappings are overlap-checked.
  • deny(unsafe_code) is enabled. One unsafe block remains, for the writer's
    output mapping, with a targeted allow and a SAFETY note.
  • SECURITY.md now states the supported version, the real EmulationLimits
    defaults and what is actually run. The previous text listed DoS protections as
    "ToDo" and claimed Valgrind testing that does not exist.

Fixed

  • Malformed table rows silently truncated a table. The row iterators
    reported a parse failure as end-of-iteration, and because the writer rebuilds
    tables by iterating them, an unreadable row became missing output rather
    than an error. Iterators now yield Result, get returns
    Result<Option<T>>, and MetadataTable::new validates and truncates to the
    declared extent.
  • MethodPtr, EventPtr and PropertyPtr tokens used the wrong table id,
    so any assembly carrying a *Ptr table lost its method-bearing types.
  • Three back-end miscompilations. Full inlining placed the return-value copy
    before the instruction defining it; switch and conditional-branch phi
    trampolines fell through into the next edge's copies. Critical edges are now
    split into real blocks by a dedicated out-of-SSA pass.
  • Handler SSA used a "last block wins" snapshot of try-scope definitions
    because the CIL CFG carried no exception edges. Real EH edges make handler
    entries ordinary join points.
  • Linear-scan allocation computed live intervals with no liveness solve, so
    a value live across a back edge could have its slot clobbered.
  • Four exception-unwind defects: the caller's finally ran against the
    grandparent frame, queued finally blocks were never drained once a catch was
    selected, a leave out of nested finallys spun on endfinally, and a filter
    returning zero terminated emulation instead of resuming the handler search.
  • PE writer layout. Heap offsets were computed twice from different inputs,
    so offsets baked into tables and IL disagreed with where data was written;
    heap index widths were inherited from the input and truncated above 0xFFFF;
    section SizeOfRawData came from the virtual extent; and the input's
    certificate directory offset was applied to the output, zeroing live .text
    before the checksum was computed over the damage. Output now writes to a
    temp file and renames.
  • Cleanup deleted live metadata: TypeRef liveness ignored ResolutionScope,
    and the opaque-field pass folded any static-to-instance load and deleted the
    owning type with no immutability precondition.
  • Byte-offset slicing of string literals panicked on multi-byte UTF-8;
    clippy::string_slice is now denied, which surfaced ten genuine sites.
  • The fuzz crash-corpus regression test passed on any checkout without the
    corpus, and CI ran cargo test --lib, so the integration tests never executed
    on Windows or macOS. Both are fixed, and the 72 crash artifacts are committed.
  • An array signature's rank was never bounded, and it was the only ceiling on
    the lower-bound count that follows it, so a declared rank of 0x400000 made that
    check permissive rather than protective and the dimension list grew to the
    declared count before any read could run out of input. This accounted for every
    out-of-memory artifact found by fuzzing.
  • Type-name validation rejected legitimate compiler-generated names. It
    matched a hand-written list of prefixes, so <Module>{GUID} failed on untouched
    input as well as on rewritten output; the closed angle bracket the C# compiler
    guarantees is the real invariant. Validation failures also reported only how
    many validators failed, discarding the messages saying why.
  • Cleanup deleted enclosing types whose nested types were still referenced,
    leaving a NestedClass row pointing at a TypeDef that no longer existed.
    Reachability now walks the nesting relation to a fixed point.
  • Reachability used the SSA call graph alone, so every method without SSA
    looked unreachable and the live set was under-approximated. SSA edges are now
    preferred where they exist and the static graph fills in where they do not.
  • Opaque static fields were only folded when every write came from a .cctor.
    Obfuscators route initialization through helpers, so those fields stayed opaque
    and their predicates survived. A write site now counts when every caller of the
    writing method is itself initialization-only; a method with no known caller is
    not admitted. .NET Reactor string samples go from 223 decryption failures to
    none.
  • Parameters removed with their method left dangling references behind them.
    Constant, FieldMarshal and CustomAttribute rows name a parameter through
    a coded index and are dropped by asking whether their parent was deleted, but a
    parameter discarded along with its method never entered that record — what had
    been deleted was the method. The rows outlived the parameters they named and
    the output failed raw validation with an out-of-range Param RID. Removed
    parameters are now cascaded to all three tables.
  • .NET Reactor NecroBit recovered nothing from full-protection binaries.
    Every encrypted body was lost on both such samples — 0 of 59 and 0 of 562 —
    while necrobit-only binaries were unaffected. The cause was not in the
    decryption: a protection that resolves VirtualProtect through
    LoadLibrary/GetProcAddress and calls it through a delegate never reached
    the hook that implements it, so the pages holding the method bodies stayed
    read-only and the write-back faulted on the first body. Both samples now
    restore every stub and validate.
  • A native function resolved at runtime never reached its hook. Hook matching
    required a declared P/Invoke, so any function obtained through GetProcAddress
    and invoked through Marshal.GetDelegateForFunctionPointer bypassed it — the
    delegate path answered from a small table of hardcoded return values instead,
    reporting success without performing the call's effect. Such calls now carry
    their arguments and dispatch through the ordinary hook path. LoadLibrary
    hands out a distinct handle per module so the resolved function can be matched
    against the library it came from.
  • A refused write was retried as a fresh mapping. Marshal's write path
    treated "mapped, but not writable" the same as "not mapped" and tried to
    materialise a window at the enclosing 64KB boundary. For an address inside a
    loaded image that is the image base, so the attempt collided with the image and
    reported an overlap — turning a recoverable permission error into a fatal one
    that named the wrong cause. The two cases are now distinguished.
  • A failed body-decryption transform caused cleanup to delete the code it
    could not decrypt.
    A technique fills its cleanup request during detection,
    before it knows whether the transform those deletions depend on will run. When
    a byte transform fails, the bodies it was meant to restore stay encrypted;
    such a method contributes no call edges, so everything it references reads as
    unreachable and the type-level sweep removes it. One .NET Reactor sample fell
    from 1181 methods to 87. Techniques now report what they could not restore
    (Technique::unrecovered_methods), cleanup protects those methods, withholds
    the failed technique's own request, and skips unreferenced-type removal for the
    run — the call graph cannot tell unreachable from undecrypted. The same sample
    now keeps 946 methods and validates.
  • Unflattening could emit a function that failed SSA validation, which
    aborted deobfuscation for the whole assembly rat...
Read more

v0.8.5

Choose a tag to compare

@BinFlip BinFlip released this 10 Aug 04:37

First release under ATRAPS LLC ownership. Two packaging defects fixed, and dependencies brought current.

Fixed

  • No licence text was shipped with the crate. LICENSE and NOTICE live at the workspace root, but the package root is dotscope/, and cargo only packages files under the package directory — so every published version declared license = "Apache-2.0" while shipping neither. Verified against 0.8.4 on crates.io: 1,002 files, no licence among them. Both files now exist inside the package and are included.
  • LICENSE was a symlink to LICENSE-APACHE. Symlinks do not survive packaging cleanly; LICENSE is now a regular file, the duplicate is gone, and the README badge, README link, and crate-level doc badge are repointed at it. That doc badge also pointed at a main branch that did not exist, so it was already dead.

Dependencies

  • analyssa 0.4.1 → 0.5.0, which fixes SSA rebuild and phi-transform correctness upstream: on a 125 MB reference binary its pass rollbacks went from 6,094 to 0 and verifier-reported undefined uses from ~28,960 to 0. No API changes were needed here.
  • comfy-table 7.2.2 → 8.0.0. The preset-string API was removed in v8; load_preset(presets::NOTHING) becomes load_style(presets::NOTHING), presets now being TableStyle constants. Rendering is unchanged.
  • All remaining dependencies refreshed via cargo update.

Changed

  • Recorded ATRAPS LLC as copyright holder in LICENSE and NOTICE (2025-2026, the range this history spans).
  • Dropped the deprecated authors field from both workspace members; repointed repository / homepage at the organisation.
  • Default branch renamed mastermain. master appeared in eight places in ci.yml, including the job conditions gating fuzzing and the security audit, all updated.
  • Publishing now uses crates.io trusted publishing instead of a stored registry token, and refuses to publish a release whose commit is not contained in main.

Only dotscope is published to crates.io. The dotscope-cli binaries are attached to this release.

v0.8.4

Choose a tag to compare

@BinFlip BinFlip released this 27 Jul 04:18
1cd43e0

Added

  • Memory optimization pass (compiler::MemoryOptimizationPass): store-to-load forwarding, redundant load elimination, and block-local dead store elimination, every rewrite gated on a Memory SSA alias proof. Registered in the deobfuscation pipeline's normalize phase and enabled by default; disable with PassConfig::memory_optimization = false. This reaches the field and array traffic obfuscators use to keep values out of SSA registers, which the register-level passes cannot see through
  • Field-sensitive points-to for CIL (CilTarget::field_member_index): the field's metadata token supplies the stable per-field cell identity Andersen's analysis keys on, so &o.a and &o.b no longer alias. An unresolved (null) field token falls back to the sound whole-object approximation
  • x86 segment overrides reach the IR: X86Memory gained a segment field, decoded from the instruction's explicit prefix, and fs:/gs:-qualified accesses now lower to LoadIndirect/StoreIndirect with a distinct address_space (257/256, following LLVM's numbering). Alias analysis treats the spaces as disjoint, so TEB/PEB and stack-cookie accesses stop colliding with flat memory at the same displacement. cs:/ds:/es:/ss: deliberately stay in the flat default — in flat user mode they share a base, and marking them would let alias analysis prove two names for one cell disjoint
  • Re-exports for analyssa's new alias machinery: analysis::{pointsto, address} modules plus MemorySsa, IndirectLocation, ArrayIndex, AliasResult, MemoryDefSite, MemoryPhiOperand, and MemorySsaStats
  • Cross-block value promotion before CFF restructuring (deobfuscation::passes::unflattening::spill): unflattening rewires the CFG, so the SSA has to be reconstructed afterwards — and reconstruction is a reaching-definition problem that can only be solved for values with a storage location. Arguments and locals have one; a stack temporary produced in one block and consumed in another exists only as an SSA name, and the sole record that two names denote the same value is the phi that merges them. Rewiring discards exactly that record, and for an edge the patch creates no phi ever described it. Every value crossing a block boundary is now promoted to a local slot before any terminator is touched — the classical "spill temporaries before restructuring" step — so rebuild can recover versions and phi placement for all of them by the ordinary algorithm. This is what makes .NET Reactor NecroBit samples reconstruct at all, and it is correct for any rewiring rather than only the shapes whose phis happen to survive
  • Zero-initialization for undefined SSA definitions (SsaConverter): a new construction phase materializes definitions for variables that had none, following ECMA-335 §I.12.3.2.2 — a typed zero Const for primitives and references, and LoadLocalAddr; InitObj; LoadLocal for value types. Previously such variables were registered with no defining instruction, so they survived initial construction but vanished the moment the variable table was rebuilt from real definitions
  • Emulation and CFF smoke tests (tests/emulation_smoke.rs): five samples whose deobfuscation depends on emulation or the unflattening tracer producing byte-identical results, each checked for semantic preservation against original.exe. The full packer suites take hours; this runs in seconds, so a change to the emulation layer or the tracer is validated against real output rather than only wall-clock time
  • Deobfuscation benchmark (benches/deobfuscation.rs): ConfuserEx and .NET Reactor groups plus a detection-only control, behind --features deobfuscation

Fixed

  • Taint-driven neutralization could produce IR with dangling reads: SentinelTaintRemovalPass and NeutralizationPass rewrote every tainted instruction to Nop and dropped every tainted phi, destroying definitions that surviving code still read. PhiTaintMode::NoPropagation makes phis taint barriers by design, so a phi routinely merges a tainted definition into code the analysis never marks. Both passes now shrink the removal set to a fixpoint (utils::retain_removable) — a candidate whose result still has a reader is kept rather than the removal widening into legitimate code. NeutralizationPass also excludes its protected DecryptedString constants from the candidate set rather than at rewrite time, so branch-target selection sees what is actually removed
  • AssemblyDependencyGraph::find_cycles reports participants, not a closed walk: following analyssa's switch to a single deterministic Tarjan pass, a self-dependency now names the assembly once rather than twice. The stale "modified DFS with three-color marking" documentation was corrected to match
  • CIL emulation fetched instructions in O(n) (emulation::engine::context::MethodCode): every executed instruction cloned the method's entire instruction vector and scanned it linearly for the current offset. A 3-million-instruction run performed 3.2 billion Instruction clones — about 1052 per step. Method bodies are now cached per token with an offset-to-index map, and the execution loop borrows the instruction instead of cloning it. Synthetic bodies bypass the cache, since ILGenerator can mutate them
  • Emulation rebuilt the assembly context on every instruction: loaded_assembly_context constructed a fresh EmulationContext and took an RwLock per executed instruction; contexts are now memoized per assembly index
  • optimize_locals split values it renumbered (SsaFunctionCilExt): renumbering a local moved its origin but left its rename group pointing at the old slot. rebuild_ssa groups variables by rename group while resolving argument/local representatives by origin, so the two views disagreed about which names denote the same local and the value ended up with no reaching definition. Both now move together
  • CFF tracer could escape its visit budget: entering an expression-switch false arm deliberately resets total_visits so each arm gets its own budget. On heavily nested methods that reset fired often enough to make the budget unbounded — a 348-block ConfuserEx method reached 3.3 million block visits against a 50,000 cap. A monotonic counter now bounds total work per trace without changing the per-arm semantics
  • CFF reconstruction produced invalid SSA after block cloning: cloned blocks duplicated every definition they contained, phi operands survived pointing at predecessors the patched CFG no longer has, and operands were left naming redirected blocks. Clones now allocate fresh variable ids (after state-tainted filtering, which keys on the original ids), phi operands are pruned against the patched predecessor sets and definedness, and redirected operands are resolved through a bounded hop limit
  • Cleanup deleted live types reachable only through another deletion candidate (#249, fix contributed in #248 by @agski331): find_unreferenced_types computed a single step of what is a transitive reachability problem. A candidate rescued partway through a pass never propagated that liveness onwards, so any cluster reachable solely through it was still read as isolated infrastructure — on reactor_virtualization that cut the assembly from 854 methods to 45, deleting a VM interpreter whose stubs remain live because nothing devirtualizes it. Reachability is now a worklist drain over a type-level call graph, in O(V+E) rather than O(depth × edges). CustomAttribute constructor types are seeded as roots before propagation rather than filtered afterwards, so the types those constructors call survive too, and liveness propagates from a nested type to its enclosing type — deleting an enclosing type cascades to its children through expand_type_tokens, which was dropping nested types that live code still called. The deletion set is sorted for a reproducible order
  • Technique cleanup ran whether or not the transformation it implies succeeded: a technique builds its cleanup request from detection findings alone, so it scheduled the decryptor, its infrastructure type, the initializer and the encrypted data for deletion even when no call site was reversed, leaving those call sites pointing at metadata that no longer exists and emptying the methods holding them. Removal now requires that nothing still calls the decryptor — a successful decryption rewrites its call site to the constant, so the absence of remaining callers is the evidence of reversal. Absence of recorded failures is not, since a decryptor that was never exercised has none either. Note that the caller check reads the SSA call graph, which covers only methods that converted successfully
  • ConfuserEx constant decryption missed builds that differ from stock 1.6.0 (#249): four defects, each masking the next. The blob index was matched against int32 alone, so builds emitting T Get<T>(uint32) yielded no decryptor at all — any integer width is now accepted, the surrounding constraints carrying the selectivity. stobj rejected reference types, though ECMA-335 §III.4.29 defines it over any typeTok and makes it equivalent to stind.ref for reference types, which is what stobj !!T becomes at T = string. ldelema hand-rolled its index match to I32/NativeInt while ldelem/stelem share a helper accepting every width, so a native uint index aborted emulation. And stfld through a pointer to a value-type array element replaced the whole element with the field's value instead of updating the field inside it, which is the path LZMA's bit-decoder struct arrays take
  • ConfuserEx LZMA blobs were rejected or misparsed (#249): the sniffer required the compressed payload to be smaller than its declared output, but LZMA expands small high-entropy input and the constants blob is XOR-encrypted before compression — a 44-byte blob compresses to 51 a...
Read more

Release v0.8.3

Choose a tag to compare

@github-actions github-actions released this 17 Jul 12:54
f39eef1

Changes in v0.8.3

Installation

Library

Add this to your Cargo.toml:

[dependencies]
dotscope = "0.8.3"

Or install via cargo:

cargo add dotscope

CLI Tool

Download the pre-built binary for your platform from the assets below and extract it.

Platform Asset
Linux (x86_64) dotscope-v0.8.3-x86_64-unknown-linux-gnu.zip
macOS (Apple Silicon) dotscope-v0.8.3-aarch64-apple-darwin.zip
Windows (x86_64) dotscope-v0.8.3-x86_64-pc-windows-msvc.zip

Note: Z3 is an optional compile-time dependency used only for the z3 feature (CFF reconstruction). The pre-built CLI binaries do not require Z3 at runtime.

Release v0.8.2

Choose a tag to compare

@github-actions github-actions released this 03 Jul 05:17
ad94899

Changes in v0.8.2

Installation

Library

Add this to your Cargo.toml:

[dependencies]
dotscope = "0.8.2"

Or install via cargo:

cargo add dotscope

CLI Tool

Download the pre-built binary for your platform from the assets below and extract it.

Platform Asset
Linux (x86_64) dotscope-v0.8.2-x86_64-unknown-linux-gnu.zip
macOS (Apple Silicon) dotscope-v0.8.2-aarch64-apple-darwin.zip
Windows (x86_64) dotscope-v0.8.2-x86_64-pc-windows-msvc.zip

Note: Z3 is an optional compile-time dependency used only for the z3 feature (CFF reconstruction). The pre-built CLI binaries do not require Z3 at runtime.

Release v0.8.1

Choose a tag to compare

@github-actions github-actions released this 26 Jun 13:03
6730db8

Changes in v0.8.1

Installation

Library

Add this to your Cargo.toml:

[dependencies]
dotscope = "0.8.1"

Or install via cargo:

cargo add dotscope

CLI Tool

Download the pre-built binary for your platform from the assets below and extract it.

Platform Asset
Linux (x86_64) dotscope-v0.8.1-x86_64-unknown-linux-gnu.zip
macOS (Apple Silicon) dotscope-v0.8.1-aarch64-apple-darwin.zip
Windows (x86_64) dotscope-v0.8.1-x86_64-pc-windows-msvc.zip

Note: Z3 is an optional compile-time dependency used only for the z3 feature (CFF reconstruction). The pre-built CLI binaries do not require Z3 at runtime.

Release v0.8.0

Choose a tag to compare

@github-actions github-actions released this 04 Jun 13:44
f52fd63

Changes in v0.8.0

Installation

Library

Add this to your Cargo.toml:

[dependencies]
dotscope = "0.8.0"

Or install via cargo:

cargo add dotscope

CLI Tool

Download the pre-built binary for your platform from the assets below and extract it.

Platform Asset
Linux (x86_64) dotscope-v0.8.0-x86_64-unknown-linux-gnu.zip
macOS (Apple Silicon) dotscope-v0.8.0-aarch64-apple-darwin.zip
Windows (x86_64) dotscope-v0.8.0-x86_64-pc-windows-msvc.zip

Note: Z3 is an optional compile-time dependency used only for the z3 feature (CFF reconstruction). The pre-built CLI binaries do not require Z3 at runtime.

Release v0.7.0

Choose a tag to compare

@github-actions github-actions released this 03 May 17:06

Changes in v0.7.0

  • fix: updated gh actions (5340a4b)
  • version 0.7.0 prepare (04ced21)
  • feat: added support for NetReactor 7.5.0 (partial, full + virtualization are still WIP) (3458cf5)
  • feat: updated dependencies (081c673)
  • fix: codegen issue that erased required address-taken locals fix: unflattening aborted trace too early by missing BranchCmp cases (8e3f987)
  • fix: resolved a bug that would lead to inlining passes sometimes missing candidates due to race on parallel execution (44a9aae)
  • fix: CFF unflattening issues from refactoring (941a856)
  • fix: resolved unused warnings for feature gated API (f0597f9)
  • feat: added support for JIEJIE.NET - 2026-01-05 obfuscator refactoring: deobfuscation pipeline feat: updated and improved test samples for all obfuscators feat: added documents from obfuscator research (9885755)
  • fix: replaced 'rsa' crate temporarily due to CVE (9df3b96)
  • feat: deobfuscation architecture overhaul - moved to a more flexible techniques based design rather than being obfuscator focused. feat: emulation engine decomposition feat: extended BCL emulation runtime feat: metadata and typesystem API extensions feat: added new DelegateProxyResolutionPass and OpaqueFieldPredicatePass (5d31c93)
  • feat: extended BCL implementation for the CIL emulator (3ca2438)
  • fix: various fixes to improve reliablity when analysing PureLogs obfuscator (aad5c41)
  • fix: propagation of 'lenient' mode to force analysis (a4ea76c)
  • fix: improvements for .net 10 (1375f3e)
  • feat: add ILDasm formatter library and VtFixup parsing for mixed-mode assemblies (9719aa3)
  • refactor: migrate metadata flags to type-safe metadata_flags! macro (ac88575)
  • fix: updated documentation, feature-gated bitmono string decryption as it requires 'legacy' crypto, removed MacOS Intel from CI/CD pipeline (763e255)
  • feat: fixed clippy warnings and updated cowfile to 0.2.1 (0cd3e0f)
  • feat: added BitMono 0.39.0 support (8cb5830)
  • feat: initial migration of 'File' backend to use 'cowfile' (9413860)
  • feat: improvement of cleanup system for better 'cascading' cleaning of output binaries (962202e)
  • fix: CALLI handling in SSA construction did not use metadata for correct stack handling (2ca69ee)
  • fix: exception handler hardening (bc38507)
  • feat: added cleanup of AssemblyRef and MemberRef entries (fc1d0e5)
  • feat: refactoring of SsaRebuild related logic to improve reliability and stability of SSA and SSA passes (da7ef90)
  • fix: updated dependencies (e423f3b)
  • fix: wrong size encoding for exception handlers fix: detection_threshold from deobfuscationconfig was not set properly fix: string heap compaction caused issues with sub-strings within strings that where removed fix: added missing calli handling in SsaConverter fix: exception handler generation in codegen fix: added support for 'call $+5' trampolines in x86 decoder (5f957b4)

Installation

Library

Add this to your Cargo.toml:

[dependencies]
dotscope = "0.7.0"

Or install via cargo:

cargo add dotscope

CLI Tool

Download the pre-built binary for your platform from the assets below and extract it.

Platform Asset
Linux (x86_64) dotscope-v0.7.0-x86_64-unknown-linux-gnu.zip
macOS (Apple Silicon) dotscope-v0.7.0-aarch64-apple-darwin.zip
Windows (x86_64) dotscope-v0.7.0-x86_64-pc-windows-msvc.zip

Note: Z3 is an optional compile-time dependency used only for the z3 feature (CFF reconstruction). The pre-built CLI binaries do not require Z3 at runtime.

Release v0.6.0

Choose a tag to compare

@github-actions github-actions released this 13 Feb 03:54
a89fa3c

Changes in v0.6.0

Installation

Library

Add this to your Cargo.toml:

[dependencies]
dotscope = "0.6.0"

Or install via cargo:

cargo add dotscope

CLI Tool

Download the pre-built binary for your platform from the assets below and extract it.

Platform Asset
Linux (x86_64) dotscope-v0.6.0-x86_64-unknown-linux-gnu.zip
macOS (Apple Silicon) dotscope-v0.6.0-aarch64-apple-darwin.zip
macOS (Intel) dotscope-v0.6.0-x86_64-apple-darwin.zip
Windows (x86_64) dotscope-v0.6.0-x86_64-pc-windows-msvc.zip

Note: The CLI requires Z3 to be installed as a runtime dependency.
Install via: apt install libz3-dev (Linux), brew install z3 (macOS), or download from Z3 releases (Windows — the Windows zip already includes libz3.dll).