
Devirtualizza le sezioni .tvm0 della VM Tencent: solleva i gestori della VM a listing a livello guest e LLVM IR, recupera il CFG e il dispatch, e applica le patch al codice recuperato nel PE.
Record Zenodo correlato: 10.5281/zenodo.23474767.
⚠️ Avvertenza: Questo progetto è destinato esclusivamente alla ricerca sulla sicurezza autorizzata, all'analisi di interoperabilità e all'istruzione. Non include né fornisce alcuna funzionalità di bypass per sistemi anti-cheat commerciali. Gli utenti sono gli unici responsabili di assicurarsi di avere il diritto legale di analizzare i binari di destinazione.
Un devirtualizzatore per Tencent VM. Recupera il codice virtualizzato in una sezione .tvm0, solleva ciò che ciascun gestore della VM fa ai registri e alla memoria del guest, e può riscrivere le funzioni recuperate nel binario come una normale sezione read+execute.
È uno strumento: puntalo a un modulo, ottieni indietro struttura e codice.
.tvm0 section -> VM semantics -> guest-level listing -> LLVM IR -> patched PE
Elenco devirtualizzato di una radice VM, con il file di registri guest associato tra i blocchi.

python -m venv .venv
source .venv/Scripts/activate # Windows: .venv\Scripts\Activate.ps1
pip install -r requirements.txt
Python 3.13, capstone. Per il backend: llvmlite==0.50.0 e, su Windows, MSVC
link.exe/cl.exe. z3-solver è opzionale — senza di esso il path conditioning resta e viene saltata
solo la prova del dispatch limitato.
Copia .env.example in .env e inserisci i percorsi per questa macchina. .env è ignorato da git, e
ogni impostazione ha anche un flag da riga di comando, quindi il file fornisce solo i valori predefiniti.
| chiave | significato |
|---|---|
TVM_MODULE | il modulo da analizzare, per le sonde eseguite senza un argomento posizionale |
TVM_LISTINGS | il corpus di listing che il backend legge |
TVM_OUT | radice per tutto l'output (predefinito out/) |
TVM_STEPS | passi per blocco prima che un blocco venga troncato (predefinito 900000) |
TVM_BLOCKS | budget di blocchi per radice (predefinito 512) |
TVM_PAYLOAD_OPT | livello di codegen del payload 0–3 |
TVM_PAYLOAD_MEM | modalità memoria guest sealed | absolute |
TVM_MSVC_BIN | directory contenente link.exe/cl.exe, invece di affidarsi al PATH |
Precedenza: flag da riga di comando → .env → ambiente → valore predefinito integrato.
I comandi dell'engine si eseguono da engine/; i comandi del backend dalla radice del repository.
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
Un elenco 0x<root>.txt e un 0x<root>.guest-state.json per funzione. Opzioni:
| flag | default | significato |
|---|---|---|
--out=DIR | stdout | scrive elenchi + guest state |
--root=0x… | tutti quelli scoperti | limita a una singola voce VM |
--show[=N] | disattivato | stampa le prime N righe |
--steps=N | 900000 | passi per blocco prima del troncamento |
--blocks=N | 512 | budget di blocchi per root |
--caller-entry=0x… --call-site=0x… | disattivato | specializza la voce per il chiamante |
L'intestazione di ciascun elenco indica se l'esecuzione è stata troncata; un elenco troncato è un limite inferiore, non la funzione.
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
Esamina un valore o uno store specifico:
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'
Esegue payload → inject → load → differential e si ferma alla prima fase che fallisce. Opzioni:
-Listings, -Out, -Opt 0..3, -Mem sealed|absolute, -SkipDifferential, -Force.
Le fasi singolarmente:
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 è il controllo che conta davvero: esegue il codice all'interno dell'immagine patchata e
lo confronta byte per byte con l'interprete sullo stesso listato. miniload.py dimostra soltanto
che l'immagine si carica e che il suo entry point viene eseguito.
ACE sostituisce una funzione nativa con un trampolino verso un dispatcher della VM. Il dispatcher esegue handler nativi x86, ma lo stato architetturale della funzione protetta risiede nel contesto della 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
Alcune operazioni vengono eseguite al di fuori della VM. Un'istruzione boxed esegue la sua codifica x86 originale dopo che la VM ha ripristinato i registri necessari, quindi restituisce i suoi effetti al contesto guest. Una guest call invoca un target nativo e riprende con il valore di ritorno e i clobber dell'ABI Windows x64 rappresentati nello stato guest. I guest return e le tail call abbandonano completamente il dispatcher.
Il contesto contiene tutti e sedici i registri general-purpose guest, incluso il guest RSP, più
i guest flag impacchettati. Il suo layout varia in base alla build del protector, quindi viene
localizzato osservando il comportamento di salvataggio/ripristino della VM anziché essere
presunto — probe.ctx_map riporta la mappatura misurata in corrispondenza degli store, e ogni
listing emesso racchiude un accesso al contesto con il suo offset di slot.