Code Virtualization
Les instructions natives sont traduites en un bytecode personnalisé exécuté par un interpréteur embarqué : le reverser voit une boucle fetch-decode-dispatch au lieu du code machine d'origine.
La virtualisation de code est la primitive de protection logicielle commerciale la
plus robuste. Le protecteur transforme une région de code natif en un jeu
d'instructions personnalisé (le bytecode) et embarque un interpréteur — une
machine virtuelle — qui exécute ce bytecode à l'exécution. Le x86/x64
d'origine a disparu ; le reverser fait face à une table de handlers et à une boucle
de dispatch. Cette technique est utilisée par VMProtect, Themida/WinLicense, Code
Virtualizer et l'outil universitaire Tigress.
Fonctionnement
La VM maintient son propre contexte virtuel (une structure ou une région de la pile
native) contenant des registres virtuels, un pointeur d'instruction virtuel (VIP)
et un pointeur de pile virtuel. Le cœur est une boucle fetch → decode → dispatch → next : lire le prochain opcode du bytecode, indexer une table de handlers,
exécuter le handler, avancer VIP.
; natif (avant virtualisation)
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 ; nextDétection & contournement
- Statique — Dans IDA/Ghidra, repérez un
jmp/callindirect via une table au sein d'une boucle serrée (le dispatcher), ainsi qu'un ensemble de fonctions courtes et structurellement similaires (les handlers) qui sautent toutes vers la même tête de boucle. Un large stub opaque d'« entrée de VM » qui empile tous les registres et un pointeur de contexte est un marqueur fort. Les handlers VMProtect sont fortement obfusqués en MBA et chaînés viapush/ret. - Dynamique — Tracez une exécution pour récupérer la vraie séquence d'opcodes :
journalisez
VIPet l'adresse du handler dispatché à chaque itération. L'émulation (Unicorn/Qiling) ou le DBI permet d'enregistrer le flux de bytecode et les effets de bord de chaque handler, ce qui révèle la sémantique de chaque opcode virtuel sans lecture manuelle. L'exécution symbolique (Triton, Miasm) d'un seul handler en donne la fonction de transfert. - Patch / simplification — Associez chaque opcode virtuel à sa sémantique native, puis liftez le bytecode enregistré vers une IR et ré-émettez un code natif simplifié (dévirtualisation). La sortie remplace la région VM.
- Outils — VTIL pour le lifting/optimisation, NoVmp pour VMProtect 2.x, Triton et Miasm pour l'analyse symbolique des handlers, les passes de recherche en dévirtualisation de Tigress, et le traçage manuel dans x64dbg.