Code Virtualization
Native instructions are translated into a custom bytecode executed by an embedded interpreter, so a reverser sees a fetch-decode-dispatch loop instead of the original machine code.
Code virtualization is the strongest commercial software-protection primitive. The
protector lifts a region of native code into a custom instruction set (the
bytecode) and ships an embedded interpreter — a virtual machine — that executes
that bytecode at runtime. The original x86/x64 is gone; the reverser instead
faces an opcode handler table and a dispatch loop. It is used by VMProtect,
Themida/WinLicense, Code Virtualizer and the academic Tigress tool.
How it works
The VM keeps its own virtual context (a struct or a region of the native stack)
holding virtual registers, a virtual instruction pointer (VIP) and a virtual
stack pointer. The core is a fetch → decode → dispatch → next loop: read the next
bytecode opcode, index a handler table, run the handler, advance VIP.
; native (before virtualization)
add eax, ebx
xor ecx, eax
; after virtualization the same logic becomes bytecode + a dispatcher:
vm_loop:
movzx eax, byte [VIP] ; fetch opcode
inc VIP
jmp [handler_table + eax*8] ; dispatch
handler_VADD: ; one handler per virtual opcode
pop_vreg r0
pop_vreg r1
add r0, r1
push_vreg r0
jmp vm_loop ; nextDetection & bypass
- Static — In IDA/Ghidra look for an indirect
jmp/callthrough a table inside a tight loop (the dispatcher), plus a cluster of short, structurally similar functions (the handlers) that all jump back to the same loop head. A large opaque "VM entry" stub that pushes all registers and a context pointer is a strong marker. VMProtect handlers are heavily MBA-obfuscated and chained throughpush/ret. - Dynamic — Trace one execution to recover the real opcode sequence: log
VIPand the dispatched handler address at every loop iteration. Emulation (Unicorn/Qiling) or DBI lets you record the bytecode stream and the side-effects of each handler, which reveals the semantics of each virtual opcode without manual reading. Symbolic execution (Triton, Miasm) of a single handler yields its transfer function. - Patch / simplify — Map each virtual opcode to its native semantics, then lift the recorded bytecode into an IR and re-emit simplified native code (devirtualization). The output replaces the VM region.
- Tools — VTIL for lifting/optimization, NoVmp for VMProtect 2.x, Triton and Miasm for symbolic handler analysis, Tigress's own devirtualization research passes, and manual tracing in x64dbg.