Skip to content
KitploitKITPLOIT
ИнструментыЭксплойтыБлог
Log in
Отправить
ИнструментыЭксплойтыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
VMProtect-devirtualization — Играемся с защитой ПО VMProtect. Автоматическая деобфускация чистых функций с помощью символьного выполнения и LLVM. | Kitploit
Инструменты/GitHubGitHub/jonathansalwan/vmprotect-devirtualization
Статический анализДинамический анализ (песочница)Обратная инженерияФаззингАнализ Бинарных Файлов
GitHubjonathansalwan/vmprotect-devirtualization

VMProtect-devirtualization

Играемся с защитой ПО VMProtect. Автоматическая деобфускация чистых функций с помощью символьного выполнения и LLVM.

Репозиторий
1.5k208164 лет назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Виртуализация VMProtect

Экспериментальный динамический подход к девиртуализации чистых функций, защищённых VMProtect 3.x

 

 

  • TL;DR
  • Введение
  • Подход
    • Пример 1: Простая битовая операция под защитой
    • Пример 2: MBA-операция под защитой
    • Пример 3: Более одного базового блока
  • Заключение и ограничения
  • Ссылки

 

 

TL;DR

Делюсь заметками о динамическом подходе к девиртуализации чистых функций, защищённых VMProtect. Этот подход показал очень хорошие результаты, если виртуализированная функция содержит только один базовый блок (независимо от его размера). Это распространённый сценарий, когда бинарные файлы защищают арифметические операции. Однако этот подход несколько более экспериментален, когда целевая функция содержит более одного базового блока. Тем не менее нам удалось девиртуализировать и восстановить двоичный код из образцов, содержащих 2 базовых блока, что позволяет предположить, что небольшие функции можно полностью девиртуализировать динамически.

Введение

VMProtect — это средство защиты ПО, которое защищает код, прогоняя его через виртуальную машину с нестандартной архитектурой. Эта защита — отличная площадка для любителей asm [0, 1, 2, 3, 4, 5, 6, 11]. Кроме того, уже существует множество инструментов, атакующих эту защиту [7, 8, 9, 12, 13]. В 2016 году мы обратили внимание на решение для защиты ПО Tigress и смогли обойти его виртуализацию с помощью символьного выполнения и LLVM. Этот подход был представлен на DIMVA 2018 [10], и я хотел проверить его на VMProtect. Обратите внимание, что не существует волшебного решения, работающего с любыми бинарными файлами; всегда есть компромиссы в зависимости от цели и ваших задач. Этот скромный вклад призван продемонстрировать пример динамической атаки на чистые функции, виртуализированные VMProtect. Главное преимущество динамической атаки в том, что она по своей природе обходит некоторые статические защиты VMProtect, такие как самоизменяющийся код, шифрование ключей и операндов и т. д.

Под чистой функцией мы понимаем функцию с конечным числом путей, не имеющую побочных эффектов. Входов может быть несколько, но выход всегда один. Ниже приведён пример чистой функции:```cpp int secret(int x, int y) { int r = x ^ y; return r; }

# Подход

Мы опираемся на ключевую интуицию, что обфусцированная трасса T' (из обфусцированного кода P') объединяет исходные инструкции из исходного кода P (трассу T, соответствующую T' в исходном коде) и инструкции виртуальной машины VM, так что T' = T + VM(T). Если нам удастся различить эти две подпоследовательности инструкций, T и VM(T), мы сможем восстановить один путь исходной программы P из трассы T'. Повторяя эту операцию для покрытия всех путей виртуализированной программы, мы сможем восстановить исходную программу P. В нашем практическом примере исходный код имеет конечное число исполняемых путей, что характерно для многих ситуаций, связанных с защитой интеллектуальной собственности. Для этого мы выполняем следующие шаги:

1. Определить виртуализированную функцию и её аргументы
2. Сгенерировать трассу VMProtect целевой программы
3. Воспроизвести трассу VMP и построить символьные выражения для получения зависимости между входными данными и выходным значением
4. Применить оптимизации к символьным выражениям, чтобы по возможности избежать инструкций VM
5. Поднять наше символьное представление до LLVM-IR, чтобы создать новую незащищённую версию целевой программы

## Пример 1: Простая побитовая операция

В качестве первого примера возьмём следующую функцию: она принимает два входных значения и возвращает `x ^ y`, которая защищена VMProtect.```cpp
int secret(int x, int y) {
  VMProtectBegin("secret");
  int r = x ^ y;
  VMProtectEnd();
  return r;
}

