
Desvirtualiza las secciones .tvm0 de la VM de Tencent: eleva los manejadores de la VM a listados a nivel de invitado y a LLVM IR, recupera el CFG y el despacho, y aplica parches al código recuperado de vuelta en el PE.
Registro relacionado de Zenodo: 10.5281/zenodo.23474767.
⚠️ Aviso legal: Este proyecto está destinado estrictamente a la investigación de seguridad autorizada, el análisis de interoperabilidad y la educación. No incluye ni proporciona ninguna funcionalidad de elusión para sistemas anti-cheat comerciales. Los usuarios son los únicos responsables de asegurarse de que tienen el derecho legal de analizar los binarios objetivo.
Un desvirtualizador para Tencent VM. Recupera el código virtualizado en una sección .tvm0, eleva lo que hace cada manejador de la VM sobre los registros y la memoria del invitado, y puede escribir las funciones recuperadas de vuelta en el binario como una sección normal de lectura+ejecución.
Es una herramienta: apúntala a un módulo, obtén la estructura y el código de vuelta.
.tvm0 section -> VM semantics -> guest-level listing -> LLVM IR -> patched PE
Listado desvirtualizado de una raíz de VM, con el archivo de registros del invitado vinculado entre bloques.

python -m venv .venv
source .venv/Scripts/activate # Windows: .venv\Scripts\Activate.ps1
pip install -r requirements.txt
Python 3.13, capstone. Para el backend: llvmlite==0.50.0 y, en Windows, MSVC
link.exe/cl.exe. z3-solver es opcional — sin él se mantiene el condicionamiento de rutas y solo se omite la
prueba de despacho acotada.
Copie .env.example a .env y complete las rutas para esta máquina. .env está ignorado por git, y
cada ajuste también tiene una opción de línea de comandos, por lo que el archivo solo proporciona valores predeterminados.
| clave | significado |
|---|---|
TVM_MODULE | el módulo a analizar, para sondas ejecutadas sin un argumento posicional |
TVM_LISTINGS | corpus de listados que lee el backend |
TVM_OUT | raíz de toda la salida (predeterminado out/) |
TVM_STEPS | pasos por bloque antes de que un bloque se trunque (predeterminado 900000) |
TVM_BLOCKS | presupuesto de bloques por raíz (predeterminado 512) |
TVM_PAYLOAD_OPT | nivel de generación de código del payload 0–3 |
TVM_PAYLOAD_MEM | modo de memoria de invitado sealed | absolute |
TVM_MSVC_BIN | directorio que contiene link.exe/cl.exe, en lugar de depender de PATH |
Precedencia: opción de línea de comandos → .env → entorno → valor predeterminado integrado.
Los comandos del motor se ejecutan desde engine/; los comandos del backend desde la raíz del repositorio.
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 listado 0x<root>.txt y un 0x<root>.guest-state.json por función. Opciones:
| flag | por defecto | significado |
|---|---|---|
--out=DIR | stdout | escribir listados + estado del guest |
--root=0x… | todos los descubiertos | restringir a una entrada de VM |
--show[=N] | desactivado | imprimir las primeras N líneas |
--steps=N | 900000 | pasos por bloque antes del truncamiento |
--blocks=N | 512 | presupuesto de bloques por root |
--caller-entry=0x… --call-site=0x… | desactivado | especializar la entrada por el llamador |
La cabecera de cada listado indica si la ejecución fue truncada; un listado truncado es una cota inferior, no la función.
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
Profundice en un valor o almacén específico:
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'
Ejecuta payload → inject → load → differential y se detiene en la primera etapa que falla. Opciones:
-Listings, -Out, -Opt 0..3, -Mem sealed|absolute, -SkipDifferential, -Force.
Las etapas individualmente:
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 es la comprobación que importa: ejecuta el código dentro de la imagen parcheada y
lo compara byte por byte con el intérprete sobre el mismo listado. miniload.py solo demuestra
que la imagen carga y que su entrada se ejecuta.
ACE reemplaza una función nativa con un trampolín hacia un despachador de VM. El despachador ejecuta manejadores nativos de x86, pero el estado arquitectónico de la función protegida reside en el contexto 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
Algunas operaciones se ejecutan fuera de la VM. Una instrucción boxed ejecuta su codificación x86 original después de que la VM restaure los registros que necesita, y luego devuelve sus efectos al contexto del guest. Una llamada guest invoca un destino nativo y se reanuda con el valor de retorno y los clobbers del ABI de Windows x64 representados en el estado del guest. Los retornos guest y las llamadas de cola abandonan el dispatcher por completo.