Releases: ATRAPSLLC/dotscope
Release list
v0.9.1
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 atTop— 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 everycatch … when
clause — a wronghandler_offsetin 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
ResourceStringin aVS_VERSIONINFOblock 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.SsaExceptionHandlercarriesprotected_range,
handler_rangeandfilter_range— each a half-openBlockRangeor nothing —
in place oftry_start_block,try_end_block,handler_start_block,
handler_end_blockandfilter_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,
ExceptionBlocksandExceptionTableErrorare 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_opisSsaBlock::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:
SsaFunctionno longer answers predecessor or successor
questions.block_predecessorsandblock_successorsare gone;
SsaCfg::from_ssais the terminator-derived relation andEhCfg::from_ssa
the exception-aware one, and which one an analysis needs is now a decision it
has to state. -
BREAKING:
SsaOp::Breakcarries aBreakpointOp. CILbreakis
SsaOp::Break(BreakpointOp::Breakpoint).BreakpointOpis re-exported from
dotscope::analysis. -
BREAKING:
ConstValuegained aSymbolvariant, 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::SymbolRefis an uninhabited type and the arm is unreachable by
construction. -
Target::handler_kindreplacesTarget::is_filter_handler.CilTarget
classifies through the existingExceptionHandlerFlags::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 pairedchunks_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_chunksyields the arrays themselves, so the
fallbacks and the bounds checks are gone with them.
Dependencies
analyssa0.5.0 → 0.6.0quick-xml0.41.0 → 0.42.0. Element names and attribute keys are&str
rather than&[u8], soPermissionSet's XML reader compares them directly
instead of decoding each one and reporting a UTF-8 error the parser has
already ruled out.z30.20.2 → 0.21.0
v0.9.0
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, andmax_unmanaged_bytesand
max_heap_objectswere 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 cyclicextendsgraph caused an uncatchable native stack overflow),
the x86 traversal (O(n²) to end of file), method-body decoding (disassembled
past the declaredcode_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
reachingMarshal.Copy,Stream.SetLength,StringBuilder.set_Length,
String.PadLeft/PadRight, theBinaryReaderreaders and the PBKDF2
constructors reservedusize::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 bymax_loaded_assembliesand
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 catchableAccessViolationException, and region mappings are overlap-checked. deny(unsafe_code)is enabled. Oneunsafeblock remains, for the writer's
output mapping, with a targeted allow and a SAFETY note.SECURITY.mdnow states the supported version, the realEmulationLimits
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 yieldResult,getreturns
Result<Option<T>>, andMetadataTable::newvalidates and truncates to the
declared extent. MethodPtr,EventPtrandPropertyPtrtokens used the wrong table id,
so any assembly carrying a*Ptrtable 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
finallyran against the
grandparent frame, queuedfinallyblocks were never drained once a catch was
selected, aleaveout of nestedfinallys spun onendfinally, 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;
sectionSizeOfRawDatacame 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.Outputnow 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_sliceis now denied, which surfaced ten genuine sites. - The fuzz crash-corpus regression test passed on any checkout without the
corpus, and CI rancargo 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,FieldMarshalandCustomAttributerows 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-rangeParamRID. 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 resolvesVirtualProtectthrough
LoadLibrary/GetProcAddressand 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 throughGetProcAddress
and invoked throughMarshal.GetDelegateForFunctionPointerbypassed 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...
v0.8.5
First release under ATRAPS LLC ownership. Two packaging defects fixed, and dependencies brought current.
Fixed
- No licence text was shipped with the crate.
LICENSEandNOTICElive at the workspace root, but the package root isdotscope/, and cargo only packages files under the package directory — so every published version declaredlicense = "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. LICENSEwas a symlink toLICENSE-APACHE. Symlinks do not survive packaging cleanly;LICENSEis 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 amainbranch that did not exist, so it was already dead.
Dependencies
analyssa0.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-table7.2.2 → 8.0.0. The preset-string API was removed in v8;load_preset(presets::NOTHING)becomesload_style(presets::NOTHING), presets now beingTableStyleconstants. Rendering is unchanged.- All remaining dependencies refreshed via
cargo update.
Changed
- Recorded ATRAPS LLC as copyright holder in
LICENSEandNOTICE(2025-2026, the range this history spans). - Dropped the deprecated
authorsfield from both workspace members; repointedrepository/homepageat the organisation. - Default branch renamed
master→main.masterappeared in eight places inci.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
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 withPassConfig::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.aand&o.bno longer alias. An unresolved (null) field token falls back to the sound whole-object approximation - x86 segment overrides reach the IR:
X86Memorygained asegmentfield, decoded from the instruction's explicit prefix, andfs:/gs:-qualified accesses now lower toLoadIndirect/StoreIndirectwith a distinctaddress_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 plusMemorySsa,IndirectLocation,ArrayIndex,AliasResult,MemoryDefSite,MemoryPhiOperand, andMemorySsaStats - 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 zeroConstfor primitives and references, andLoadLocalAddr; InitObj; LoadLocalfor 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 againstoriginal.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:
SentinelTaintRemovalPassandNeutralizationPassrewrote every tainted instruction toNopand dropped every tainted phi, destroying definitions that surviving code still read.PhiTaintMode::NoPropagationmakes 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.NeutralizationPassalso excludes its protectedDecryptedStringconstants from the candidate set rather than at rewrite time, so branch-target selection sees what is actually removed AssemblyDependencyGraph::find_cyclesreports 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 billionInstructionclones — 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, sinceILGeneratorcan mutate them - Emulation rebuilt the assembly context on every instruction:
loaded_assembly_contextconstructed a freshEmulationContextand took anRwLockper executed instruction; contexts are now memoized per assembly index optimize_localssplit values it renumbered (SsaFunctionCilExt): renumbering a local moved its origin but left its rename group pointing at the old slot.rebuild_ssagroups 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_visitsso 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_typescomputed 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 — onreactor_virtualizationthat 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, inO(V+E)rather thanO(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 throughexpand_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
int32alone, so builds emittingT Get<T>(uint32)yielded no decryptor at all — any integer width is now accepted, the surrounding constraints carrying the selectivity.stobjrejected reference types, though ECMA-335 §III.4.29 defines it over anytypeTokand makes it equivalent tostind.reffor reference types, which is whatstobj !!Tbecomes atT = string.ldelemahand-rolled its index match toI32/NativeIntwhileldelem/stelemshare a helper accepting every width, so anative uintindex aborted emulation. Andstfldthrough 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...
Release v0.8.3
Changes in v0.8.3
Installation
Library
Add this to your Cargo.toml:
[dependencies]
dotscope = "0.8.3"Or install via cargo:
cargo add dotscopeCLI 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
z3feature (CFF reconstruction). The pre-built CLI binaries do not require Z3 at runtime.
Release v0.8.2
Changes in v0.8.2
Installation
Library
Add this to your Cargo.toml:
[dependencies]
dotscope = "0.8.2"Or install via cargo:
cargo add dotscopeCLI 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
z3feature (CFF reconstruction). The pre-built CLI binaries do not require Z3 at runtime.
Release v0.8.1
Changes in v0.8.1
Installation
Library
Add this to your Cargo.toml:
[dependencies]
dotscope = "0.8.1"Or install via cargo:
cargo add dotscopeCLI 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
z3feature (CFF reconstruction). The pre-built CLI binaries do not require Z3 at runtime.
Release v0.8.0
Changes in v0.8.0
Installation
Library
Add this to your Cargo.toml:
[dependencies]
dotscope = "0.8.0"Or install via cargo:
cargo add dotscopeCLI 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
z3feature (CFF reconstruction). The pre-built CLI binaries do not require Z3 at runtime.
Release v0.7.0
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 dotscopeCLI 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
z3feature (CFF reconstruction). The pre-built CLI binaries do not require Z3 at runtime.
Release v0.6.0
Changes in v0.6.0
Installation
Library
Add this to your Cargo.toml:
[dependencies]
dotscope = "0.6.0"Or install via cargo:
cargo add dotscopeCLI 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 includeslibz3.dll).