Skip to content
Obfuscationadvanced

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.

text
; 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               ; next

Détection & contournement

  • Statique — Dans IDA/Ghidra, repérez un jmp/call indirect 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 via push/ret.
  • Dynamique — Tracez une exécution pour récupérer la vraie séquence d'opcodes : journalisez VIP et 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.
Votes

Commentaires(0)