
Desvirtualiza as seções .tvm0 da Tencent VM: eleva os handlers da VM para listagens em nível de guest e LLVM IR, recupera o CFG e o despacho, e aplica o código recuperado de volta no PE.
Registro Zenodo relacionado: 10.5281/zenodo.23474767.
⚠️ Aviso: Este projeto destina-se estritamente a pesquisa de segurança autorizada, análise de interoperabilidade e educação. Não inclui nem fornece qualquer funcionalidade de contorno para sistemas anti-cheat comerciais. Os usuários são os únicos responsáveis por garantir que têm o direito legal de analisar os binários alvo.
Um desvirtualizador para Tencent VM. Recupera o código virtualizado numa secção .tvm0,
eleva o que cada handler da VM faz aos registos e memória do guest, e pode escrever as
funções recuperadas de volta no binário como uma secção normal de leitura+execução.
É uma ferramenta: aponta-a para um módulo, obtém a estrutura e o código de volta.
.tvm0 section -> VM semantics -> guest-level listing -> LLVM IR -> patched PE
Listagem desvirtualizada de uma raiz de VM, com o arquivo de registradores do convidado vinculado entre blocos.

python -m venv .venv
source .venv/Scripts/activate # Windows: .venv\Scripts\Activate.ps1
pip install -r requirements.txt
Python 3.13, capstone. Para o backend: llvmlite==0.50.0 e, no Windows, MSVC
link.exe/cl.exe. z3-solver é opcional — sem ele o condicionamento de caminho permanece e apenas a
prova de despacho limitada é ignorada.
Copie .env.example para .env e preencha os caminhos para esta máquina. .env é ignorado pelo git, e
cada configuração também tem uma flag de linha de comando, portanto o arquivo apenas fornece os padrões.
| chave | significado |
|---|---|
TVM_MODULE | o módulo a analisar, para sondagens executadas sem um argumento posicional |
TVM_LISTINGS | corpus de listagens que o backend lê |
TVM_OUT | raiz para toda a saída (padrão out/) |
TVM_STEPS | passos por bloco antes de um bloco ser truncado (padrão 900000) |
TVM_BLOCKS | orçamento de blocos por raiz (padrão 512) |
TVM_PAYLOAD_OPT | nível de codegen do payload 0–3 |
TVM_PAYLOAD_MEM | modo de memória guest sealed | absolute |
TVM_MSVC_BIN | diretório que contém link.exe/cl.exe, em vez de depender do PATH |
Precedência: flag de linha de comando → .env → ambiente → padrão embutido.
Os comandos do engine são executados a partir de engine/; os comandos do backend a partir da raiz do repositório.
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
Uma listagem 0x<root>.txt e um 0x<root>.guest-state.json por função. Opções:
| flag | padrão | significado |
|---|---|---|
--out=DIR | stdout | grava listagens + estado do guest |
--root=0x… | todos descobertos | restringe a uma entrada de VM |
--show[=N] | desativado | imprime as primeiras N linhas |
--steps=N | 900000 | passos por bloco antes do truncamento |
--blocks=N | 512 | orçamento de blocos por root |
--caller-entry=0x… --call-site=0x… | desativado | especializa a entrada pelo chamador |
O cabeçalho de cada listagem indica se a execução foi truncada; uma listagem truncada é um limite inferior, não a função.
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
Aprofunde-se em um valor ou armazenamento 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'
Executa payload → injeta → carrega → diferencial e para no primeiro estágio que falhar. Opções:
-Listings, -Out, -Opt 0..3, -Mem sealed|absolute, -SkipDifferential, -Force.
Os estágios 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 é a verificação que importa: ele executa o código dentro da imagem corrigida e
o compara byte a byte com o interpretador sobre a mesma listagem. miniload.py apenas prova
que a imagem carrega e que sua entrada é executada.
O ACE substitui uma função nativa por um trampolim para um despachante de VM. O despachante executa handlers x86 nativos, mas o estado arquitetural da função protegida reside no contexto da 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
Algumas operações são executadas fora da VM. Uma instrução boxed executa sua codificação x86 original depois que a VM restaura os registradores de que precisa, e então devolve seus efeitos ao contexto do guest. Uma chamada de guest invoca um alvo nativo e retoma com o valor de retorno e os clobbers da ABI x64 do Windows representados no estado do guest. Retornos de guest e tail calls abandonam o dispatcher por completo.
O contexto contém todos os dezesseis registradores de uso geral do guest, incluindo o RSP do guest, além das flags do guest empacotadas. Seu layout varia conforme a build do protetor, portanto é localizado observando o comportamento de save/restore da VM em vez de ser presumido — probe.ctx_map reporta o mapeamento medido nos stores, e cada listagem emitida delimita um acesso ao contexto com o offset do seu slot.