Bundled with Unleash skills pack. Source: C:\Users\Admin.agents\skills\virtualization-deobfuscation\SKILL.md
Virtualization Deobfuscation
Overview
Virtualization replaces native semantics with an interpreter whose ISA, dispatch, state, encoding, and handlers may be randomized per build. The robust objective is not to assign folklore names to handlers. It is to recover a verified relation between protected inputs, state transitions, branches, memory effects, and outputs.
Back Engineering Labs’ current guidance explicitly treats its VMProtect 2 handler-identification project as legacy and brittle (aftermathlabs/vmp2). Apply that principle across protectors.
Aftermath 2026 writeups (use these as the current method, not vmp2):
- 2026-05-09 Static Devirtualization of Themida — guided symbolic eval; const promotion & memory model; fold / DSE / combine / branch fold; VMEXIT + virtual CF; dead-dependency + RSP rewrite; 1:1 lower (BLARE2). Corpus:
aftermathlabs/themida-devirt. - 2026-07-31 Static Devirtualization of Tencent VM — boxed instructions, CET/SEH compatibility, guided SE, virtualized jcc, MBA identity reduction, coverage stats. ACE/Proton interoperability framing.
Do not lower through LLVM just to pretty-print. Aftermath’s stated preference is 1:1 lift/lower (BLARE2). Mergen/Dna remain valid when LLVM is the chosen product (llvm-lift-deobfuscation).
Authorization Boundary
Use for owned or explicitly authorized binaries, research challenges, and isolated malware analysis. Game binaries may be studied only offline and lawfully. Do not disable anti-cheat, instrument live multiplayer clients, bypass integrity/attestation, extract secrets, or create cheating capabilities.
Tracing target execution, emulation, process-memory access, rewriting, or replacing protected code requires explicit scope. Static analysis remains the default.
Vocabulary Without Assumptions
Track observed roles, not fixed registers:
- VM entry/exit: native-to-virtual and virtual-to-native boundaries
- virtual context: state representing virtual registers/flags/stack
- VIP: virtual instruction position or equivalent cursor
- VSP: virtual stack position or equivalent state
- dispatcher: mechanism selecting the next semantic fragment
- handler/semantic fragment: code contributing a virtual operation
- bytecode/operand stream: encoded VM program and operands
- transform: encoding/decoding or rolling-key operation
A role can move across registers/memory, be split among blocks, be implicit, or be interlinked with other handlers. Assign it only from data-flow evidence.
Choose the Recovery Product
- Behavioral formula: input/output relation for a bounded pure region.
- Recovered CFG: virtual blocks and branch conditions.
- Normalized IR: lifted side effects suitable for analysis.
- Readable pseudocode: semantically useful but not necessarily recompilable.
- Recompiled/native replacement: highest validation burden and explicit mutation scope.
Done when the chosen product and equivalence boundary are written before building automation.
Stage 1 — Establish VM Boundaries
- Start from proven native anchors, calls, protected macros if source exists, or stable transitions from unpacking.
- Identify state initialization, context save/restore, bytecode source, dispatcher loop, exits, and interactions with native APIs.
- Record module/RVA, architecture, image version, calling convention, live inputs/outputs, memory regions, and exception behavior.
- Compare at least two entries or builds before labeling a pattern protector-wide.
Done when a bounded VM invocation can be described as a state transformer with explicit inputs and outputs.
Stage 2 — Recover State Roles by Invariants
Use value and data-flow invariants:
- a cursor advances/decodes from a bytecode-like region
- a stack/context pointer indexes repeated VM state accesses
- a rolling key feeds operand transforms
- dispatch targets depend on decoded opcode/state
- exits restore native ABI-visible state
Corroborate with dynamic traces or symbolic dependencies. Do not hardcode “VIP is RSI” or a handler catalog unless pinned to one hash/build and guarded by validation.
Static Incremental-Lifting Pipeline
Use when code and VM stream are sufficiently available:
- Decode native instructions from proven boundaries.
- Lift exact side effects into an IR preserving bit widths, flags, memory, and partial registers.
- Model the VM stack/context as memory first; do not prematurely invent virtual registers.
- Propagate stores to loads where aliasing is proven.
- Propagate constants from immutable sections and decoded operand streams.
- Simplify address arithmetic and normalize register fragments/flags.
- Eliminate dead computations relative to selected observable outputs.
- Recover indirect targets and split blocks only when target sets are justified.
- Iterate propagation, simplification, DCE, and CFG recovery to a fixed point.
- Introduce VM-specific semantics only for operations the generic passes cannot expose.
Completion criterion: each iteration reduces a named metric—live IR operations, unresolved indirect targets, unknown memory dependencies, or dispatcher cycles—without failing equivalence fixtures.
Do not lower to LLVM merely because it is available. Back Engineering Labs reports that one-to-one lifting/lowering can produce cleaner results than forcing all semantics through LLVM. Choose IR based on precise semantics and validation tooling.
Dynamic Trace/Symbolic Pipeline
Use when self-modification, encrypted operands, or opaque dispatch defeat static recovery:
- Define exact VM entry/exit and symbolize only intended inputs.
- Capture ordered instructions, code versions, register state needed for replay, memory reads/writes, branches, exceptions, and module mappings.
- Replay with precise instruction semantics.
- Taint from protected inputs and selected state; concretize VM machinery only when independence is proven.
- Build formulas forward, then backward-slice from observable outputs and path conditions.
- Simplify/synthesize expressions while preserving width, signedness, and undefined behavior.
- Invert user-dependent path predicates to obtain contrasting inputs.
- Merge traces at shared prefixes into guarded expressions/CFG.
- Mark uncovered paths and symbolic-address limitations explicitly.
Jonathan Salwan’s VMProtect experiments demonstrate this path-oriented method for bounded pure functions, but also document limitations: path explosion, symbolic memory, loops/calls, and incomplete multi-path recovery. Do not generalize a successful pure-function result to arbitrary stateful code.
Virtual Branch Recovery
Classify a transition using dependencies, not native jcc appearance:
- unconditional virtual transfer
- input-dependent conditional transfer
- dispatcher recurrence
- native call/return bridge
- exception-mediated transfer
- VM exit
For a candidate virtual condition, prove that changing a solver-derived or contrast input changes the virtual successor while keeping unrelated setup fixed. Record both traces and the formula slice.
MBA and Data-Encoding Simplification
- Preserve exact bit-vector widths and flag semantics.
- Slice from selected output/state.
- Constant-fold VM-only state.
- Canonicalize commutative operations and extensions/truncations.
- Use SMT equality checks or synthesis for bounded expressions.
- Apply compiler optimization only after semantics are represented without accidental undefined behavior.
- Validate candidate simplifications on solver counterexamples and concrete differential fixtures.
A shorter formula is not automatically the original source and may not preserve poison/undefined semantics in a compiler IR.
Nested and Interlinked VMs
For nested virtualization or handlers that execute partial semantics:
- keep a call/entry stack of VM contexts
- tag operations by context and code version
- recover observable state at each boundary
- permit semantic fragments to span multiple dispatcher visits
- use slicing to group fragments by contribution rather than address adjacency
Stop seeking one-handler/one-opcode correspondence when evidence contradicts it.
Equivalence Ladder
- Decoder: native bytes decode identically in an independent decoder.
- Trace replay: replay reproduces recorded registers/memory at checkpoints.
- Local semantic: lifted fragment matches original over concrete and solver-generated states.
- Path: recovered formula matches protected path outputs/side effects.
- CFG: contrasting inputs reach corresponding recovered successors.
- Function: differential tests cover boundaries, errors, loops, and memory effects.
- Translation: if LLVM is used, apply translation validation such as Alive2 where its model applies, plus concrete testing.
Report timeouts, unsupported instructions, unconstrained memory, and uncovered paths. “Solver returned unknown” is not equivalence.
Tool Routing
- IDA database and decompiler:
ida-pro-mcp - trace acquisition/normalization:
trace-guided-deobfuscation - opaque predicates/CFF/MBA:
binary-obfuscation-deconstruction - exact x86 semantics checks:
zydis-disassembly-engineering - terminal static work:
radare2-terminal-re
Historical projects such as backengineering/vmp2, NoVmp/VTIL, and version-specific handler tables are architecture case studies, not universal current solutions.
Required Report
Include target hash/build; protector/version confidence; VM entry/exit RVAs; observable state boundary; state-role evidence; static/dynamic method; IR semantics; simplification metrics; recovered branches/paths; unsupported cases; equivalence fixtures and solver results; artifact paths/hashes; and residual protection.
Common Pitfalls
- Hardcoding handler names/register roles from another build.
- Treating every dispatcher target as one virtual instruction.
- Losing partial-register, flag, or memory-alias semantics during lifting.
- Concretizing input-dependent VM state.
- Claiming whole-function recovery from one trace.
- Ignoring code versions in self-modifying handlers.
- Trusting prettier LLVM/pseudocode without translation validation.
- Recompiling/reinjecting before the recovered model is independently equivalent.
Verification Checklist
- authorization and offline boundary recorded
- VM invocation inputs/outputs and recovery product defined
- state roles proven by invariants/data flow
- lifting preserves widths, flags, memory, and code versions
- VM-specific rules minimized and build-scoped
- user-dependent branches tested with contrasting inputs
- path/memory/loop limitations explicit
- recovered semantics differentially or formally checked
- no live anti-cheat/service bypass or unrequested rewriting occurred