Мы начинаем с определения того, где функции используют VMProtect и сколько у них аргументов. Для нашего примера у нас может быть что-то вроде следующего:

Уже просто читая код, мы знаем, что функция начинается по адресу 0x4011c0, имеет два 32-битных аргумента (edi и esi) и возвращается по адресу 0x4011ef. Это всё, что нам нужно от реверс-инжиниринга. Дальнейшие шаги будут автоматическими. Теперь мы должны сгенерировать трассировку выполнения этой виртуализированной функции. Для этого мы используем Pintool. Ему нужны только начальный (start) и конечный (end) адреса (для нашего примера, 0x4011c0 и 0x4011ef), которые задают диапазон инструментирования. Обратите внимание, что для этой задачи подойдёт любой DBI или эмулятор.``` $ ./pin/pin -t ./pin/source/tools/VMP_Trace/obj-intel64/VMP_Trace.so -start 4198848 -end 4198895 -- ./vmp_binaries/binaries/sample2.vmp.bin 1 2 &> ./vmp_traces/sample2.vmp.trace

Вы можете увидеть результат [здесь](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/main/vmp_traces/sample2.vmp.trace). Формат трассировки использует три вида операций: `mr`, `r` и `i`.
`mr` — это чтение из памяти, выполненное инструкцией `i`, а `r` — регистры CPU. Например:```
mr:0x7ffda459d718:8:0x227db4f8
r:0x40200a:0x0:0x7ffda459f571:0x2:0x40200a:0x0:0x0:0x7ffda459d688:0x0:0x0:0x7feee9b80ac0:0x7feee9b8000f:0xad1c3e:0x0:0x0:0x0
i:0x89173e:8:488BB42490000000

У нас есть чтение памяти, которое загружает 8-байтовую константу 0x227db4f8 из адреса 0x7ffda459d718. Инструкция выполняется по адресу 0x89173e, и её 8-байтовый опкод — 488BB42490000000, что является mov rsi, qword ptr [rsp + 0x90]. Состояние регистров перед выполнением следующее:```python (1) RAX = 0x40200a (9) R8 = 0 (2) RBX = 0 (10) R9 = 0 (3) RCX = 0x7ffda459f571 (11) R10 = 0x7feee9b80ac0 (4) RDX = 0x2 (12) R11 = 0x7feee9b8000f (5) RDI = 0x40200a (13) R12 = 0xad1c3e (6) RSI = 0 (14) R13 = 0 (7) RBP = 0 (15) R14 = 0 (8) RSP = 0x7ffda459d688 (16) R15 = 0

После того как трассировка VMP была создана, мы воспроизводим её с помощью скрипта [attack_vmp.py](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/main/attack_vmp.py). Этот скрипт использует
[Triton](https://github.com/jonathansalwan/Triton) для построения предиката пути трассировки. Обратите внимание, что все выражения, которые
включают символьные переменные (входные данные функции), остаются символьными, тогда как все выражения, не связанные с входными данными, конкретизируются. Другими
словами, наши символьные выражения не содержат ни одной операции, относящейся к виртуальной машине (сам механизм
не зависит от пользователя), а только операции, относящиеся к исходной программе.

Например, ниже показан пример конкретизации. Слева у нас есть AST, содержащий подвыражения, которые не включают
символьную переменную (`1 + 2` и `6 ^ 3`). Поэтому эти ветви конкретизируются и заменяются константами `3` и `5`, что приводит к
AST справа. **Именно так мы девиртуализируем код.**

<p align="center">
  <img src="https://assets.kitploit.com/production/public/readmes/8096/857ed50cfe9cb2347f816ece1d8dc4c13174c971dcb65ebc5621be14269a5ca8.png">
</p>
Скачать инструмент