
Девиртуализирует секции .tvm0 виртуальной машины Tencent: поднимает обработчики VM до листингов гостевого уровня и LLVM IR, восстанавливает CFG и диспетчеризацию, а также встраивает восстановленный код обратно в PE.
Связанная запись Zenodo: 10.5281/zenodo.23474767.
⚠️ Отказ от ответственности: Этот проект предназначен строго для авторизованных исследований в области безопасности, анализа совместимости и обучения. Он не содержит и не предоставляет никаких функций обхода для коммерческих античит-систем. Пользователи несут полную ответственность за наличие у них законного права на анализ целевых бинарных файлов.
Девиртуализатор для Tencent VM. Восстанавливает виртуализированный код в секции .tvm0, поднимает то, что каждый обработчик VM делает с регистрами и памятью гостя, и может записать восстановленные функции обратно в бинарный файл как обычную секцию read+execute.
Это инструмент: укажите ему на модуль — получите обратно структуру и код.
.tvm0 section -> VM semantics -> guest-level listing -> LLVM IR -> patched PE
Девиртуализированный листинг корня виртуальной машины с привязкой файла регистров гостя между блоками.

python -m venv .venv
source .venv/Scripts/activate # Windows: .venv\Scripts\Activate.ps1
pip install -r requirements.txt
Python 3.13, capstone. Для бэкенда: llvmlite==0.50.0 и, на Windows, MSVC
link.exe/cl.exe. z3-solver необязателен — без него сохраняется обусловливание путей и пропускается
только доказательство ограниченной диспетчеризации.
Скопируйте .env.example в .env и заполните пути для этой машины. .env игнорируется git, и
у каждой настройки также есть флаг командной строки, поэтому файл лишь задаёт значения по умолчанию.
| ключ | значение |
|---|---|
TVM_MODULE | модуль для анализа, для проб, запускаемых без позиционного аргумента |
TVM_LISTINGS | корпус листингов, который читает бэкенд |
TVM_OUT | корневой каталог для всех выходных данных (по умолчанию out/) |
TVM_STEPS | шагов на блок до усечения блока (по умолчанию 900000) |
TVM_BLOCKS | бюджет блоков на корень (по умолчанию 512) |
TVM_PAYLOAD_OPT | уровень кодогенерации полезной нагрузки 0–3 |
TVM_PAYLOAD_MEM | режим гостевой памяти sealed | absolute |
TVM_MSVC_BIN | каталог, содержащий link.exe/cl.exe, вместо использования PATH |
Приоритет: флаг командной строки → .env → окружение → встроенное значение по умолчанию.
Команды движка запускаются из engine/; команды бэкенда — из корня репозитория.
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
Один листинг 0x<root>.txt и один 0x<root>.guest-state.json на каждую функцию. Опции:
| флаг | по умолчанию | значение |
|---|---|---|
--out=DIR | stdout | записывать листинги + состояние гостя |
--root=0x… | все обнаруженные | ограничить одной точкой входа VM |
--show[=N] | выкл. | вывести первые N строк |
--steps=N | 900000 | шагов на блок до усечения |
--blocks=N | 512 | бюджет блоков на корень |
--caller-entry=0x… --call-site=0x… | выкл. | специализировать точку входа по вызывающему |
Заголовок каждого листинга указывает, был ли запуск усечён; усечённый листинг — это нижняя граница, а не функция.
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
Углубитесь в конкретное значение или хранилище:
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'
Запускает цепочку payload → inject → load → differential и останавливается на первом этапе, который завершился неудачей. Параметры:
-Listings, -Out, -Opt 0..3, -Mem sealed|absolute, -SkipDifferential, -Force.
Этапы по отдельности:
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 — это та проверка, которая имеет значение: она запускает код внутри пропатченного образа и сравнивает его побайтово с интерпретатором на том же листинге. miniload.py лишь доказывает, что образ загружается и его точка входа выполняется.
ACE заменяет нативную функцию трамплином в диспетчер виртуальной машины. Диспетчер выполняет нативные обработчики x86, но архитектурное состояние защищённой функции живёт в контексте виртуальной машины.
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
Некоторые операции выполняются вне виртуальной машины. Инструкция в боксе запускает свою исходную x86-кодировку после того, как виртуальная машина восстанавливает необходимые ей регистры, а затем возвращает свои эффекты в контекст гостя. Гостевой вызов вызывает нативную цель и возобновляется с возвращаемым значением и затираниями ABI Windows x64, представленными в состоянии гостя. Гостевые возвраты и хвостовые вызовы полностью покидают диспетчер.
Контекст содержит все шестнадцать регистров общего назначения гостя, включая гостевой RSP, а также упакованные флаги гостя. Его раскладка зависит от сборки протектора, поэтому он определяется путём наблюдения за поведением сохранения/восстановления виртуальной машины, а не предполагается — probe.ctx_map сообщает отображение, измеренное на операциях сохранения, и каждый сгенерированный листинг заключает обращение к контексту в скобки с указанием смещения его слота.