
Dévirtualise les sections .tvm0 de la VM Tencent : élève les gestionnaires de la VM vers des listings au niveau invité et du LLVM IR, récupère le CFG et le dispatch, et réinjecte le code récupéré dans le PE.
Enregistrement Zenodo associé : 10.5281/zenodo.23474767.
⚠️ Avertissement : Ce projet est strictement destiné à la recherche en sécurité autorisée, à l'analyse d'interopérabilité et à l'éducation. Il n'inclut ni ne fournit aucune fonctionnalité de contournement pour les systèmes anti-triche commerciaux. Les utilisateurs sont seuls responsables de s'assurer qu'ils disposent du droit légal d'analyser les binaires cibles.
Un dévirtualiseur pour Tencent VM. Il récupère le code virtualisé dans une section .tvm0, extrait ce que chaque gestionnaire de VM fait aux registres et à la mémoire de l'invité, et peut réécrire les fonctions récupérées dans le binaire sous forme de section normale en lecture+exécution.
C'est un outil : pointez-le vers un module, récupérez la structure et le code en sortie.
.tvm0 section -> VM semantics -> guest-level listing -> LLVM IR -> patched PE
Listing dévirtualisé d'une racine de VM, avec le fichier de registres de l'invité lié à travers les blocs.

python -m venv .venv
source .venv/Scripts/activate # Windows: .venv\Scripts\Activate.ps1
pip install -r requirements.txt
Python 3.13, capstone. Pour le backend : llvmlite==0.50.0 et, sous Windows, MSVC
link.exe/cl.exe. z3-solver est optionnel — sans lui, le conditionnement de chemin est conservé et seule la
preuve de répartition bornée est ignorée.
Copiez .env.example vers .env et renseignez les chemins pour cette machine. .env est ignoré par git, et
chaque paramètre dispose également d'un indicateur en ligne de commande, le fichier ne fournissant donc que des valeurs par défaut.
| clé | signification |
|---|---|
TVM_MODULE | le module à analyser, pour les sondes exécutées sans argument positionnel |
TVM_LISTINGS | corpus de listings que lit le backend |
TVM_OUT | racine de toutes les sorties (par défaut out/) |
TVM_STEPS | étapes par bloc avant qu'un bloc soit tronqué (par défaut 900000) |
TVM_BLOCKS | budget de blocs par racine (par défaut 512) |
TVM_PAYLOAD_OPT | niveau de génération de code du payload 0–3 |
TVM_PAYLOAD_MEM | mode de mémoire invitée sealed | absolute |
TVM_MSVC_BIN | répertoire contenant link.exe/cl.exe, au lieu de se fier au PATH |
Priorité : indicateur en ligne de commande → .env → environnement → valeur par défaut intégrée.
Les commandes du moteur s'exécutent depuis engine/ ; les commandes du backend depuis la racine du dépôt.
cd engine
python -m probe.scan_tvm0 /path/to/dir # which files carry a .tvm0, and its roots
python -m probe.trampoline_forms input.exe # what the entry trampolines look like
python -m probe.mnemonic_census input.exe # what x86 the VM contains
python -m core.devirt input.exe --out ../out/listings
Une liste 0x<root>.txt et un fichier 0x<root>.guest-state.json par fonction. Options :
| flag | défaut | signification |
|---|---|---|
--out=DIR | stdout | écrire les listings + l'état invité |
--root=0x… | tous découverts | restreindre à une seule entrée de VM |
--show[=N] | désactivé | afficher les N premières lignes |
--steps=N | 900000 | étapes par bloc avant troncature |
--blocks=N | 512 | budget de blocs par racine |
--caller-entry=0x… --call-site=0x… | désactivé | spécialiser l'entrée selon l'appelant |
L'en-tête de chaque listing indique si l'exécution a été tronquée ; un listing tronqué est une borne inférieure, pas la fonction.
python -m probe.cfg_run input.exe 0x14015fc2e # walk it, print the CFG
python -m probe.guest_effect input.exe 0x14015fc2e # what handlers do to guest registers
python -m probe.ctx_map input.exe 0x14015fc2e # which context slot holds which register
python -m probe.why_unresolved input.exe 0x14015fc2e # why a dispatch did not resolve
python -m probe.block_exits input.exe 0x14015fc2e # why a block has no successor
python -m probe.stop_debug input.exe 0x14015fc2e # why evaluation stopped
python -m probe.unresolved_detail input.exe 0x14015fc2e # per-block detail
Explorez une valeur ou un stockage spécifique :
python -m probe.trace_window input.exe 0x14015fc2e 0x14015fc40 0x14015fc90
python -m probe.reg_history input.exe 0x14015fc2e rax
python -m probe.who_wrote input.exe 0x14015fc2e 0x18
python -m probe.flags_at input.exe 0x14015fc2e 0x14015fc60
python -m probe.stack_at input.exe 0x14015fc2e
.\write-devirt.ps1 -Module 'C:\path\to\SGuardSvc64.exe'
Exécute payload → inject → load → differential et s'arrête à la première étape qui échoue. Options :
-Listings, -Out, -Opt 0..3, -Mem sealed|absolute, -SkipDifferential, -Force.
Les étapes individuellement :
cd llvm
python payload.py --listings=../out/hard-cases-listings-final --out=../out/write-devirt --opt=0 --mem=sealed
python peinject.py --host=input.exe --payload=../out/write-devirt --out=../out/write-devirt/input.neko.exe
python miniload.py ../out/write-devirt/input.neko.exe
python diff_payload.py --payload=../out/write-devirt --patched=../out/write-devirt/input.neko.exe
diff_payload.py est la vérification qui compte : il exécute le code à l'intérieur de l'image patchée et
le compare octet par octet à l'interpréteur sur le même listing. miniload.py prouve seulement
que l'image se charge et que son point d'entrée s'exécute.
ACE remplace une fonction native par un trampoline vers un dispatcher de VM. Le dispatcher exécute des gestionnaires x86 natifs, mais l'état architectural de la fonction protégée réside dans le contexte de la VM.
flowchart TB
subgraph TOP[ ]
direction LR
subgraph TEXT[.text]
ENTRY["<b>Function entry</b><br/>jump to VM entry"]
end
subgraph VM[.tvm0 section]
direction LR
CORE["<b>Shared dispatcher and handlers</b><br/>execute x86 - resolve next handler"]
CTX["<b>Guest context</b><br/>GPRs - RFLAGS - VIP"]
CORE <--> CTX
end
end
subgraph NATIVE[Native execution outside the VM]
direction LR
BOXED["<b>Boxed instruction</b><br/>execute original x86"]
CALL["<b>Guest call</b><br/>invoke native target"]
EXIT["<b>Guest return or tail call</b><br/>resume native execution"]
BOXED ~~~ CALL ~~~ EXIT
end
TEXT --> VM
VM <--> NATIVE
style TOP fill:transparent,stroke:transparent
Certaines opérations s'exécutent en dehors de la VM. Une instruction boxée exécute son encodage x86 d'origine après que la VM a restauré les registres dont elle a besoin, puis renvoie ses effets au contexte invité. Un appel invité invoque une cible native et reprend avec la valeur de retour et les clobbers de l'ABI Windows x64 représentés dans l'état invité. Les retours invités et les appels de queue quittent entièrement le dispatcheur.