Vmprotect защита windows приложений часть 2

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

Вторая статья небольшой серии о защите, которая используется для сокрытия алгоритма приложения. В прошлой статье мы разобрали основные части защиты и собрали тестовое приложение. Здесь познакомимся со структурой обработчиков команд и попробуем поотлаживать и раскодировать исполняемый файл.

Описание работы VMProtect

Рассматриваемая защита имеет ряд функций, которые здорово портят жизнь реверс-инженеру. Например:

  1. Создание полиморфных обработчиков для одной и той же программы;

  2. Однонаправленное кодирование команд процессора;

Чтобы продемонстрировать эти проблемы, запакуем еще раз файл, который использовали для первой части статьи.

Дизассемблированный граф выглядит так:

В этот раз нам повезло, процессинг файла прошел успешно и все обработчики команд виртуальной машины VMP были обработаны корректно для построения графа. В первой части статьи исполняемый граф был разбит на отдельные команды, которые не могли быть использованы для графического представления.

Чтобы проанализировать приложение, нужно понимать, как строится код VMP и как он обрабатывает различные команды. Приведем в качестве шаблона следующий псевдокод.

pusha ; сохранить все регистры
push 0 ; установка начального значения
mov esi, [esp+x+var] ; esi = указатель на VM байт код, адрес может меняться за счет x и var
mov ebp, esp ; так как VMProtect использует стековую виртуальную машину, то ebp = VM "stack" указатель
sub esp, 0C0h
mov edi, esp ; edi = область памяти, где находятся регистры общего назначения

Сместить обработчик:
add esi, [ebp+0]

Выбрать следующую команду:
mov al, [esi]; читаем байт байткода в EIP виртуальной машины
movzx eax, al
sub esi, -1; смещаем значение EIP виртуальной машины
jmp ds:VMHandlers[eax*4] ; выполняем обработчик команды

Теперь тоже самое, но на графе:

Финальная часть, которая решает, какой будет следующая команда и каким обработчиком эту команду выполнять:

Теперь, когда мы разобрались с основным конвейером, можно начинать отладку.

Отладка VM

Для отладки будем использовать x64dbg, на сегодняшний день это самый активно развивающийся отладчик для ОС Windows (помимо WinDBG). Вообще можно пользоваться любым отладчиком, лишь бы вы могли удобно видеть регистры и память, с которой работает приложение. Загрузим приложение в отладчик и встанем на первую команду протектора:

Выделим основные точки для наблюдения, чтобы в дальнейшем можно было восстановить оригинальный алгоритм приложения. Если опустить все подробности работы виртуальной машины, то нам необходимо сейчас определить, где находится в коде так называемый main_loop. Его достаточно просто найти, если несколько раз протрассировать приложение:

На рисунке пунктирной красной рамкой выделен main loop. Это основной цикл, который работает с байткодом, расположенным по адресу из регистра ESI. EIP для виртуальной машины является AX регистр. В него помещаются идентификаторы обработчиков. В итоге, чтобы получить развернутый листинг приложения, нам нужно собрать номера вызываемых обработчиков. Сделать это можно либо используя приложение, которое будет самостоятельно парсить сегмент упакованного исполняемого файла, либо нужно установить условный breakpoint, который будет регистрировать заданные данные. Будем использовать второй метод и запишем в лог отладчика все номера вызванных хэндлеров.

Для начала определим точку, где индекс хэндлера можно получить уже после вычислений:

Теперь откроем меню «breakpoints» и установим вот такие значения:

Настройка достаточно проста, поле «Log Text» используется для занесения данных в лог отладчика. Формат строки, которая заполняется из регистров или локальных переменных отладчика выглядит так: {формат данных:объект откуда взять данные}. В нашем случае мы просматриваем регистр eax и локальную переменную $breakpointcounter.

Поле Command Text позволяет выполнять операции автоматически по достижению адреса точки останова. В нашем случае мы ничего не делаем, поэтому команда просто продолжает выполнение приложения.

Посмотрим на результат:

Итак, у нас есть индексы обработчиков и их количество — 669. Согласитесь, разбирать такое количество обработчиков достаточно трудоемкий процесс, однако уникальных индексов может быть гораздо меньше. Попробуем это выяснить. Для фильтрации будем использовать notepad++ и его функцию замены текста:

Для замены использовалась следующая регулярка: ^(.*?)$s+?^(?=.*^1$) Итого у нас 53 уникальных индекса для хэндлеров. Уже лучше, перейдем к следующему этапу.

Сбор команд алгоритма

К сожалению, на этом этапе придется попрощаться на время с отладчиком и заняться программированием. Основная наша задача будет получение общего листинга всех обработчиков приложения, которые вызываются последовательно из main loop. Зачем это нужно? Чтобы собрать алгоритм воедино и попробовать его оптимизировать для разбора.

Скрипт, который нам поможет собрать обработчики в единую последовательность команд:

import pefile

# загружаем файл
pe = pefile.PE(filePath)
# помещаем в память
image = pe.get_memory_mapped_image()
# смещение до таблицы хэндлеров
baseOffset = 0xB400

# Всего 255 хэндлеров в исполняемом файла
handlers = []
for i in range(255):
    offset+=4
    handlers.append(image[offset])

# собираем байткод хэндлеров
for h in handlers:
    md = Cs(CS_ARCH_X86, CS_MODE_32)
    for i in md.disasm(h, 0x1000):
        print("0x%x:t%st%s" %(i.address, i.mnemonic, i.op_str))

Фрагмент получаемого листинга:

Следующий этап — оптимизация кода и удаление заведомо избыточного кода. Выполняется это либо вручную, либо с привлечением рекомпиляции через промежуточный язык программирования. Для этих действий можно использовать вот этот проект. Предлагаем читателям попробовать сделать это самостоятельно.


Данная статья продолжает серию постов о продукте VMProtect Integration Kit (VMPKit).

В первой части были рассмотрены функции-обертки WinAPI, упрощающие работу с подсистемой UAC, функции VMPKit для управления хранением лицензий, сервисные функции для API VMProtect, упрощающие работу с ним, и некоторые другие вспомогательные функции.

В данной части будет рассмотрена подсистема протоколирования VMPKit, реализованная для упрощения отладки процесса активации приложений.

Подсистема VMPKit GUI предоставляет несколько функций, упрощающих использование Shell-диалогов WinAPI для выбора файла для сохранения, выбора файла для записи и выбора каталога.

Также в данной части будет описана часть API VMPKit, которая реализует графический интерфейс активации приложения, ввод серийного номера, и отображает гибко настраиваемый диалог «О Программе»

Содержание
  • Протоколирование
    • Флаги, используемые при открытии лога
    • Инициализация и закрытие
    • Запись сообщений
    • Пример лог-файла, содержащий сведения о системе
  • Обертки для shell-функций WinAPI
    • Флаги shell-функций
      • Флаги функций VMPIK_OpenFileDialog/VMPIK_SaveFileDialog
      • Флаги функции VMPIK_BrowseForFolder
      • Флаги, общие для всех shell-функций
    • Shell-Функции VMPKit
  • Подсистема GUI
    • Скриншоты диалогов
    • Флаги диалога активации
    • Коды возврата диалоговых функций активации
    • Функция VMPIK_ParseSerialNumberFormat
    • Функция VMPIK_InputSerialNumberDlg
    • Функция VMPIK_ActivateAppDlg
    • Функция VMPIK_ActivateAppDlgEx
    • Диалог «О Программе»
      • Флаги диалога «О Программе»
      • Функция диалога «О Программе»
      • Функция-хелпер для масштабного коэффициента

Отмазка aka disclaimer

VMPKit является молодым продуктом, и нельзя говорить, что он доведен до совершенства. Если вы обнаружите в нем недоработки или ошибки, пожалуйста, не спешите оставлять гневные комментарии, попробуйте с начала связаться с нами для устранения проблемы.

О данном руководстве

Данная статья является в настоящий момент наиболее полным руководством/справочником по VMPKit. Документация на официальном сайте vmpkit.com в настоящее время отстает по полноте и детализации от данной статьи.

Протоколирование

Подсистема протоколирования VMPKit спроектированна прежде всего для протоколирования действий пользователя и реакции VMPKit, происходящих в процессе активирования приложения, но может быть использована и в других частях программного обеспечения, использующего VMPKit. Подсистема протоколирования является однопоточной и в многопоточном окружении ею следует пользоваться с осторожностью. Подсистема протоколирования спроектированна с учетом возможности протоколировать процесс активации при активации приложения из инсталляционной setup-программы.

Флаги, используемые при открытии лога

  • VMPIK_LOG_DEFAULT — значение флагов по умолчанию.
  • VMPIK_LOG_OVERWRITE — указывает, что при открытии лог-файла его содержимое следует перезаписать. Используется при вызове функции VMPIK_LogCreate. Если не указаны флаги VMPIK_LOG_OVERWRITE и VMPIK_LOG_APPEND и файл с заданным именем существует, открытие лог-файла завершится ошибкой.
  • VMPIK_LOG_APPEND — указывает, что при открытии лог-файла новое содержимое будет добавляться в конец существующего файла. Используется при вызове функции VMPIK_LogCreate. Если не указаны флаги VMPIK_LOG_OVERWRITE и VMPIK_LOG_APPEND и файл с заданным именем существует, открытие лог-файла завершится ошибкой.
  • VMPIK_LOG_APPPATH — данный флаг указывает использовать путь к исполняемому файлу приложения, если путь не задан аргументом функции VMPIK_LogCreateTemporary.
  • VMPIK_LOG_EXTRACTPATH — данный флаг указывает, что путь, заданный аргументом функции VMPIK_LogCreateTemporary на самом деле является именем файла, и путь к лог-файлу необходимо извлечь из указанного полного имени файла. Данную возможность удобно использовать, когда необходимо создать лог-файл в том же каталоге, что и какой-либо другой файл с известным именем.

Инициализация и закрытие

HANDLE VMPIKAPI VMPIK_LogRelease( );

Функция VMPIK_LogRelease возвращает хэндл лог-файла и очищает внутренние структуры, связанные с протоколированием, не закрывая лог-файл.

BOOL VMPIKAPI VMPIK_LogClose( );

Функция VMPIK_LogClose закрывает лог-файл и очищает внутренние структуры, связанные с протоколированием.

BOOL VMPIKAPI VMPIK_LogSetHandle( HANDLE hLogFile );

Функция VMPIK_LogSetHandle устанавливает хэндл лог-файла и инициализирует внутренние структуры, связанные с протоколированием.

BOOL VMPIKAPI VMPIK_LogCreate( DWORD dwFlags, LPCTWSTR lpszLogFile );

Функция VMPIK_LogCreate создает лог-файл с именем lpszLogFile в соответствии со значениями флагов dwFlags.

BOOL VMPIKAPI VMPIK_LogCreateTemporary( DWORD dwFlags, LPCTSTR lpszLogFilePath, LPCTSTR lpszLogFileNamePrefix, LPCTSTR lpszLogFileNameExt, UINT n, LPTSTR filenameBuf, UINT fnBufSize );

Функция VMPIK_LogCreateTemporary создает временный файл с именем вида «$PATH$PREFIX$NNNN.$EXT», где $NNNN — параметр n, оформатированный как 4х-разрядное шестнадцатеричное число. Если такой файл существует, то значение n будет увеличиваться до тех пор, пока не будет создан новый файл или пока число попыток не превысит 65536.

Если параметр lpszLogFilePath задан как 0 (или указывает на пустую строку), и не заданны флаги VMPIK_LOG_APPPATH и VMPIK_LOG_EXTRACTPATH, то будет использоваться путь для временных файлов, который возвращает функция WinAPI GetTempPath.

Функция VMPIK_LogCreateTemporary может возвращать имя созданного лог-файла, если задан параметр filenameBuf.

Запись сообщений

BOOL VMPIKAPI VMPIK_LogWriteString( LPCSTR str );

Функция VMPIK_LogWriteString производит запись строки в лог-файл. Каждый символ перевода строки ‘n’ преобразуется в последовательность «rn». После каждого перевода строки в начало новой строки добавляется текущее время. Рекомендуется производить запись строк в виде «nSome message to log» вместо «Some message to logn»; в лог файле строка будет выглядет так — «[2014/03/09 16:18:24] Some message to log»

Unicode версия функции VMPIK_LogWriteString предварительно перекодирует строку в Ansi кодировку.

BOOL VMPIKAPIV VMPIK_LogPrint( LPCTSTR  fmt, ... );

Функция VMPIK_LogPrint осуществляет форматный вывод в лог-файл.

void VMPIKAPI VMPIK_LogWriteSysInfo();

Функция VMPIK_LogWriteSysInfo записывает в лог-файл основные сведения о системе и о приложении.

Пример вывода VMPIK_LogWriteSysInfo

[2014/03/10 21:00:55] Application version: 1.2.3.4
[2014/03/10 21:00:55] Windows version: 6.1/x64, Service Pack 1
[2014/03/10 21:00:55] CPU: 4 X x64
[2014/03/10 21:00:55] Memory: 8155 Mb, used: 55%
[2014/03/10 21:00:55] Is User Administrator: Yes
[2014/03/10 21:00:55] Is Run as Administrator: No
[2014/03/10 21:00:55] Is Process Elevated: No
[2014/03/10 21:00:55] Process Integrity Level: MEDIUM
[2014/03/10 21:00:55] Default Virtualization Status: Enabled

Обертки для shell-функций WinAPI

Если использование функции GetOpenFileName для отображения диалогов сохранения и открытия файлов не слишком сложно, то уже использование SHBrowseForFolder представляет некоторую трудность, с учетом ее различного поведения даже под Windows 2000 и Windows XP.

В современных ОС Windows (Windows Vista+) в Microsoft отошли от использования этих функций, и для отображения аналогичных диалогов предлагают использовать COM-интерфейсы IFileOpenDialog и IFileSaveDialog, при этом отображаемые диалоги довольно сильно отличаются внешним видом от диалогов из предыдущих версий Windows.

Современные GUI-библиотеки в основном предоставляют упрощающие жизнь обертки над shell-функциями, но в каждом случае надо уточнять вопрос с совместимостью с различными версиями Windows. Также возможна ситуация, когда разработка ведется в старых системах разработки с использованием устаревших SDK (например, Delphi 5-7), или необходимо минимизировать использование GUI-библиотек, но хочется, хотя бы частично, придать приложению современный вид, и/или автоматически выбирать вид отображаемых диалогов в зависимости от версии Windows. Последнее требование, так же как и необходимость минимизации использования GUI-библиотек, как раз и имело место быть при создании GUI VMPKit, и было решено вынести эти функции в публичный интерфейс VMPKit.

Еще одним аргументом за использование shell-функций VMPKit является то, что COM-интерфейсы IFileOpenDialog и IFileSaveDialog не имеют версий, работающих с Ansi-строками, и добавление диалогов в стиле Vista+ в старое ANSI-приложение дополнительно затруднено.

Еще одной не слишком важной, но полезной функцией является функция отображения MessageBox’а без звукового сигнала — в случае, если при показе MessageBox’а используется флаг, указывающий использовать какую-либо иконку, стандартная WinAPI функция MessageBox выдает звуковой сигнал. Иногда хочется этого избежать и отключить вывод звука совсем или сделать его вывод опционально.

Флаги shell-функций

Флаги функций VMPIK_OpenFileDialog/VMPIK_SaveFileDialog

  • VMPIK_OSFD_HIDEREADONLY — скрывает чекбокс «только для чтения». Соответствует флагу WinAPI OFN_HIDEREADONLY. Используется по умолчанию, в отличие от OFN_HIDEREADONLY. Не используется в Vista+.
  • VMPIK_OSFD_NOHIDEREADONLY — отображает чекбокс «только для чтения». Отменяет действие флага VMPIK_OSFD_HIDEREADONLY. Не используется в Vista+.
  • VMPIK_OSFD_OVERWRITEPROMPT — выводит запрос на перезапись файла, если он уже существует. Соответствует флагу WinAPI OFN_FILEMUSTEXIST.
  • VMPIK_OSFD_FILEMUSTEXIST — файл с указанным именем должен существовать. Соответствует флагу WinAPI OFN_FILEMUSTEXIST.
  • VMPIK_OSFD_ALLOWMULTISELECT — позволяет выбирать несколько файлов одновременно. Соответствует флагу WinAPI OFN_ALLOWMULTISELECT.
  • VMPIK_OSFD_NOCHANGEDIR — запрещает смену каталога. Соответствует флагу WinAPI OFN_NOCHANGEDIR. Не действует для VMPIK_OpenFileDialog.
  • VMPIK_OSFD_PATHMUSTEXIST — указывает, что пользователь должен вводить только имена существующих файлов и каталогов. Соответствует флагу WinAPI OFN_PATHMUSTEXIST.

Флаги функции VMPIK_BrowseForFolder

  • VMPIK_OSFD_NONEWDIALOGSTYLE — соответствует флагу WinAPI BIF_NEWDIALOGSTYLE. Не используется в Vista+.
  • VMPIK_OSFD_NONEWFOLDERBUTTON — соответствует флагу WinAPI BIF_NONEWFOLDERBUTTON. Не используется в Vista+.
  • VMPIK_OSFD_INCLUDEFILES — соответствует флагу WinAPI BIF_BROWSEINCLUDEFILES. Не используется в Vista+.

Флаги, общие для всех shell-функций

  • VMPIK_OSFD_FORCESHOWHIDDEN — задает опцию показа скрытых файлов и папок вне зависимости от настроек системы. Соответствует флагу WinAPI OFN_FORCESHOWHIDDEN.
  • VMPIK_OSFD_ADDTORECENT — указывает, что выбранный файл/папку следует добавить в список часто используемых.
  • VMPIK_OSFD_CLASSICDIALOG — принудительно отображает диалоги в классическом стиле (W2K/WinXP).

Shell-Функции VMPKit

INT  VMPIKAPI VMPIK_SilentMessageBoxEx( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType, BOOL bSilent );
INT  VMPIKAPI VMPIK_SilentMessageBox  ( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType );

Вышеприведенные функции полностью аналогичны функции WinAPI MessageBox, но в одной версии вывод звука является опциональным, в другой — отключен совсем.

BOOL VMPIKAPI VMPIK_OpenFileDialog( HWND hWndParent, DWORD osfdFlags, LPCTSTR lpszDialogTitle, LPCTSTR lpszDefExt, LPCTSTR lpszFilter, LPCTSTR lpszInitialDir, LPTSTR lpszFilenameBuf, DWORD dwBufSize, /*out*/ DWORD *pFilterIndex );

Функция VMPIK_OpenFileDialog отображает диалог открытия файла. Можно задать заголовок и каталог, который будет выбран изначально.

BOOL VMPIKAPI VMPIK_SaveFileDialog( HWND hWndParent, DWORD osfdFlags, LPCTSTR lpszDialogTitle, LPCTSTR lpszDefExt, LPCTSTR lpszFilter, LPCTSTR lpszInitialDir, LPTSTR lpszFilenameBuf, DWORD dwBufSize, /*out*/ DWORD *pFilterIndex );

Функция VMPIK_SaveFileDialog отображает диалог сохранения файла. Можно задать заголовок и каталог, который будет выбран изначально.

BOOL VMPIKAPI VMPIK_BrowseForFolder(HWND hWndParent, DWORD osfdFlags, LPCTSTR lpszDialogTitle, LPCTSTR lpszInitialDir, LPTSTR lpszFilenameBuf, DWORD dwBufSize, LPTSTR lpszDisplayBuf, DWORD dwDisplaySize );

Функция VMPIK_BrowseForFolder отображает диалог выбора папки. Можно задать заголовок и каталог, который будет выбран изначально. Помимо пути к выбранному каталогу, может возвращать его отображаемое название, если задан параметр lpszDisplayBuf. Так, для каталога «C:UserAll UsersDocuments» отображаемое название будет — «Общие документы».

Подсистема GUI

VMPKit содержит несколько функций для отображения диалогов: простой диалог ввода серийного номера; диалог активации, поддерживающий различные способы активации; диалог «О Программе», поддерживающий различные режимы отображения.

Скриншоты диалогов

Диалог ввода серийного номера

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог активации — активация при помощи серийного номера с возможностью offline-активации

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог активации — активация при помощи серийного номера и активация при помощи ключевого файла лицензии (1)

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог активации — активация при помощи серийного номера и активация при помощи ключевого файла лицензии (2)

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог активации — активация при помощи серийного номера и активация при помощи ключевого файла лицензии (3)

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог активации — активация при помощи серийного номера и активация при помощи ключевого файла лицензии (4)

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог активации — активация при помощи серийного номера и активация при помощи ключевого файла лицензии (5)

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог активации — все варианты выбора

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог ‘О Программе’ (1)

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог ‘О Программе’ (2)

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог ‘О Программе’ (3)

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог ‘О Программе’ (4

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог ‘О Программе’ (5)

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Диалог ‘О Программе’ (6)

VMPKit — интеграция протектора VMProtect в Windows приложение. Часть вторая. Протоколирование и GUI

Флаги диалога активации

Диалог активации является очень гибко настраиваемым. Для настройки диалога активации используются одновременно два способа: 1) задание соответствующих значений аргументов при вызове диалоговых функций, и задание флагов диалога активации. В некоторых случаях эти способы могу дополнять друг друга.

Развернуть список флагов

  • VMPIK_AADF_ACTIVATE_SERIAL — включает возможность активации при помощи серийного номера (кода активации). При отображении диалога активации одна из двух опций: VMPIK_AADF_ACTIVATE_SERIAL или VMPIK_AADF_ACTIVATE_KEY_FILE должна быть включена.
  • VMPIK_AADF_ACTIVATE_KEY_FILE — включает возможность активации при помощи ключевого файла лицензии. При отображении диалога активации одна из двух опций: VMPIK_AADF_ACTIVATE_SERIAL или VMPIK_AADF_ACTIVATE_KEY_FILE должна быть включена.
  • VMPIK_AADF_ACTIVATE_TRIAL — включает возможность активации trial лицензии.
  • VMPIK_AADF_ACTIVATE_LATER — включает опцию «активировать позже».
  • VMPIK_AADF_REACTIVATE — включает режим реактивации (деактивация и последующая активация).
  • VMPIK_AADF_DEACTIVATE — включает режим деактивации. При успешной деактивации данные лицензии удаляются с компьютера пользователя.
  • VMPIK_AADF_ACTIVATE_MASK — маска режимов активации.
  • VMPIK_AADF_SELECT_ACTIVATE_MASK — маска изначально выбранного типа активации.
  • VMPIK_AADF_SIMPLE_SERIAL_INPUT — включает режим ввода серийного номера, без активации и каких-либо действий.
  • VMPIK_AADF_OFFLINE — добавляет опцию offline активации/деактивации.
  • VMPIK_AADF_ACTIVATION_TYPE — тип активации — для текущего пользователя/для всех пользователей.
  • VMPIK_AADF_SELECT_USER_GROUP — тоже, что и VMPIK_AADF_ACTIVATION_TYPE.
  • VMPIK_AADF_SELECT_ACTIVATE_SERIAL, VMPIK_AADF_SELECT_ACTIVATE_KEY_FILE, VMPIK_AADF_SELECT_ACTIVATE_TRIAL, VMPIK_AADF_SELECT_ACTIVATE_LATER — задает изначально выбранную опцию активации.
  • VMPIK_AADF_SELECT_ACTIVATION_TYPE_CURRENT_USER, VMPIK_AADF_SELECT_ACTIVATION_TYPE_ALL_USERS — задает изначально выбранный тип активации (для текущего пользователя/для всех пользователей).
  • VMPIK_AADF_DLG_RESIZE — включает возможность изменения размера диалога.
  • VMPIK_AADF_UPPERCASE — ограничивает ввод и приводит вводимые буквы серийного номера к верхнему регистру.
  • VMPIK_AADF_LOWERCASE — ограничивает ввод и приводит вводимые буквы серийного номера к нижнему регистру.
  • VMPIK_AADF_DEACTIVATE_OFFLINE_KEEP_LICENSE — offline при деактивации не удаляет данные лицензии с компьютера.
  • VMPIK_AADF_SERIAL_NUMBER_FORMAT_STR — задаем формат серийного номера в виде форматной строки.
  • VMPIK_AADF_SHOW_CONNECTION_REQUIRED — показывать строку «требуется подключение к интернету» для соответствующих опций.
  • VMPIK_AADF_CLASSIC_FILE_DIALOGS — указывает использовать классические (Win2K/WinXP) диалоги открытия/сохранения файлов в системах Vista+.
  • VMPIK_AADF_VERTICAL_BUTTONS — задает вертикальное расположение кнопок «Ok»/«Cancel»/«Elevate».
  • VMPIK_AADF_BUTTONS_AT_LEFT — задает расположение кнопок по левому краю.
  • VMPIK_AADF_BUTTONS_AT_RIGHT — задает расположение кнопок по правому краю.
  • VMPIK_AADF_SHOW_ELEVATION_BUTTON — указывает всегда показывать кнопку «Elevate».
  • VMPIK_AADF_ALWAYS_SILENT_MESSAGEBOX — при выводе сообщений MessageBox не будет использоваться звуковое сопровождение.
  • VMPIK_AADF_SHOW_OFFLINE_API_ERRORS — указывает показывать ошибки при вызове offline-API.
  • VMPIK_AADF_DONT_SHOW_ACTIVATION_SUCCESS — указывает не показывать сообщение об успешной активации.
  • VMPIK_AADF_DISABLE_LICENSE_DROP_TARGET — указывает не использовать возможности Drag’n’Drop для поля файла лицензии.
  • VMPIK_AADF_BOTTOM_RIGHT_POS — при задании точки, в которой будет отображаться диалог, использовать координаты правого нижнего угла вместо левого верхнего.

Коды возврата диалоговых функций активации

  • VMPIK_AADR_OK — активация прошла успешно, диалог закрыт по кнопке «Ok»/«Close».
  • VMPIK_AADR_SUCCESS — тоже, что и VMPIK_AADR_OK.
  • VMPIK_AADR_CANCELED — пользователь отменил активацию «Cancel»/Escape.
  • VMPIK_AADR_INVALID_PARAM — неверный параметр. В этом случае диалог не показывается пользователю.
  • VMPIK_AADR_ELEVATION_REQUIRED — возвращается, если пользователь нажал кнопку «Elevate».
  • VMPIK_AADR_SAVE_FAILED — активация прошла успешно, но возникла ошибка при сохранении данных лицензии. Данная ситуация теоретически не должна возникать, так как перед активацией возможность записи данных в нужное месторасположение проверяется.

Функция VMPIK_ParseSerialNumberFormat

Вспомогательная функция разбора форматной строки серийного номера. Используется при задании формата серийного номера строкой вместо массива целых чисел. Если часть формата серийного номера начинается с символа ‘#’, то длина этой части является её размером, вне зависимости от остальных символов. Если формата серийного номера состоит из цифр, то они задают длину данной части, но не больше 32. Примеры эквивалентных форматных строк: «8-####-##-6», «8-4-2-6», «8-4-#4-6» («#4» — 2, считается длина части), «########-####-##-######».

UINT VMPIKAPI VMPIK_ParseSerialNumberFormat( LPCTSTR strFormat, TCHAR sepChar, UINT *pSerialNumberNumChars, UINT *pNumPartsFound);

Функция VMPIK_InputSerialNumberDlg

INT VMPIKAPI VMPIK_InputSerialNumberDlg( HWND     hWndParent
                                    , LPCTSTR  lpcDlgTitle
                                    , HICON    hIcon
                                    , HICON    hIconSmall
                                    , POINT    *pLeftTop
                                    , UINT     serialNumberNumParts
                                    , UINT     serialNumberNumChars
                                    , const UINT *pSerialNumberNumChars
                                    , CHAR     serialNumberSeparator
                                    , LPCTSTR  lpcSerialNumberIncludeCharsMask
                                    , LPCTSTR  lpcSerialNumberExcludeCharsMask
                                    , UINT     fAadFlags
                                    , LPSTR    lpEnteredSerialBuf
                                    , UINT    *pEnteredSerialBufSize
                                    );

Функция VMPIK_InputSerialNumberDlg выводит диалог ввода серийного номера. Поддерживается копирование серийного номера из буфера обмена как целиком во все поля (если серийных номер скопирован целиком), так и в отдельное поле.

Параметры:

  • hWndParent — хэндл родительского окна.
  • lpcDlgTitle — заголовок диалога. Можно задавать 0.
  • hIcon — маленькая иконка. При использовании функции LoadImage следует задавать размер GetSystemMetrics(SM_CXICON). Можно задавать 0.
  • hIconSmall — большая иконка. При использовании функции LoadImage следует задавать размер GetSystemMetrics(SM_CXSMICON). Можно задавать 0.
  • pLeftTop — задает положение верхнего левого угла диалога. Можно задавать 0, в этом случае диалог будет спозиционирован по центру экрана/родительского окна.
  • serialNumberNumParts — количество частей в серийном номере, не более 16.
  • serialNumberNumChars — количество символов в каждой части серийного номера.
  • pSerialNumberNumChars — массив целых чисел, задающих длины отдельных частей серийного номера, при этом параметр serialNumberNumChars игнорируется. Можно задать указатель на строку формата, при этом параметр serialNumberNumParts также будет проигнорирован.
  • serialNumberSeparator — разделитель частей серийного номера. Задание 0 указывает использовать стандартный разделитель — ‘-‘.
  • lpcSerialNumberIncludeCharsMask — строка, задающая фильтр допустимых символов.
  • lpcSerialNumberExcludeCharsMask — строка, задающая фильтр исключаемых символов.
  • fAadFlags — флаги, см Флаги диалога активации.
  • lpEnteredSerialBuf — буфер, в который будет помещен введенный серийный номер.
  • pEnteredSerialBufSize — размер буфера для серийного номера.

Функция VMPIK_ActivateAppDlg

Определение

INT VMPIKAPI VMPIK_ActivateAppDlg  ( HWND     hWndParent
                                    , LPCTSTR  lpcDlgTitle
                                    , HICON    hIcon
                                    , HICON    hIconSmall
                                    , POINT    *pLeftTop
                                    , DWORD    activationSaveLoadFlags
                                    , HKEY     hRootKeyCurrentUser
                                    , LPCTSTR  lpcRegOrFilePathCurrentUser
                                    , HKEY     hRootKeyAllUsers
                                    , LPCTSTR  lpcRegOrFilePathAllUsers
                                    , LPCTSTR  lpcRegValOrFileName
                                    , LPCTSTR  lpcSerialRegValOrFileName
                                    , LPCTSTR  lpcTrialFlagRegValOrFileName
                                    , UINT     serialNumberNumParts
                                    , UINT     serialNumberNumChars
                                    , const UINT *pSerialNumberNumChars
                                    , CHAR     serialNumberSeparator
                                    , UINT     fAadFlags
                                    , LPCTSTR  lpcOnlineActivationUrl
                                    , LPCTSTR  lpcOfflineActivationUrl
                                    , LPCSTR   lpcTrialLicenseSerial
                                    , LPVOID   lpvCallbackParam
                                    , PFN_VMPIK_ActivateOnlineCallback                 ActivateOnlineProc
                                    , PFN_VMPIK_DeactivateOnlineCallback               DeactivateOnlineProc
                                    , PFN_VMPIK_GetOfflineActivationStringCallback     GetOfflineActivationStringProc
                                    , PFN_VMPIK_GetOfflineDeactivationStringCallback   GetOfflineDeactivationStringProc
                                    , PFN_VMPIK_ActivateCheckLicenseCallback           ActivateCheckLicenseProc
                                    );

Функция VMPIK_ActivateAppDlg является упрощенной, по сравнению с VMPIK_ActivateAppDlgEx, версией функции вывода диалога активации.

Параметры:

  • hWndParent — хэндл родительского окна.
  • lpcDlgTitle — заголовок диалога. Можно задавать 0.
  • hIcon — маленькая иконка. При использовании функции LoadImage следует задавать размер GetSystemMetrics(SM_CXICON). Можно задавать 0.
  • hIconSmall — большая иконка. При использовании функции LoadImage следует задавать размер GetSystemMetrics(SM_CXSMICON). Можно задавать 0.
  • pLeftTop — задает положение верхнего левого угла диалога. Можно задавать 0, в этом случае диалог будет спозиционирован по центру экрана/родительского окна.
  • activationSaveLoadFlags — см. флаги сохранения данных лицензии.
  • hRootKeyCurrentUser — задает ветку реестра, куда будут записываться данные лицензии при активации для текущего пользователя. Следует задать 0 для сохранения в файл.
  • lpcRegOrFilePathCurrentUser — задает путь в реестре или файловой системе, по которому будут сохранены данные лицензии при активации для текущего пользователя.
  • hRootKeyAllUsers — задает ветку реестра, куда будут записываться данные лицензии при активации для всех пользователей. Следует задать 0 для сохранения в файл.
  • lpcRegOrFilePathAllUsers — задает путь в реестре или файловой системе, по которому будут сохранены данные лицензии при активации для всех пользователей. Может быть равен 0.
  • lpcRegValOrFileName — задает имя переменной в реестре или имя файла, в котором будут сохранены данные лицензии. Может быть равен 0.
  • lpcSerialRegValOrFileName — задает имя переменной в реестре или имя файла, в котором будет сохранен серийный номер (код активации), использованный при активации. Может быть равен 0.
  • lpcTrialFlagRegValOrFileName — задает имя переменной в реестре или имя файла, в котором будет сохранен Trial признак лицензии, если будет произведена активация пробной версии. Может быть равен 0.
  • serialNumberNumParts — количество частей в серийном номере, не более 16.
  • serialNumberNumChars — количество символов в каждой части серийного номера.
  • pSerialNumberNumChars — массив целых чисел, задающих длины отдельных частей серийного номера, при этом параметр serialNumberNumChars игнорируется. Можно задать указатель на строку формата, при этом параметр serialNumberNumParts также будет проигнорирован.
  • serialNumberSeparator — разделитель частей серийного номера. Задание 0 указывает использовать стандартный разделитель — ‘-‘.
  • fAadFlags — флаги, см Флаги диалога активации.
  • lpcOnlineActivationUrl — задает URL, который используется для online активации. Данный параметр игнорируется в версии для VMProtect (там URL «зашивается» при защите модуля).
  • lpcOfflineActivationUrl — задает URL, который используется для online активации. Данный параметр игнорируется в версии для VMProtect (там URL «зашивается» при защите модуля).
  • lpcTrialLicenseSerial — задает серийный номер (код активации), который будет использоваться для активации пробной версии.
  • lpvCallbackParam — параметр для функций обратного вызова. Может быть равен 0.
  • ActivateOnlineProc — указатель на функцию online активации. Может быть равен 0.
  • DeactivateOnlineProc — указатель на функцию online деактивации. Может быть равен 0.
  • GetOfflineActivationStringProc — указатель на функцию, возвращающую строку данных для offline активации. Может быть равен 0.
  • GetOfflineDeactivationStringProc — указатель на функцию, возвращающую строку данных для offline деактивации. Может быть равен 0.
  • ActivateCheckLicenseProc — указатель на функцию, осуществляющую проверку лицензии. Может быть равен 0.

Для того, чтобы активировать приложение только для текущего пользователя или только для всех пользователей, необходимо задать только параметры hRootKeyCurrentUser и lpcRegOrFilePathCurrentUser. Для того, чтобы включить возможность выбора типа активации, следует указать флаг VMPIK_AADF_SELECT_USER_GROUP (VMPIK_AADF_ACTIVATION_TYPE), а также задать параметры hRootKeyAllUsers и lpcRegOrFilePathAllUsers.

Функции обратного вызова можно не задавать, указав значение 0, при этом будут использоваться встроенные реализации, использующие API VMProtect. Данная возможность добавлена для поддержки сторонних процедур активации.

Адреса (URL) активации и деактивации игнорируются при использовании встроенных реализаций, использующих VMProtect — при защите приложения VMProtect «зашивает» эти адреса в защищаемый модуль.

Поддержка Drag’n’Drop. Функция VMPIK_ActivateAppDlg (также как и функция Функция VMPIK_ActivateAppDlgEx) поддерживает перетаскивание ключевого файла данных лицензии на диалог активации, если данный способ активации разрешен флагами активации. Перетаскивание работает для всего диалога, не только для поля ввода имени файла лицензии. После перетаскивания сразу происходит активация данной лицензии, и выводится сообщение об успехе операции или об ошибке.

Функция VMPIK_ActivateAppDlgEx

Определение

INT VMPIKAPI VMPIK_ActivateAppDlgEx( HWND     hWndParent
                                    , LPCTSTR  lpcDlgTitle
                                    , HICON    hIcon
                                    , HICON    hIconSmall
                                    , POINT    *pLeftTop
                                    , DWORD    activationSaveLoadFlags
                                    , HKEY     hRootKeyCurrentUser
                                    , LPCWSTR  lpcRegOrFilePathCurrentUser
                                    , HKEY     hRootKeyAllUsers
                                    , LPCTSTR  lpcRegOrFilePathAllUsers
                                    , LPCTSTR  lpcRegValOrFileName
                                    , LPCTSTR  lpcSerialRegValOrFileName
                                    , LPCTSTR  lpcTrialFlagRegValOrFileName
                                    , UINT     serialNumberNumParts
                                    , UINT     serialNumberNumChars
                                    , const UINT *pSerialNumberNumChars
                                    , CHAR     serialNumberSeparator
                                    , LPCTSTR  lpcSerialNumberIncludeCharsMask
                                    , LPCTSTR  lpcSerialNumberExcludeCharsMask
                                    , UINT     fAadFlags
                                    , LPCTSTR  lpcOnlineActivationUrl
                                    , LPCTSTR  lpcOfflineActivationUrl
                                    , LPCSTR   lpcTrialLicenseSerial
                                    , LPVOID   lpvCallbackParam
                                    , PFN_VMPIK_ActivateOnlineCallback                 ActivateOnlineProc
                                    , PFN_VMPIK_DeactivateOnlineCallback               DeactivateOnlineProc
                                    , PFN_VMPIK_GetOfflineActivationStringCallback     GetOfflineActivationStringProc
                                    , PFN_VMPIK_GetOfflineDeactivationStringCallback   GetOfflineDeactivationStringProc
                                    , PFN_VMPIK_ActivateCheckLicenseCallback           ActivateCheckLicenseProc
                                    , LPCTSTR  lpcActivationFailedCustomTitle
                                    , LPCTSTR  lpcActivationSuccessCustomTitle
                                    , LPCTSTR  lpcActivationSuccessCustomMessage
                                    , LPCTSTR  lpcDeactivationFailedCustomTitle
                                    , LPCTSTR  lpcDeactivationSuccessCustomTitle
                                    , LPCTSTR  lpcDeactivationSuccessCustomMessage
                                    , INT     *pLastActivationStatus
                                    , LPSTR    lpEnteredSerialBuf
                                    , UINT    *pEnteredSerialBufSize
                                    , LPSTR    lpLicenseBuf
                                    , UINT    *pLicenseBufSize
                                    );

Функция VMPIK_ActivateAppDlgEx является расширенной версией функции вывода диалога активации.

Параметры:

  • hWndParent — хэндл родительского окна.
  • lpcDlgTitle — заголовок диалога. Можно задавать 0.
  • hIcon — маленькая иконка. При использовании функции LoadImage следует задавать размер GetSystemMetrics(SM_CXICON). Можно задавать 0.
  • hIconSmall — большая иконка. При использовании функции LoadImage следует задавать размер GetSystemMetrics(SM_CXSMICON). Можно задавать 0.
  • pLeftTop — задает положение верхнего левого угла диалога. Можно задавать 0, в этом случае диалог будет спозиционирован по центру экрана/родительского окна.
  • activationSaveLoadFlags — см. флаги сохранения данных лицензии.
  • hRootKeyCurrentUser — задает ветку реестра, куда будут записываться данные лицензии при активации для текущего пользователя. Следует задать 0 для сохранения в файл.
  • lpcRegOrFilePathCurrentUser — задает путь в реестре или файловой системе, по которому будут сохранены данные лицензии при активации для текущего пользователя.
  • hRootKeyAllUsers — задает ветку реестра, куда будут записываться данные лицензии при активации для всех пользователей. Следует задать 0 для сохранения в файл.
  • lpcRegOrFilePathAllUsers — задает путь в реестре или файловой системе, по которому будут сохранены данные лицензии при активации для всех пользователей. Может быть равен 0.
  • lpcRegValOrFileName — задает имя переменной в реестре или имя файла, в котором будут сохранены данные лицензии. Может быть равен 0.
  • lpcSerialRegValOrFileName — задает имя переменной в реестре или имя файла, в котором будет сохранен серийный номер (код активации), использованный при активации. Может быть равен 0.
  • lpcTrialFlagRegValOrFileName — задает имя переменной в реестре или имя файла, в котором будет сохранен Trial признак лицензии, если будет произведена активация пробной версии. Может быть равен 0.
  • serialNumberNumParts — количество частей в серийном номере, не более 16.
  • serialNumberNumChars — количество символов в каждой части серийного номера.
  • pSerialNumberNumChars — массив целых чисел, задающих длины отдельных частей серийного номера, при этом параметр serialNumberNumChars игнорируется. Можно задать указатель на строку формата, при этом параметр serialNumberNumParts также будет проигнорирован.
  • serialNumberSeparator — разделитель частей серийного номера. Задание 0 указывает использовать стандартный разделитель — ‘-‘.
  • lpcSerialNumberIncludeCharsMask — строка, задающая фильтр допустимых символов.
  • lpcSerialNumberExcludeCharsMask — строка, задающая фильтр исключаемых символов.
  • fAadFlags — флаги, см Флаги диалога активации.
  • lpcOnlineActivationUrl — задает URL, который используется для online активации. Данный параметр игнорируется в версии для VMProtect (там URL «зашивается» при защите модуля).
  • lpcOfflineActivationUrl — задает URL, который используется для online активации. Данный параметр игнорируется в версии для VMProtect (там URL «зашивается» при защите модуля).
  • lpcTrialLicenseSerial — задает серийный номер (код активации), который будет использоваться для активации пробной версии.
  • lpvCallbackParam — параметр для функций обратного вызова. Может быть равен 0.
  • ActivateOnlineProc — указатель на функцию online активации. Может быть равен 0.
  • DeactivateOnlineProc — указатель на функцию online деактивации. Может быть равен 0.
  • GetOfflineActivationStringProc — указатель на функцию, возвращающую строку данных для offline активации. Может быть равен 0.
  • GetOfflineDeactivationStringProc — указатель на функцию, возвращающую строку данных для offline деактивации. Может быть равен 0.
  • ActivateCheckLicenseProc — указатель на функцию, осуществляющую проверку лицензии. Может быть равен 0.
  • lpcActivationFailedCustomTitle — заголовок сообщения об ошибке активации.
  • lpcActivationSuccessCustomTitle — заголовок сообщения об успешной активации.
  • lpcActivationSuccessCustomMessage — сообщение об успешной активации.
  • lpcDeactivationFailedCustomTitle — заголовок сообщения об ошибке деактивации.
  • lpcDeactivationSuccessCustomTitle — заголовок сообщения об успешной деактивации.
  • lpcDeactivationSuccessCustomMessage — сообщение об успешной деактивации.
  • pLastActivationStatus — статус последней осуществленной попытки активации.
  • lpEnteredSerialBuf — буфер, в который будет помещен введенный серийный номер.
  • pEnteredSerialBufSize — размер буфера для серийного номера.
  • lpLicenseBuf — буфер, в который будет помещены данные активированной лицензии.
  • pLicenseBufSize — размер буфера для данных лицензии.

Функция VMPIK_ActivateAppDlgEx предоставляет некоторые дополнительные возможности по сравнению с функцией VMPIK_ActivateAppDlg.

Функция VMPIK_ActivateAppDlgEx позволяет задавать маску допустимых для ввода в поле серийного номера символов (lpcSerialNumberIncludeCharsMask), а также маску недопустимых символов (lpcSerialNumberExcludeCharsMask).

Также функция VMPIK_ActivateAppDlgEx позволяет задать заголовки для сообщений об успешной/неуспешной активации/деактивации.

Дополнительно данная функция позволяет получить введенный серийный номер (код активации) и данные лицензии для дальнейшей обработки. Это может быть полезно, если попытка записи данных лицензии после успешной активации по каким-либо причинам завершилась ошибкой.

Диалог «О Программе»

Создание диалога «О Программе» иногда представляет некоторую сложность — некоторые новые продукты часто в первых версиях не имеют такого диалога вообще. Также, если диалог «О Программе» создан для одной программы, часто переделка его под потребности другой программы занимает некоторое время.

VMPKit пытается помочь в данном вопросе и предоставляет достаточно гибко настраиваемый диалог «О Программе», который может отображать различную информацию о программе, и автоматически адаптируется под выводимую информацию.

Диалог «О Программе», предоставляемый VMPKit, умеет копировать всю отображаемую информацию в буфер обмена (в текстовом виде) по нажатию клавиш Ctrl+C или Ctrl+Ins. Данная функция удобна при поддержке приложения — достаточно дать пользователю простые инструкции, и он без труда предоставит все необходимые сведения о программе в вашу службу поддержки.

Диалог «О Программе» конфигурируется заданием нужных значений в соответствующих аргументах функции диалога, а также использует для настройки флаги опций.

Флаги диалога «О Программе»

Развернуть

  • VMPIK_ABOUT_CLOSE_BUTTON — задает использовать кнопку «Close» вместо кнопки «Ok».
  • VMPIK_ABOUT_BUTTON_WITH_IMAGE — задает расположить кнопку «Ok»/«Close» непосредственно под изображением.
  • VMPIK_ABOUT_BUTTON_AT_LEFT — указывает разместить кнопку «Ok»/«Close» слева внизу (по умолчанию — справа внизу).
  • VMPIK_ABOUT_BUTTON_AT_CENTER — указывает разместить кнопку «Ok»/«Close» по-центру внизу (по умолчанию — справа внизу).
  • VMPIK_ABOUT_IMAGE_BITMAP — задает тип изображения HBITMAP (по умолчанию — HICON).
  • VMPIK_ABOUT_IMAGE_EMF — задает тип изображения Enhanced Metafile.
  • VMPIK_ABOUT_IMAGE_AT_LEFT — указывает разместить изображение слева от информационной части диалога (по умолчанию — справа).
  • VMPIK_ABOUT_PREPEND_COPYRIGHT_SIGN — указывает предварять при выводе строку копирайта символом копирайта.
  • VMPIK_ABOUT_COMPOSITE_COPYRIGHT_SIGN — указывает использовать символ копирайта как комбинацию — «©».
  • VMPIK_ABOUT_COPYRIGHT_APPEND_ALL_RIGHTS — указывает добавлять строку «All rights reserved» («Все права защищены») к строке копирайта.
  • VMPIK_ABOUT_SYS_INFO_WINDOWS_INFO — добавляет в раздел сведений о системе информацию о текущей версии Windows.
  • VMPIK_ABOUT_SYS_INFO_MEM_INFO — добавляет в раздел сведений о системе информацию об использовании памяти.
  • VMPIK_ABOUT_SYS_INFO_CPU_INFO — добавляет в раздел сведений о системе информацию о процессоре.
  • VMPIK_ABOUT_DISABLE_CLIPBOARD_COPY — запрещает копирование информации в буфер обмена по нажатию клавиш Ctrl+C или Ctrl+Ins.
  • VMPIK_ABOUT_ALWAYS_SHOW_REGISTERED — указывает всегда отображать раздел сведений о регистрации, даже если не указаны имя, email пользователя и срок окончания действия лицензии.
  • VMPIK_ABOUT_CLOSE_ON_URL_EVENT — указывает закрывать диалог при переходе на сайт по клику на соответствующей метке.
  • VMPIK_ABOUT_BOTTOM_RIGHT_POS — указывает использовать правый нижний угол для позиционирования диалога.
  • VMPIK_ABOUT_COPY_ADDITIONAL_INFO — указывает копировать в буфер обмена также и дополнительную информацию (если задана) по нажатию клавиш Ctrl+C или Ctrl+Ins.
  • VMPIK_ABOUT_PURCHASE_BTN_NONE — запрещает отображение кнопки «Купить».
  • VMPIK_ABOUT_PURCHASE_BTN_DEF_SIZE — задает размер кнопки по ширине «Купить» по умолчанию.
  • VMPIK_ABOUT_PURCHASE_BTN_HALF_SIZE — задает размер кнопки по ширине «Купить» в половину вычисленного при адаптации размера диалога.
  • VMPIK_ABOUT_PURCHASE_BTN_FULL_SIZE — задает размер кнопки по ширине «Купить» как размер, вычисленный при адаптации размера диалога.
  • VMPIK_ABOUT_CLOSE_ON_PURCHASE_EVENT — указывает закрывать диалог при нажатии кнопки «Купить».
  • VMPIK_ABOUT_VERSION_SHORT_FROM_APP_MODULE — указывает использовать отображаемую версию как короткую версию модуля приложения («1.0»).
  • VMPIK_ABOUT_VERSION_SHORTEX_FROM_APP_MODULE — указывает использовать отображаемую версию как расширенную короткую версию модуля приложения («1.2.3»).
  • VMPIK_ABOUT_VERSION_LONG_FROM_APP_MODULE — указывает использовать отображаемую версию как версию модуля приложения («1.2.3.4»).
  • VMPIK_ABOUT_VERSION_FILE_VERSION — использовать версию файла модуля приложения вместо версии продукта.

Функция диалога «О Программе»

INT VMPIKAPI VMPIK_AboutAppDlgW( HWND     hWndParent
                               , LPCWSTR  lpcDlgTitle
                               , const VMPIK_ABOUTDLG_FONTS_INFOW *lpcLabelsFonts
                               , UINT     horScale
                               , HICON    hIcon
                               , HICON    hIconSmall
                               , HANDLE   hAppImage
                               , POINT    *pLeftTop
                               , DWORD    aboutFlags
                               /*--------------------------*/
                               , LPCWSTR  lpcAppName
                               , LPCWSTR  lpcAppNameSubtitle
                               , LPCWSTR  lpcAppInfoString
                               , LPCWSTR  lpcAppVersionString
                               /*--------------------------*/
                               , LPCWSTR  lpcAppCopyrightString
                               , LPCWSTR  lpcAppUrlString
                               /*--------------------------*/
                               , BOOL     bTrial
                               , LPCWSTR  lpcRegisteredToNameString
                               , LPCWSTR  lpcRegisteredToEmailString
                               , LPCWSTR  lpcRegistrationExpiresDateString
                               , LPCWSTR  lpcPurchaseButtonText
                               , LPCWSTR  lpcPurchaseUrl
                               /*--------------------------*/
                               , LPCWSTR  lpcAdditionalInfoTextTitle
                               , LPCWSTR  lpcAdditionalInfoText
                               /*--------------------------*/
                               , PFN_VMPIK_AboutBoxImageClickCallback pfnImageClickCallback
                               , LPVOID                               lpvImageClickCallbackParam
                               );

Параметры:

  • hWndParent — хэндл родительского окна.
  • lpcDlgTitle — заголовок диалога. Можно задавать 0.
  • lpcLabelsFonts — указатель на структуру, содержащую информацию о шрифтах, используемых при отображении различных элементов диалога.
  • horScale — при выводе диалога его размеры автоматически адаптируются на основании заданных данных, но адаптация может быть не точной, и часть информации может быть «обрезана», или наоборот, может оставаться пустое пространство. Данный параметр позволяет уменьшить этот эффект, дополнительно масштабируя диалог по горизонтали. Этот коэффициент рекомендуется делать зависимым от локали, так как строки данных диалога для различных могут иметь разную длину. Данные коэффициент задается в процентах. Задание 0 (также как и заданий 100) оставляет ширину диалога без изменений.
  • hIcon — маленькая иконка. При использовании функции LoadImage следует задавать размер GetSystemMetrics(SM_CXICON). Можно задавать 0.
  • hIconSmall — большая иконка. При использовании функции LoadImage следует задавать размер GetSystemMetrics(SM_CXSMICON). Можно задавать 0.
  • hAppImage — изображение, помещаемое на диалог. Расположение и тип изображения задается флагами.
  • pLeftTop — задает положение верхнего левого угла диалога. Можно задавать 0, в этом случае диалог будет спозиционирован по центру экрана/родительского окна.
  • aboutFlags — см. флаги диалога «О Программе».
  • lpcAppName — название приложения, выводится увеличенным шрифтом в самом верху.
  • lpcAppNameSubtitle — подзаголовок, выводится под названием; может отсутствовать.
  • lpcAppInfoString — строка информации о приложении, выводится под подзаголовком; может отсутствовать.
  • lpcAppVersionString — строка версии приложения. Может отсутствовать, при этом может использоваться версия модуля приложения — управляется флагами.
  • lpcAppCopyrightString — строка копирайта, может отсутствовать.
  • lpcAppUrlString — задает домашний URL, подсвечивается цветом ссылки. При клике происходит переход по указанному адресу.
  • bTrial — секция диалога — сведения о регистрации. Задает признак, полная или пробная лицензия активна в данный момент.
  • lpcRegisteredToNameString — секция диалога — сведения о регистрации. Указывает, на кого зарегистрированна программа.
  • lpcRegisteredToEmailString — секция диалога — сведения о регистрации. Указывает, на кого зарегистрированна программа (email).
  • lpcRegistrationExpiresDateString — секция диалога — сведения о регистрации. Дата истечения срока действия текущей лицензии.
  • lpcPurchaseButtonText — секция диалога — сведения о регистрации. Текст кнопки «Купить».
  • lpcPurchaseUrl — секция диалога — сведения о регистрации. Если задан, то отображается кнопка купить, при нажатии которой происходит переход по указанному адресу.
  • lpcAdditionalInfoTextTitle — задает заголовок секции дополнительных сведений.
  • lpcAdditionalInfoText — задает текст дополнительных сведений, помещаемых в многострочный прокручиваемый текстовый элемент.
  • pfnImageClickCallback — задает обработчик клика по изображению. Может использоваться для добавления «пасхалки».
  • lpvImageClickCallbackParam — параметр, передаваемый в обработчик pfnImageClickCallback.

Функция-хелпер для масштабного коэффициента

Как описано выше, иногда может потребоваться масштабирование диалога «О Программе» по горизонтали. Коэффициент должен быть зависимым от текущей локали, и неплохой идеей является решение хранить его в виде строки в строковых ресурсах. Данная функция-хелпер помогает извлекать его из ресурсов и преобразовывать в целое число, как требуется. Данная функция доступна как inline-функция и описана в заголовочном файле VMPKit.

inline
UINT VMPIKAPI VMPIK_AboutAppDlgLoadHorScale( HINSTANCE hInstance, int strId, UINT defVal )
{
    TCHAR buf[32];
    VMPIK_LoadString( hInstance, strId, buf, ARRAYSIZE(buf)-1, _T("0"));
    return _tcstol( buf, 0, 10 );
}

Продолжение следует

Данная статья не является заключительной в серии постов о VMPKit, ожидайте продолжения в скором будущем.

Автор: kruzhevnik

Источник

Взлом VMProtect защиты. Полная распаковка программы скриптом + доработка (Глава 2)

Из чего состоит этот туториал

Давайте немного поговорим о том, как построен этот туториал. Он состоит из 3 глав:
1. В первой главе даются общие подробности о самом протекторе, о его опциях и способах
защиты этим протектором. Также здесь рассматривается список литературы, который
желательно прочитать, если Вы хотите хорошо разобраться в распаковке этого протектора.
Во второй части первой главы мы познакомимся с собственноручно написанным
unpackme, узнаем о том, что он из себя представляет и как его «обезвредить».
Ссылка на главу:Взлом VMProtect защиты. Знакомство, подготовка крэкми (Глава 1)

2. Вторая глава туториала предназначена для самых маленьких :-). Здесь мы попробуем
распаковать unpackme, но практически полностью автоматизируем эту работу с помощью
скрипта от LCF-AT . Поначалу я не хотел включать эту главу в туториал, но учитывая, что у
новичков возникает очень много вопросов по работе со скриптом, да и сам скрипт не
лишён недостатков, то всё-таки было решено показать, как работать с отладчиком и его
скриптовым языком. Так же попробуем разобраться, что делать, если файл не запускается
после распаковки. Опытные crackers могут пропустить эту главу.
Ссылка на главу:Взлом VMProtect защиты. Полная распаковка программы скриптом + доработка (Глава 2)

3. Третья глава, пожалуй, самая интересная. Она посвящена инлайн патчингу VMProtect. В
этой главе немного поговорим о теории инлайн патчинга VMProtect-а, попробуем
пропатчить наш unpackme, а также посмотрим, как немного можно ускорить этот процесс.
Вторая часть этой главы будет посвящена реальной коммерческой программе — Movienizer
6.3 . Здесь мы рассмотрим как можно пропатчить заблокированный ключ к программе, и
убрать онлайн проверку регистрации.
Ссылка на главу:Взлом VMProtect защиты. Инлайн патчинг на примере программы Movienizer (Глава 3)

В заключении хочу добавить, что все инструменты, а также программы, использующиеся в
качестве подопытных приложены к этому туториалу.
Ссылка на архив (пароль: www.exelab.ru):Взлом VMProtect защиты.

Глава 2

Подготовка инструментов

Для изучения этого «чуда» нам понадобятся следующие инструменты:
1. OllyDbg 1.10 — прекрасный отладчик пользовательского режима;
2. Плагин Phantom 1.85 для скрытия отладчика OllyDbg;
3. Плагин OdbgScript 1.82.6 для использования скриптов;
4. Плагин CommandBar 3.20.110 для удобной установки брейкпоинтов (точек останова);
5. Scylla 0.9 или ImpREC 1.7 для восстановления импорта.
Конечно, каждый волен использовать тот инструмент, к которому он привык или считает лучше.
Поэтому этот список можно как расширить, так и сузить. Я привёл его лишь для того, чтобы
показать, чем привык пользоваться я.
Теперь нам нужно настроить инструменты и избежать обнаружения защитой нашего отладчика.
Начнём с отладчика. У меня он уже множество раз пропатчен и я, честно говоря, уже и не помню
где, когда и зачем патчил. Поэтому к туториалу прикладываю свой exe-шник. Убираем все
плагины, кроме трёх указанных выше и у нас должно получиться, что-то похожее на это:
Screenshot_4.png
Рисунок 4.Плагины для OllyDbg

Далее смотрим в папку с отладчиком и находим там файл dbghelp.dll — его нужно обновить до
последней версии, т.к. протектор использует ошибку в этой dll (переполнение буфера). В случае
если у Вас будет старая версия dbghelp.dll , то при открытии защищённой программы в отладчике,
отладчик просто сразу закроется.
Если Вы сейчас попробуете запустить программу в отладчике, то сначала получите такое
сообщение:
Screenshot_5.png
Рисунок 5. Предупреждение отладчика

А затем и это:

Screenshot_6.png
Рисунок 6. Обнаружение отладчика

Первое сообщение не относится к защите протектора, а является опцией OllyDbg, которая просто
указывает нам на то, что точка входа программы (в нашем случае это EP, а не OEP, т.к. программа
запакована) находится за пределами секции кода, указанного в PE заголовке. Дело здесь вот в
чем. Если мы посмотрим на карту памяти в отладчике, то увидим, что начало работы
запакованной программы начинается из секции .UPX1
Screenshot_7.png
Рисунок 7. Секции упакованного файла

В то время, как нормальная и не запакованная программа обычно начинает своё выполнение из
секции CODE:
Screenshot_8.png
Рисунок 8. Секции не упакованного файла

Многих новичков это предупреждение отладчика жутко раздражает, а некоторых вообще вводит
в ступор. Мне лично на это сообщение абсолютно всё равно, а иной раз оно даже помогает понять
запакована ли программа или нет. Если кого это предупреждение доводит до безумия, то его
можно отключить в плагине Phantom. Галка называется — fix OllyDbg bugs
Второе сообщение в отличие от первого, относится как раз к защите протектора и указывает нам
на то, что всё, мы своё отладили… :-) Новые версии протектора более дружелюбны к отладчикам,
поэтому для того чтобы скрыть отладчик нужен всего лишь один плагин. Есть один такой
прекрасный плагин — Phantom от Hellspawn-а Phantom делает свою работу прекрасно, прячет
отладчик практически от всего. Единственная просьба к Hellspawn-у — не ленись, допиливай
плагин ко второй ольке :-), очень его там не хватает.
Итак, для того, чтобы анпакми нормально запускался, и его можно было отлаживать, необходимо
настроить плагин следующим образом:

Screenshot_9.png
Рисунок 9. Настройка плагина Phantom

И последнее, что нам нужно сделать, это немного настроить опции OllyDbg , а именно произвести
настройки вкладки «Exceptions»:
Screenshot_10.png
Рисунок 10. Настройка исключений

Таким образом, мы должны проскакивать на всех сгенерированных исключениях. Вот и всё,
теперь можно спокойно «работать» с протектором.

Скрипт VMProtect Ultra Unpacker 1.0

Как уже говорилось выше, LCF-AT написал прекрасный скрипт для автоматической распаковки
VMProtect. Он поддерживает довольно много опций протектора, неплохо выполняет свою работу,
но всё же имеет определённые огрехи. Во-первых, он требует небольшой настройки при первом
использовании, во-вторых он для восстановления импорта использует библиотеку ARImpRec.dll от
Nacho_dj из команды ARTeam . Как мне кажется, это не совсем удачный вариант, т.к. она довольно
много косячит, что выливается в невозможности запуска распакованной программы на другой
операционной системе. Хотя это моё личное и субъективное мнение. И в-третьих даже после
удачной распаковки скриптом в некоторых случаях всё-таки приходится «дорабатывать» дамп
вручную. Несмотря на всё это, многие новички не читают, или не хотят читать инструкции по
работе со скриптом, которые автор скрипта приложил к самому скрипту. И самое главное новички
не хотят думать ГОЛОВОЙ, а это ещё хуже. Ребята ну запомните: скрипт, это НЕ автоматический
распаковщик, это всего лишь хороший помощник в наших начинаниях.
Первоначальная настройка скрипта занимает буквально одну-две минуты. Открываем скрипт в
блокноте, немного прокручиваем вниз и настраиваем следующее:
Screenshot_10_1.png

У меня настроено всё по умолчанию. Исправлено только расположение dll. Если Вы прописали
правильно расположение ARImpRec.dll, но скрипт всё равно упорно указывает, что не может его
найти, то попробуйте:
1. Перекачать скрипт с библиотекой (в самой первой версии такое было, потом поправили);
2. Поместить скрипт и dll в корень диска (например «C:»).
Также необходимо найти эту строчку:
Screenshot_11.png
Рисунок 11. Комментирование строки

И закомментировать строчку, которая выделена. Должно получиться:
// mov ARIMPREC_PATH, «C:Nacho dll testARImpRec.dll»
Ну, всё подготовлено и настроено, поэтому «let’s go…»

Распаковка VMProtect с помощью скрипта

Загружаем файл Unpackme_vmp.exe под отладчиком, стоя на EP, запускаем скрипт командой Run
Script -> Open -> выбираем файл скрипта. Сначала скрипт покажет нам окошко с информацией о
нашей системе (если она была включена в настройках). Нажимаем ОК, т.к. она нам не интересна.
Дальше видя, что отладчик ничего не делает, новички впадают в панику, а нужно всего лишь
продолжить скрипт Правая кнопка мыши -> Script Function -> Resume , так как исполнение скрипта
просто приостановлено. Смотрим, как бегают разные буквы, затем перед нами появляется вопрос
скрипта:
Screenshot_12.png
Рисунок 12. Поиск OEP

Здесь нас спрашивают, каким образом необходимо найти OEP
?

С помощью трюка ESP (hr esp-4)
?

С помощью апи метода
Честно говоря, я всегда нажимал «ДА», т.е. пользовался методом ESP, т.к. он всегда срабатывал.
Немного ждём и скрипт извещает нам, что мы на OEP. Также он показывает нам — реальная ли это
оеп (1) или с украденными байтами (0). Тут бы мне хотелось обратить Ваше внимание на два
момента.
1) В моем случае скрипт остановился на абсолютно верном OEP (у меня не было украденных
байт), но в большинстве случаев он останавливается после вхождения в первый CALL после
OEP, имеющий примерно такой вид (рассматривается программа на Delphi):
Screenshot_13.png
Рисунок 13. Вид первой функции после OEP

Скрипт честно отрапортует, что он остановился РЯДОМ с OEP, а Вам нужно по F8 (пошаговая
трассировка) выйти из функции и чуть посмотреть реальный адрес OEP.
Теперь перезапускаем программу в отладчике ( F2 ) и в папке с программой ищем файл OEP RVA of
Unpackme_vmp.exe — .txt , открываем его и проверяем, какой OEP указан в первой строчке:

Screenshot_14.png
Рисунок 14. Проверяем OEP

Регистры не трогаем, а вот адрес OEP проверяем. Если он найден правильно, то оставляем, в
противном случае указываем правильный в RVA-формате (т.е. как Вы видите его в отладчике).
Когда всё проверено и исправлено, перезапускаем анпакми в отладчике и повторяем все
шаги, которые мы проделываем выше. Появится запрос скрипта о поиске «API LOGGER».
Screenshot_15.png
Рисунок 15. Поиск API Logger

API LOGER это, то место где протектор в открытом виде перебирает все API функции.
Существует два метода поиска этого места:
1) Автоматически (Нажать ДА)
2) Ручной (Нажать НЕТ). При выборе этого метода будет необходимо искать это место
вручную. К скрипту приложено видео, как найти это место. Выбирать второй вариант
необходимо, если Ваша программа запакована старыми версиями VMProtect.
Далее нам говорят о том, что скрипт установил новый адрес OEP и переопределил прыжок на
старый OEP:
Screenshot_16.png
Рисунок 16. Jump на OEP

Если Вам необходимо добавить украденные байты вручную, то это необходимо сделать сейчас.
После этого нажимаем ОК. Если после этого Вы получите сообщение о том, что не найдена
библиотека ARImpRec.dll , то проверьте настройки в скрипте, а лучше переместите скрипт и файл
DLL в корень диска (чтобы был наиболее короткий путь). В противном случае всё придётся
начинать сначала. Если Вы настроили всё правильно, то Вам поступит предложение
просканировать все ссылки на импорт в секции кода:
Screenshot_17.png
Рисунок 17. Сканирование импорта

Нажимаем ДА, и сразу же в следующем окне:
Screenshot_18.png
Рисунок 18. Выбор метода сканирования

Выбираем метод сканирования:
1) ДА — только в секции кода (быстрее)
2) НЕТ — во всех секциях (медленнее, зато надёжнее, так как в старых версиях протектора API
команды находятся в секции VMProtect)
Выбираем второй вариант, т.е. НЕТ. Далее появится запрос о проверке на Vmed APIs (честно
говоря, не понял, что это такое, т.к. этот вопрос появляется очень редко). Нас предупреждают,
что это может привести в дальнейшем к неработоспособности дампа. Давайте пропустим и
нажмём НЕТ. А вот при этом запросе:
Screenshot_19.png
Рисунок 19. Поиск «жёстких» ссылок на импорт

Советую нажимать НЕТ. Так как это практически всегда приводит к ошибкам.

Следующее сообщение рассказывает нам о CPUID RDTSC
Screenshot_20.png
Рисунок 20. Отчёт о CPUID и RDTSC

Читаем информацию и нажимаем ОК. Всё, скрипт закончил основную работу и даёт нам
общую информацию о нашем защищённом файле: показывает нам размер файла до и после
распаковки, уведомляет нас, что присутствовал локальный анти-дамп, анти-дамп ресурсов и
кучи, показывает размер таблицы импорта.

Доработка дампа (Проверяем импорт)

Если Вы сейчас запустите нашу распакованную программу, то увидите, что она запускается и
прекрасно работает. Но не спешите радоваться. Дело в том, что запускаться и работать она будет
только на системе где Вы распаковывали программу (в моем случае на Windows XP). Если Вы
попробуете запустить её на другой операционной системе (например, на Windows 7), то она
работать не будет. Это возможно по двум причинам:
1) Не совсем правильно восстановлена таблица импорта
2) Сработала защита ВМ
В первом случае, проблема возникает, скорее всего, из-за того, что в качестве утилиты для
восстановления импорта скрипт использовал ARImpRec.dll основанный на движке Import
Reconstruction. По сути это и есть тот же самый старый, добрый ImpREC, только с какими-то
изменениями. Какими именно я не знаю, но косячит он с импортом точно также. Import
REConstructor также иногда грешил этим делом. Как это дело проверить и исправить?
Запускаем наш дамп программы (на системе где распаковывали) в OllyDbg , В окне дампа
переключаемся в режим отображения адресов Правая кнопка мыши -> Long -> Address
Screenshot_21.png
Рисунок 21. Режим отображения API адресов

Затем в главном окне нажимаем сочетание клавиш Ctrl+B и в появившемся окне производим
поиск любой API функции. Для Delphi Hex-значение соответствует FF25 , для CC++ FF15
Screenshot_22.png
Рисунок 22. Поиск API функции

На первой же функции (например, по адресу 004011F4 ), нажимаем Правая кнопка мыши -> Follow
in Dump -> Memory Address , затем смотрим в дамп:

Screenshot_23.png
Рисунок 23. API функции операционной системы

На первый взгляд всё в порядке, все функции восстановлены, а нули между функциями — это всего
лишь разделители между библиотеками (на рисунке выше это- kernel32, user32, advapi32, oleaut32
и т.д.). Это нормально и так должно быть. Но первые впечатления обманчивы. Если в окне дампа
Вы немного прокрутите вверх, то увидите, например такую картину:
Screenshot_24.png
Рисунок 24. API функция GetVersion

Обратите внимание, как происходит переход к функции GetVersion . Он происходит через
дополнительный переход, который находится в секции протектора. На другой операционной
системе это может сослужить нам плохую службу. Скорее всего, это происходит, потому что ранее
при запросе скрипта о поиске жёстких ссылок на импорт мы ответили отказом. К сожалению эта
опции в скрипте у меня при согласии всегда приводит к ошибке (скрипт перестаёт работать).
Поэтому эту вещь нам придётся исправлять вручную. Можете покрутить окно дампа немного вниз,
и Вы также увидите, что такая проблема существует ещё с двумя API функциями:
Screenshot_25.png
Рисунок 25. Ещё две функции

Стоит добавить, что из всех протестированных мною программ, такая история происходит почти
всегда и только с функциями:
GetVersion
GetVersionExA
GetVersionExW

Не знаю, виноват ли в этом скрипт или сам VMProtect, но в любом случае это желательно
исправлять (тем более, если Вам понадобится отрезать секции протектора). Но и это ещё не всё.
Попробуйте проделать все выше сказанные операции на другой операционной системе (у меня
Windows 7):
Screenshot_26.png
Рисунок 26. Импорт на Windows 7

Вообще всё не правильно
?
. В окне дампа Вы можете видеть адреса, начинающиеся с 77XXXXXX
(адреса системных функций), но они указывают в «небо». Всё дело в том, что эти адреса
указывали на правильные системные функции на Windows XP, но в Windows 7 на этих местах
другой код. Этим частенько грешил Import Reconstruction, именно поэтому я и сделал вывод, что в
этом виновата именно DLL, входящая в комплект к скрипту. В любом случае это надо исправлять,
так что давайте начнём. Перезапускаем программу в отладчике ( Ctrl+F2 ), производим поиск
первой API функции и находим в дампе наш первый «неверный» GetVersion . Далее нажимаем
Ctrl+G и вводим название API функции GetVersion , нажимаем Enter, смотрим её адрес, а затем в
дампе на этой API функции правая кнопка мыши -> Modify , исправляем старый адрес:
Screenshot_27.png
Рисунок 27. Старый адрес

На новый:
Screenshot_28.png
Рисунок 28. Новый адрес

Нажимаем ОК и в окне дампа видим:

Screenshot_29.png
Рисунок 29. Нормальная IAT

Вот теперь всё соответствует стандарту :-). Проделываем тоже самое и с остальными функциями
(GetVersionExA и ещё один GetVersion).
Ищем начало и конец IAT (при этом просматриваем другие функции):
Screenshot_30.png
Рисунок 30. Начало IAT

Screenshot_31.png
Рисунок 31. Конец IAT

Собираем общую информацию.
Начало : 0049B154
Конец : 0049B7E4
Размер : 0049B7E4 — 0049B154 = 690
Не закрывая отладчик с программой, запускаем Scylla , указываем нашу программу, OEP не
меняем, т.к. она указана правильно. Указываем начало таблицы импорта и её размер, затем жмём
кнопку Get Imports

Screenshot_32.png
Рисунок 32. Восстановление импорта

Видим, что не определилось 9 функций — это мусор. Выделяем правой кнопкой всё дерево
функций и нажимаем delete tree node . Теперь всё прекрасно. Нажимаем кнопку Dump, указываем
имя для нового дампа программы и сохраняем его на диск. И наконец, жмём Fix Dump, указав
последний сохранённый файл. Смотрим как выглядит таблица импорта на Windows 7:
Screenshot_33.png
Рисунок 33. Нормальная таблица импорта

Видим, что всё идеально и красиво :-). Даже GetVersion прописался как надо.

Доработка дампа (Фиксим ВМ)

Бывают случаи, когда вроде бы импорт в порядке, но при запуске приложения выскакивает такая
ошибка:
Screenshot_34.png
Рисунок 34. Ошибка на Windows 7

К сожалению, я так и не успел полностью разобраться из-за чего она происходит и как её
исправить. Но обходные пути всё-таки имеются :-).
Одной из причин по которой происходит данная ошибка, может быть виртуальная машина
VMProtect-а. Как мы уже знаем ВМ протектора может быть:
Screenshot_34_1.png

Первый вариант, тяжело назвать сильной виртуальной машиной, т.к. в данном случае
оригинальный код просто мешается с мусором. Восстановить данный код не составляет
практически никакого труда.
Второй вариант является уже чистым исполнителем виртуальных команд, т.е. по сути это и есть
настоящая виртуальная машина. Восстановить этот код в разы сложнее.
Третий вариант — это смесь первого и второго варианта. Вот это настоящий экстрим. Восстановить
этот код…. очень проблематично :-).
Примером мутации в нашем анпакми может служить проверка на включениеотключение пункта
меню InlineMe (Mutation) по адресу 0046C91C . Вообще, такую вм можно и не восстанавливать, т.к.
никаких ошибок и проверок в ней не будет, но бывают случаи, когда это необходимо, например
интересный код хочется посмотреть без мусора. Тем более процедура очистки мусора не
слишком сложная, и занимает немного времени. Давайте поставим точку останова ( F2 ) на адресе
0046C91C и запустим программу. Мы остановились вот здесь:
Screenshot_34_2.png

Вначале мы видим, что в стек кладётся тип виртуальной машины, а затем происходит прыжок на
её начало. С адреса 0046C94B по 0046C96A обращаем внимание на мусор, который протектор
оставил после обработки нашего кода. Сразу после него (0046C96E) идёт адрес возврата, т.е. адрес
на который мы попадём после выхода из виртуальной машины. Идём дальше ( F7 ), смотрим и
отсеиваем мусорный код:
Screenshot_34_3.png
Мусорный код зачеркнут, нужный выделен. Пока у нас есть две реальные команды:
0057A2F0 8D55 DC LEA EDX, DWORD PTR [EBP-24];
0057A2F7 A1 5C384700 MOV EAX, DWORD PTR [47385C];
Идём дальше:
Screenshot_34_4.png
Следующий кусок:
Screenshot_34_5.png
Далее:
Screenshot_34_6.png
Следующий большой кусочек. Как говорится «от души» :-):
Screenshot_34_8.png
Уже почти конец:
Screenshot_34_9.png
Screenshot_34_A.png

Последний обфусцированный кусок:
Screenshot_34_B.png

Итак, смотрим что получилось:
LEA EDX, DWORD PTR [EBP-24]
MOV EAX, DWORD PTR [47385C]
MOV EAX, DWORD PTR [EAX]
CALL 00455370
MOV EAX, DWORD PTR [EBP-24]
LEA EDX, DWORD PTR [EBP-20]
CALL 00455370
LEA EAX, DWORD PTR [EBP-20]
MOV EDX, 0046CD10
CALL 00404720
MOV EAX, DWORD PTR [EBP-20]
CALL 004089D8
TEST AL, AL
JE 0046C96E
MOV EAX, DWORD PTR [EBP-4]
MOV EAX, DWORD PTR [EAX+324]
MOV DL, 1
CALL 00446EF4
JMP 0046C96E
// выход из вм в программу
На месте ВМ писать нам нельзя, т.к. это место будет использоваться протектором в будущем, и мы
просто получим ошибку. Поэтому найдём немного свободного места (например, по адресу
00EDD0DA) и напишем:

Screenshot_35.png
Рисунок 35.Чистый код

Обращаем внимание на кусок кода выделенный жёлтым. Здесь идёт проверка на наличие файла
«InlineMe.txt», и если он есть, то ниже разблокируется меню InlineMe(Mutation) . Занопим
переход. В самом конце нашего кода поставим прыжок на возврат в наш оригинальный код.
Сохранимся ( Copy to executable -> Selection ), перезапустим анпакми ( Ctrl+F2 ). И последнее: там, где
был вход в вм протектора, ставим безусловный прыжок на наш код:
Screenshot_36.png
Рисунок 36. Прыжок на наш код

Сохраним файл ( Copy to executable -> Selection ) и проверим всё ли правильно. Видим, что всё в
порядке, а как бонус мы получили активированный пункт подменю:
Screenshot_37.png
Рисунок 37. Пункт подменю

Весь тот геморрой, который мы проделали выше, в подавляющем большинстве случаев не нужен,
т.к. обфусцированный код обычно восстанавливать не нужно — он и так прекрасно работает, но
для эстетов это может быть необходимо.
При запуске на другой системе, мы всё равно опять отлавливаем ошибку при запуске. Это
означает, что ошибка была не в обфусцированном коде, а где-то в другом месте.
Запускает отладчик с анпакми под другой операционной системой, и настраиваем исключения
таким образом:

Screenshot_38.png
Рисунок 38. Настройка исключений

Запускаем анпакми ( F9 ) и останавливаемся здесь:
Screenshot_39.png
Рисунок 39. Ошибка доступа

В отчёте отладчика видим, что произошла ошибка доступа при чтении по адресу 359668C5 . Что за
адрес такой интересный? Здесь дело вот в чём: по коду сразу видно, что это код самого
протектора (ВМ). А откуда вызывается сама виртуальная машина? Вот отсюда:
Screenshot_40.png
Рисунок 40. Вызов виртуальной машины

Если сейчас поставить сюда брекпоинт, перезапустить отладчик и запустить программу, то можно
будет потрейсить это место и попытаться разобраться, почему происходит эта ошибка. Я, к
сожалению так и не понял, что это такое
?
. Самое интересное то, что всё прекрасно работает на
всех системах, кроме Windows 7 . Но мы можем воспользоваться тем, что это место не несёт
никакой смысловой нагрузки и просто перепрыгнуть его, перейдя в конец вм:
Screenshot_41.png
Рисунок 41. «Патч» ошибки

Это конечно нельзя назвать хорошим способом, но другого я пока не знаю. Узнаете, сообщите :-)


Зимний шалом.

Статья делится на две части: первая часть была сделана ещё в августе, вторая — сегодня вечером :)
Причина такому долгому простою послужила учёба и много-много прочей рутины, даже сейчас я не обладаю каким-либо большим запасом времени, поэтому я просто дополнил материал в статье, не могу сказать, что это полностью завершённая статья.

— Благодарности
За помощь в дополнительной и полезной информации я обязан нескольким источникам информации

back.engineering/17/05/2021/
t.me/battleyeblog (Сеня давал информацию о хендлерах и Control Flow)
katyscode.wordpress.com/2021/01/23/reverse-engineering-adventures-vmprotect-control-flow-obfuscation-case-study-string-algorithm-cryptanalysis-in-honkai-impact-3rd/

— Предисловие
Во многих P2C проектах я видел, что люди в основном абузят слитую лицензию VMProtect 3.5 с билдом 1213, поэтому в некоторых моментах, где есть отличия я буду сравнивать две версии — 3.5 и 3.6.
Также весь дизассемблированный код, который я прикладываю будет в основном очищен от джанк-кода, исключениями выступают демонстрации джанк-кода в виртуальной машине.

— Информация о протекторах
VMProtect 3.5 — билд 1213 (Слитая лицензия)
VMProtect 3.6 — билд 1406 (Слитая лицензия)

Первая часть

— Обработчики виртуальной машины

После того как юзер накрывает программу протектором, в секции .text появляются входы в VM, которые ведут уже в секцию протектора, где лежит исполняемый код, данные и контекст виртуальной машины.

Входы в VM были статичны в том плане, что их можно было найти по паттерну и начиная с VMProtect 3.6 этот паттерн частично ломается. Частично, потому что в 3.6 можно всё ещё составить правильный паттерн для нахождения входов в VM Entry.

Сравним вход в VM у 3.5 и 3.6 версии:

VMProtect 3.5:

push 2A95E159
call 7FF6EA991D85

VMProtect 3.6:

00007FF760C7106B  | PUSH R14                                               |
00007FF760C7106D  | PUSH R15                                               |
00007FF760C7106F  | CALL vmprotect36.vmp.7FF760C7570C                      |

00007FF760C7570C  | PUSHFQ                                                 |
00007FF760C7570D  | MOV R14,58AC28AC3F68476C                               |
00007FF760C75717  | ADD QWORD PTR SS:[RSP+8],FFFFFFFFBFFFEF8C              |
00007FF760C75727  | MOV R15,6E740BAA45855371                               |
00007FF760C75731  | JGE vmprotect36.vmp.7FF760C752E2                       |

00007FF760C752E6  | MOV R14,QWORD PTR SS:[RSP+18]                          |
00007FF760C752EB  | MOV QWORD PTR SS:[RSP+18],FFFFFFFFF60D74C5             |
00007FF760C752FD  | CALL vmprotect36.vmp.7FF760C7108B                      |

00007FF760C7108B  | CALL vmprotect36.vmp.7FF760C75302                      |

00007FF760C75302  | PUSH R11                                               |
00007FF760C75304  | MOV R11,2D4D16365288609E                               |
00007FF760C7530E  | MOVSX R11D,R11W                                        |
00007FF760C75312  | MOV R15,QWORD PTR SS:[RSP+28]                          |
00007FF760C75317  | TEST R11D,E1508A5                                      |
00007FF760C7531E  | JL vmprotect36.vmp.7FF760C7532C                        |
00007FF760C75333  | MOV R11,QWORD PTR SS:[RSP]                             |
00007FF760C75338  | CALL vmprotect36.vmp.7FF760C75881                      |

Второй метод входа в VM для 3.6:

00007FF650C151F5  | MOV QWORD PTR SS:[RSP],FFFFFFFFF44BB4F8                |
00007FF650C151FE  | CALL vmprotect36.vmp.7FF650C15269                      |

00007FF650C15269  | PUSH 36241C98                                          |
00007FF650C1526E  | CALL vmprotect36.vmp.7FF650C158BE                      |

00007FF650C158BE  | MOV WORD PTR SS:[RSP+8],231E                           |
00007FF650C158C5  | PUSH R13                                               |
00007FF650C158C7  | CALL vmprotect36.vmp.7FF650C150D4                      |

00007FF650C150D4  | PUSHFQ                                                 |
00007FF650C150D5  | MOV R13,QWORD PTR SS:[RSP+20]                          |
00007FF650C150DA  | MOV R13,QWORD PTR SS:[RSP+10]                          |
00007FF650C150DF  | AND WORD PTR SS:[RSP+20],289E                          |
00007FF650C150E6  | SUB WORD PTR SS:[RSP+20],4E                            |
00007FF650C150ED  | JL vmprotect36.vmp.7FF650C150FC                        |
00007FF650C150F3  | ADD QWORD PTR SS:[RSP+8],FFFFFFFFBFFFA734              |
00007FF650C150FC  | CMP QWORD PTR SS:[RSP+20],8B23C7D                      |
00007FF650C15105  | NOT WORD PTR SS:[RSP+20]                               |
00007FF650C1510A  | PUSH QWORD PTR SS:[RSP]                                |
00007FF650C1510E  | POPFQ                                                  |
00007FF650C1510F  | LEA RSP,QWORD PTR SS:[RSP+30]                          |
00007FF650C15114  | CALL vmprotect36.vmp.7FF650CD2288                      |

После перехода в VM Entry нас встречает декрипт адреса следующего блока с кодом, которому программа передаст управление. Естественно, всё это также будет окутано джанк-кодом.

00007FF6D1341DED  | mov rdx, 7FF5912A0000 ; Вшивает результат (Base Address - Image Base)
00007FF6D1341E09  | mov r8, qword ptr ss:[rsp+90]

Для х64 двоичных файлов зашифрованный относительный виртуальный адрес всегда будет находится по адресу RSP+90, это так же относится и к 3.6 версии.

00007FF6D1341E16  | bswap r8d                                              | Начало расшифровки адреса
00007FF6D1341E19  | sar al,cl                                              |
00007FF6D1341E1B  | sar di,58                                              |
00007FF6D1341E1F  | xadd cx,bp                                             |
00007FF6D1341E23  | neg r8d                                                |
00007FF6D1341E26  | dec r8d                                                |
00007FF6D1341E29  | rcl r10,E3                                             |
00007FF6D1341E2D  | movsx di,sil                                           |
00007FF6D1341E32  | sbb bpl,FA                                             |
00007FF6D1341E36  | xor r8d,261F7AAB                                       |
00007FF6D1341E3D  | cmc                                                    |
00007FF6D1341E3E  | ror r8d,1                                              |

После инструкции ror относительный виртуальный адрес уже будет расшифрованным, однако помимо ксора для преобразования может использоваться и обыкновенный add.

Пример взят с перекомпиленного двоичного файла, поэтому и регистр соответственно тоже используется другой.

00007FF6D1341E16  | add ebp, 2594586D ; Второй метод расшифровки адреса
00007FF6D1341E19  | neg ebp
00007FF6D1341E1B  | inc ebp
00007FF6D1341E1F  | rol ebp,1
00007FF6D1341E2D  | add rbp, r10 ; Конец расшифровки

00007FF6D1341E45  | lea r8,qword ptr ds:[r8+rdx]                           |
00007FF6D1341E49  | bts ecx,r12d                                           |
00007FF6D1341E52  | mov rax,100000000                                      |
00007FF6D1341E5F  | lea r8,qword ptr ds:[r8+rax]                           |

00007FF6D1341E66  | sub rsp,180                                            | Выравнивание стека
00007FF6D1341E74  | and rsp,FFFFFFFFFFFFFFF0                               |

У основного алгоритма тоже есть два метода расшифровки следующего хендлера

Первый метод:

00007FF6D1341E7B  | mov rdi,r8                                             |
00007FF6D1341E83  | mov r9,7FF5912A0000                                    |
00007FF6D1341E8D  | ror r10b,26                                            |
00007FF6D1341E91  | sub rdi,r9                                             |
00007FF6D1341E94  | sbb ecx,50BC75E8                                       |
00007FF6D1341E9A  | cmp r14b,69                                            |
00007FF6D1341E9E  | sub r10b,cl                                            |
00007FF6D1341EA1  | lea r10,qword ptr ds:[7FF6D1341EA1]                    |
00007FF6D1341EAF  | mov ecx,dword ptr ds:[r8]                              |
00007FF6D1341EB6  | add r8,4                                               |
00007FF6D1341EBD  | xor ecx,edi                                            |
00007FF6D135082C  | inc ecx                                                |
00007FF6D1350833  | not ecx                                                |
00007FF6D1350835  | bswap ecx                                              |
00007FF6D1350837  | add ecx,7E0005C8                                       |
00007FF6D135083E  | push rdi                                               |
00007FF6D135083F  | xor dword ptr ss:[rsp],ecx                             |
00007FF6D135084F  | pop rdi                                                |
00007FF6D1350850  | movsxd rcx,ecx                                         |

Второй метод:

00007FF6EF52103D  | mov r10d, dword ptr ds:[rdi]                            |
00007FF6EF58C876  | xor r10d, r11d                                          |
00007FF6EF58C879  | neg r10d                                               |
00007FF6EF58C87C  | rol r10d, 1                                             |
00007FF6EF4CBDDF  | add r10d,47BC214D                                      |
00007FF6EF4CBDE7  | neg r10d                                               |
00007FF6EF4C064A  | not r10d                                               |
00007FF6EF530B38  | dec r10d                                               |
00007FF6EF530B3B  | xor r10d,433320D7                                      |
00007FF6EF530B46  | sub r10d,29F1797C                                      |
00007FF6EF530B55  | push r11                                               |
00007FF6EF530B62  | xor dword ptr ss:[rsp],r10d                            |
00007FF6EF530B6A  | pop r11                                                |
00007FF6EF530B6C  | movsxd r10,r10d                                        |

В регистре уже есть расшифрованный адрес для последующего перехода к нему, для передачи управления VMProtect может использовать как и подмену адреса возврата, так и прямой джамп по адресу в регистре:

Первый метод:

add r8, r10 ; Вычисляем адрес
jmp r8 ; Прыгаем по адресу в R8

Второй метод:

add r10, rcx ; Вычисляем адрес
push r10 ; Помещаем адрес в стек
retп ; Передаем управление адресу, который положили в стек

Обфускация смешанной булевой арифметики

Обфускация смешанной булевой арифметики — это метод для выполнения сохраняющего семантику преобразования из простого выражения в представление, которое в дальнейшем тяжело представить во время анализа. В дальнейшем я буду использовать аббревиатуру — MBA (Mixed-Boolean-Arithmetic Obfuscation).

Рассмотрим обфускацию по подробнее как раз на примере таргета под VMProtect.

— Инструкция Sub

Объясню вкратце начало обфускации: Берется первый операнд и инвертируется инструкцией NOT.

Думаю стоит разъяснить как именно работает эта инструкция на очень простом примере. Зачем? Дело в том, что информация об этой инструкции поможет нам понять принцип работы обфусцированного SUB и других ребилднутых по ходу статьи.

Инструкция NOT — это логическое отрицание одного единственного операнда, он инвертирует каждый бит операнда.

Представим число в RDX — 0x1337, в битах это будет представлено как 0001001100110111. И затем инструкция NOT инвертирует каждый бит, т.е. 0 в 1, а 1 в 0. И получим в результате мы
1110110011001000. А если перевести полученное значение в hex, то у нас получится — 0xECC8.

Возьмем за пример: 4183 — 2000, чтобы не путаться: 4183 — в hex это 0x1057, а 2000 — 0x7D0

Как я уже и сказал выше операнд, в котором хранится число 4183 будет инвертировано в 0xEFA8, но протектор за собой в регистр тащит и 0xFFFF, поэтому в результате получается 0xFFFFEFA8, к этому числу плюсуется уже нетронутая 2000.
После операции добавления мы получим 0x00000000FFFFF778, думаю вы уже догадались, что дальше будет делать протектор :)

Это почти, что конец ребилда. Число 0xFFFFF778 попадет под инструкцию NOT и мы получим в результате наше заветное число — 2183, в hex это будет 0x883.

Теперь попробуем представить эту функцию без всякого джанк-кода виртуальной машины. Будем использовать тот же алгоритм исполнения MBA обфусцированной инструкции, что и в виртуальной машине.

mov r10, dwFirstOp
mov rcx, dwSecondOp
not r10
add r10, rcx
not r10d
mov dwFirstOp, r10

Во всех представленных инструкциях я так же буду показывать и сишный код, и вот код Sub:

DWORD VMProtect::Arithmetic::ManualSub( DWORD dwFirstOp, DWORD dwSecondOp )
{
    return ~( dwSecondOp + ~dwFirstOp );
}

— Инструкция Xor

Вспоминаем основы дискретной математики. Xor — операция исключающего ИЛИ. Сравниваются все биты двух операндов между собой, в результате 1 бит может записаться только в том случае, если сравниваемые биты не были равны друг другу. Пример:

Первый операнд            Второй      Результат

            0     XOR     0     =     0

            0     XOR     1     =     1

            1     XOR     0     =     1

            1     XOR     1     =     0

Теперь посмотри как VMProtect смог ребилднуть эту инструкцию.

Сам конец операции выглядит таким образом:

not edx
not r11d
and edx, r11d

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

1671833863323.png

До выполнения операции NOT на EDX и R11D хранились значения 0xFFFFE828 и 0x50. Если еще с первым числом понятно, что до этого над ним повторно выполнялась инструкция NOT, то с 0x50 ничего не понятно. Начинаем снова просматривать дизасм код.

И попадаем на следующий код:

invert_operands.png

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

0xFFFFE828.png

После and edx, r11d в регистр edx будет записано число FFFFE828, то самое число, которое мы встретили в конце операции ксора, в дальнейшем это значение будет хранится в указателе.

Теперь попробуем понять откуда взялось 0x50, первой мыслью у меня было, что это число было преобразовано после операции первого и второго операнда, оставалось только понять где именно это происходит. Чуть позже я нашел этот код и оказался прав, 0x50 получился в результате инструкции AND EDX, R11D. Где в EDX хранилось число 0x1057, а в R11D — 0x7D0.

Попробуем сгенерировать чистый алгоритм без джанк кода виртуальной машины.

mov edx, dwFirstOp
mov r11, dwSecondOp

mov eax, edx ; Резервируем первый операнд
and edx, r11d ; Получаем третье число
mov ecx, edx ; Пихаем третье число в ecx
mov edx, eax ; Восстанавливаем первый операнд

Инвертируем операнды

Получаем итоговые константы

and edx, r11d
mov r11d, ecx

Проворачиваем ту же операцию с этими константами

Получаем наше заветное число после ксора

and edx, r11d
mov dwFirstOp, edx

Так же можно этот вариант представить и без ассемблерного кода, попытавшись его декомпилировать.
На выходе мы получаем чистый алгоритм XOR инструкции VMProtect :)

Разницы особой нет в выполнении, результат будет один, а главное — что он является рабочим.

— Инструкции And и Or

Я объединил эти две инструкции в один подпункт, т.к. их реализация различается в одной инструкции.
Для начала объясню вкратце реализацию AND:

mov eax, dwFirstOp
mov r10, dwSecondOp

Инвертируем операнды

Используем на них операцию ИЛИ и снова инвертируем, получая верный результат

Возвращаем полученный результат в первый операнд

Алгоритм ребилда OR у VMProtect почти 1 в 1, единственная разница между ними в том, что вместо операции ИЛИ в OR используется операция И.

mov eax, dwFirstOp
mov r10, dwSecondOp

Инвертируем операнды

Используем на них операцию ИЛИ и снова инвертируем, получая верный результат

Возвращаем полученный результат в первый операнд

— Остальные инструкции и обфускация константных значений и вычисления адреса хендлеров

Нужно отметить еще один интересный вариант трансляции инструкции, допустим у нас есть инструкция: add r9, rax. VMProtect может транслировать её как:

push r9 ; Указатель стека теперь указывает на адрес регистра r9
add qword ptr ds[rsp], rax ; Добавляем rax к указателю стека
pop r9 ; После выполнения этой инструкции в r9 будет результат инструкции add r9, rax

Такую работу со стеком VMProtect практикует и на других инструкциях.

Я объединил эти две части воедино, поскольку во время анализа виртуальной машины я заметил, что хендлер, в котором происходит расшифровка переменной затем использует её для вычисления следующего адреса хендлера. Это легко проверить поменяв значение переменной сразу же после её расшифровки.

Допустим переменную типа DWORD64 со значением 0xDEADC0DEDEADBEEF, теперь посмотрим как она расшифруется и в дальнейшем на вычисления адреса.

00007FF7F8CB142B  | sub r9,8                                               |
00007FF7F8CB1433  | rcr r10b,5                                             |
00007FF7F8CB1437  | mov r10,qword ptr ds:[r9]                              | Зашифрованная переменная -> 0x4404498584F7A556
00007FF7F8CB143A  | xor r10,r11                                            | Начало расшифровки
00007FF7F8CB143D  | btr esi,eax                                            |
00007FF7F8CB1440  | btc si,FE                                              |
00007FF7F8C195AC  | bswap r10                                              |
00007FF7F8C195AF  | ror r10,1                                              |
00007FF7F8C195B2  | bts rsi,r9                                             |
00007FF7F8C195B6  | neg r10                                                |
00007FF7F8C195B9  | movzx si,r8b                                           |
00007FF7F8C195BE  | movsx si,bpl                                           |
00007FF7F8C195C3  | cmova rsi,r13                                          |
00007FF7F8C195C7  | inc r10                                                |
00007FF7F8C195CA  | bts rsi,rcx                                            |
00007FF7F8C195CE  | ror r10,1                                              | Дешифрованная переменная -> 0xDEADC0DEDEADBEEF

Теперь хендлер начинает вычислять адрес следующего хендлера, используя во всю нашу расшифрованную переменную

00007FF7F8C195D1  | xor r11,r10                                            | ; Начинает использовать нашу переменную
00007FF7F8C195E3  | mov qword ptr ds:[rdi],r10                             | ; Сохраняет расшифрованную переменную для следующего хендлера
00007FF7F8C195E9  | sub r9,4                                               |
00007FF7F8C195F3  | sbb si,17C8                                            |
00007FF7F8C195F8  | bsf si,bp                                              |
00007FF7F8C195FC  | mov esi,dword ptr ds:[r9]                              |
00007FF7F8C19602  | xor esi,r11d                                           |
00007FF7F8BBDC26  | add esi,60E00069                                       |
00007FF7F8BBDC2C  | neg esi                                                |
00007FF7F8BBDC2E  | add esi,2DAA5513                                       |
00007FF7F8BBDC3B  | xor esi,B596E8B                                        |
00007FF7F8C0A0CE  | inc esi                                                |
00007FF7F8C0A0D0  | rol esi,1                                              |
00007FF7F8C0A0D2  | neg esi                                                |
00007FF7F8C0A0D5  | not esi                                                |
00007FF7F8C0A0D8  | push r11                                               |
00007FF7F8C0A0E8  | xor dword ptr ss:[rsp],esi                             | Значение в rsp ксорим на адрес esi
00007FF7F8C0A0F4  | pop r11                                                | Результат ксора запихиваем в r11
00007FF7F8C0A0F6  | movsxd rsi,esi                                         |
00007FF7F8C0A0FB  | add rbx,rsi                                            | Расшифрованный адрес следующего хендлера
00007FF7F8C13F05  | push rbx                                               |
00007FF7F8C13F06  | ret                                                    |

— Мутация

Однако, хочу сказать сразу, что нет смысла сравнивать мутацию в VMP 3.x, она попросту не улучшается уже долго время, но мы специально это сделаем для наглядности :)

Мутация в VMProtect 3.5 и 3.6 не представляет особой сложности, оригинальный асм код вполне читабелен, в основном мутация состоит из добавления ненужного мусора, таковой является jmp $+5, инструкция, которая делает джамп на следующую инструкцию.

Сравнение мутации с 3.5 и 3.6, 3.7

Возьмем как пример простой код, который будет сравнивать две переменные, будем смотреть поменялся ли как-то Control Flow.

Но несмотря на слабый джанк-код, сама мутация со связкой виртуализации (Ультра) является довольно мощной штукой, хоть виртуализация даже без галочки «Мутации» использует джанк-код в хендлерах.
P.S. По слухам от одного человека разработчик VMProtect планировал переделать мутацию, но, видимо в 2022 году такого обновления пока не стоит ждать.

Вторая часть
[Анализ от 23.12.2022]

Сравнение VMProtect 3.x с VMProtect 2.x

Для сравнения двух разных версии возьмем VMProtect 3.6 и VMProtect 2.13.8.

Наше сравнение будет состоять из следующих пунктов:
— Вызов обыкновенного Native Api
— Вызов Native Api syscall

Для тестирования будет использоваться вот такой семпл:

— Загрузка аргумента для вызова функции в VMProtect 2

Загрузка аргумента происходит посредством дешифрования через инструкции sub, xor, neg и других нестандартных инструкции, которые берут значение только из определенных регистров x64.

00007FF7A1E26D3D    | 8A46 FF                          | mov al,byte ptr ds:[rsi-1]              | al = 0x14
00007FF7A1E26D43    | 28D8                             | sub al,bl                               | al - 0x70 = 0xA4
00007FF7A1E257B2    | 2C 5A                            | sub al,5A                               | al - 0x5A = 0x4A

00007FF7A1E257B6    | F6D8                             | neg al                                  | Инвертация всех битов нашего аргумента, получим из 0xA4 -> B6

Дальше над al будет произведена операция XOR со специальным ключем, чтобы получить 0xFF.

00007FF7A1E289A6    | 34 49                            | xor al,49                               | al ^ 0x49 = 0xFF

Как уже было выше сказано про нестандартные инструкции, не имеющих операнда при вызове, то таковыми являются: cbw, cwde, cdqe.
Все эти инструкции для своего операнда используют только регистр RAX и никак не влияют на RFlags.

Каждая из этих инструкции работает только с определенным размером регистра, если вы используете Cbw, то соответственно Cbw будет брать только al значение, а Cwde — eax значение.

  • Cbw — конвертирует byte в word, т.е. 0xFF будет преобразован в 0xFFFF.
  • Cwde — конвертирует word в dword, т.е. 0xFFFF будет преобразован в 0xFFFFFFFF.
  • Cdqe — как вы могли уже догадаться конвертирует dword в qword, т.е. 0xFFFFFFFF будет преобразован в 0xFFFFFFFFFFFFFFFF.

Далее, преобразованный аргумент виртуальная машина сохранит в виртуальный стек, перед этим сместив стек на 0x8.

00007FF7A1E29E3E    | 48:83ED 08                       | sub rbp,8                               | Смещаем виртуальный стек машины на 0x8, чтобы пушнуть туда qword число
00007FF7A1E288C8    | 48:8945 00                       | mov qword ptr ss:[rbp],rax              | Делаем своего рода push 8-байтного числа, в нашем случае это -1

В последующем виртуальная машина будет запрашивать этот аргумент из виртуального стека для своих манипуляции.

00007FF7A1E27029    | 48:8B55 00                       | mov rdx,qword ptr ss:[rbp]              | Выгружаем обратно для наших хитрых махинации

— Загрузка аргумента для вызова функции в VMProtect 3

Декрипт -1 выглядит следующим способом:

00007FF68A68E7D5    | 48:FFCE                          | dec rsi                                 | rsi = 0
00007FF68A68E7D5    | 48:FFCE                          | dec rsi                                 | rsi = -1
00007FF68A68E81F    | 49:8931                          | mov qword ptr ds:[r9],rsi               | Загружаем в виртуальный стек

Да, преобразование действительно выглядит так, и для достоверности я решил поменять значение rsi до его загрузки в вирт. стек, после F9 в RAX появился статус ошибки STATUS_INVALID_HANDLE.
Конечно мы рассматриваем случай, связанный только с -1, где есть множество способов преобразовать его. Для каких-нибудь других больших чисел используется совсем другой метод дешифрования.

— Вызов функции в VMProtect 2 и 3

Разницы между ними особой и нет, в любом случае используется RET для передачи управления адресу, на который указывает стек.
По соглашениям вызовов первый аргумент всегда будет лежать в регистре RCX, что и делает VMProtect.

00007FF7A1E258B5    | 6641:87C8                        | xchg r8w,cx                             |
00007FF7A1E258B9    | 59                               | pop rcx                                 |
00007FF7A1E258BA    | 6641:89C8                        | mov r8w,cx                              |
00007FF7A1E258BE    | 6644:0FBEC2                      | movsx r8w,dl                            |
00007FF7A1E258C3    | 4C:8D80 9902A751                 | lea r8,qword ptr ds:[rax+51A70299]      |
00007FF7A1E258CA    | 41:58                            | pop r8                                  |
00007FF7A1E258CC    | C3                               | ret                                     |

4 инструкции тут являются обыкновенным джанком, поэтому их можно вырезать, и получим такой вызов:

00007FF7A1E258B9    | 59                               | pop rcx                                 | ; 0xFFFFFFFFFFFFFFFF
00007FF7A1E258CA    | 41:58                            | pop r8                                  | ; NtSuspendProcess
00007FF7A1E258CC    | C3                               | ret                                     |

— Вызов syscall в VMProtect 2 и 3

Разобрались с обычной функцией, а что на счёт системного вызова? Дадим протектору новый семпл:

В VMProtect 2 инициализация системного вызова выглядит следующим образом:

00007FF7E3FE9B3F    | 66:0FB746 FE                     | movzx ax,word ptr ds:[rsi-2]            |
00007FF7E3FE9B49    | 86E0                             | xchg al,ah                              |
00007FF7E3FE9B50    | 66:31D8                          | xor ax,bx                               |
00007FF7E3FE9B54    | 66:F7D8                          | neg ax                                  |
00007FF7E3FE9B58    | 66:F7D0                          | not ax                                  |
00007FF7E3FE9B61    | 66:05 3F1E                       | add ax,1E3F                             | ax = 0x1BB - системный номер NtSuspendProcess

00007FF7E3FE9F3D    | 41:5A                            | pop r10                                 | r10 = 0x1BB
00007FF7E3FE9F41    | 5A                               | pop rdx                                 | Адрес с сисколлом
00007FF7E3FE9F42    | C3                               | ret                                     |

00007FF7E3FEAD1A    | 0F05                             | syscall                                 |

VMProtect 3.x:

00007FF7093BE5DC    | 8B2F                             | mov ebp,dword ptr ds:[rdi]              | ebp = 6B1ED59D
00007FF7093BE5EC    | 41:33EB                          | xor ebp,r11d                            | ebp ^ 4016D008 = 2B080595
00007FF7093BE5F4    | F7D5                             | not ebp                                 | ebp = D4F7FA6A
00007FF7093BE5F6    | F7DD                             | neg ebp                                 | ebp = 2B080596
00007FF7093BE600    | 81ED 5207082B                    | sub ebp,2B080752                        | ebp = FFFFFE44
00007FF7093BE60D    | F7D5                             | not ebp                                 | ebp = 1BB

00007FF70934233C    | 41:5A                            | pop rax                                 | rax = 0x1BB
00007FF70934233D    | 41:5A                            | pop rcx                                 | r10 = -1
00007FF709397B77    | 41:5A                            | pop r10                                 |
00007FF709397B80    | 5D                               | pop rbp                                 |
00007FF709397B81    | 41:5B                            | pop r11                                 |
00007FF709397B88    | 41:59                            | pop r9                                  |
00007FF709397B8A    | 41:5E                            | pop r14                                 |
00007FF709397B8C    | 5E                               | pop rsi                                 |
00007FF709397B8D    | E9 31CCF7FF                      | jmp easton_gay.vmp36.7FF7093147C3       |
00007FF7093147C3    | C3                               | ret                                     |

00007FF7E3FEAD1A    | 0F05                             | syscall                                 |

— Разница между вызовами для защиты памяти в VMProtect 2 и VMProtect 3

Анти-отладку обозревать, думаю, смысла нет никакого, но для тех, кто не видел — Ahora57 в своём треде расписал работу анти-дебага и темиды и вмпрота. https://yougame.biz/threads/264690/

Перед считыванием CRC32 VMProtect 2 вызывает GetModuleFileNameW, чтобы получить полный путь к таргету. Не тяжело догадаться, что после этого будет вызван CreateFileW с аргументом, где лежит наш путь к файлу.

Последовательность вызовов выглядит таким образом: GetModuleFileNameW -> CreateFileW -> GetFileSize -> CreateFileMappingW -> MapViewOfFile -> UnmapViewOfFile -> CloseHandle, и всё это вызывается в одном месте, просто call rax.
Только после этой чреды вызовов протектор начнёт сканировать байты абсолютно всех секции, чем-то напоминает метод SecuROM :)

Обойти подобный чек возможно подменив путь к оригинальному файлу в CreateFileW, однако, тот же SecuROM после вызова CloseHandle специально себя патчил, и не стоит забывать о том, что он сканирует самого себя.

В защите памяти для VMProtect 3 из WinAPI используется только GetModuleFileNameW, всё остальное это NtApi.
Список NtApi функции, которые вызываются для чтения файла и дальнейшего взаимодействия с ним:

NtOpenFile
NtCreateSection
NtMapViewOfSection
NtQueryVirtualMemory
NtUnmapViewOfSection

Так же вместо CloseHandle теперь используется NtClose.

Что ещё примечательно, так это то, что считывание байтов секции у VMProtect 2 происходит только в одном месте:

xor al, byte ptr ds:[rdx]

Когда у VMProtect 3 вычисления разбросаны повсюду.

Заключение
Вот и подошла к концу моя короткая статья про VMProtect. Добавить мне особого нечего к заключению, как я и говорил выше в предисловии — у меня больше не хватает времени пилить контент для этого форума. Не буду заглядывать в будущее, всякое может приключиться, ведь саму статью я вообще планировал выпускать в сентябре.
Не будем о плохом, ведь скоро новый год, поэтому желаю вам всех благ!

Мой бложик:

Авторы: Sudeep Ghosh, Jason Hiser, Jack W. Davidson

В данном разделе проводится всестороннее исследование методологии атаки замещения. Здесь описывается Strata, защитная виртуальная машина, и создание защищенного приложения PV. Целевое приложение было выбрано из тестовых задач на целочисленную арифметику набора SPEC CPU2000. Данные тестовые задачи были выбраны как примеры типичных приложений, они широко используются для измерения производительности. Данные программы содержат от нескольких тысяч до сотен тысяч строк и выполняют различные задачи. Таким образом, они достаточно варьируются как по размеру кода, так и по функциональности, чтобы проверить наши идеи. В рамках данного исследования мы сконцентрировались на тестовой задаче 256.bzip2 в качестве целевого гостевого приложения P. 256.bzip2 – это модифицированная версия архиватора bzip, разработанная для оценки производительности CPU. Все наши тесты выполнялись на 32-битной платформе Intel x86 под управлением ОС Linux. Все компоненты были изначально скомпилированы с помощью gcc.

В разделе 2 было дан высокоуровневый обзор виртуализации уровня процесса. Хотя системы такой виртуализации сложны по своей природе, они являются вариациями подхода декодирование-диспетчеризация. Системы декодирования-диспетчеризации состоят из главного цикла который проходит через три стадии: декодирование, диспетчеризация и выполнение. На стадии декодирования извлекается опкод инструкции. Стадия диспетчеризации использует эту информацию, чтобы вызвать соответствующие обработчики. На стадии выполнения затем извлекаются операнды и вызывается соответствующий обработчик, реализованный на хостовой системе. В системах декодирования-диспетчеризации в их базовой форме проблемой могут стать издержки производительности. Одно из требований использования виртуализации как платформы для безопасности – низкие издержки при выполнении. Данное исследование сосредоточено на виртуализации уровня процесса, основанной на динамической трансляции двоичного кода [45], которая включает выполнение преобразования по запросу блоков кода гостевого приложения в исполняемые инструкции хостовой машины и кэширование их для последцющего использования. Существует множество эффективных инструментов динамической трансляции двоичного кода с низкими издержками, включая DynamoRIO [7], HDTrans [47], Pin [33], PTLSim [55] и Strata [43].

 

(a) Виртуализованное приложение, PV, запущено под фреймворком интроспекции кода (CIF).

(b) CIF перехватывает вызов к функции входа защитной ПВМ.

(c) CIF приступает к загрузке атакующей ПВМ.

(d) Управление затем передается функции входа атакующей ПВМ, которая приступает к запуску P без динамических защит.

Рисунок 3. Шаги, иллюстрирующие методологию атаки на виртуализованные приложения.

Защитная ПВМ в нашем случае реализована с помощью двоичного транслятора Strata [42, 43]. Рисунок 4 иллюстрирует механизм Strata и методы динамической защиты. При старте программы Strata получает контроль над выполнением, сохраняет текущий контекст выполнения (текущий PC, значения регистров, условные коды и т. д.), и начинает выборку, декодирование и трансляцию инструкций, начиная со стартового адреса P. Этот процесс продолжается до тех пор, пока не будет выполнено условие окончания трансляции. Транслятор восстанавливает контекст и передает управление только что транслированному блоку. После завершения выполнения блока управление возвращается к транслятору и он начинает трансляцию со следующего адреса.

Блоки транслированного кода после выполнения не ликвидируются а кэшируются в памяти (называемой кэшем кода) [7]. Перед попыткой трансляции Strata сначала ищет блок в кэше. Если блок найден, управление может быть передано кэшированному блоку. Этот процесс кэширования значительно уменьшает стоимость трансляции. Для уменьшения издержек производительности двоичных трансляторов было предложено и множество других методов [24, 25].

Strata настроена на повышение безопасности во время выполнения. Например, в ходе трансляции она вставляет бесполезный код случайного размера в виртуализованные блоки кода. Таким образом, разметка кэша кода будет различной для каждого запуска программы. Этот метод затрудняет запуск автоматизированных атак, поскольку действительное положение транслированного кода отличается от запуска к запуску.

Кодовый кэш также укреплен в целях противодействия анализу [20]. Один из таких укрепляющих методов – периодическое очищение кэша, в ходе которого транслированный код удаляется из кэша через постоянные промежутки времени. Для восстановления приложения целиком должны быть сопоставлены все снимки кодового кэша. Strata может использовать преобразования кода [6] так, чтобы один и тот же блок приложения транслировался в разные формы после каждой очистки кэша. Очистка также гарантирует, что транслированный код появляется в ходе выполнения в разных местах. Подобная динамика усложняет нахождение и удаление защитных средств.

Рисунок 4 Виртуальная машина Strata, модифицированная для применения динамической защиты.

6.2 Создание защищенных программ

Защищенное виртуализованное приложение было создано с помощью линковочного инструмента перезаписи двоичного кода, Diablo [16]. Diablo считывает все объектные файлы и библиотеки, составляющие приложение и создает его внутренне представление. Модифицированное представление может затем быть выгружено в виде исполняемого файла. Мы модифицировали Diablo, чтобы применить несколько методов защиты к приложению. Например, охранники контрольных сумм1 были помещены в код 256.bzip2 и Strata.

Во время выполнения охранники контрольных сумм присутствуют в 256.bzip2, защищая Strata и наоборот, осуществляя циклическую верификацию. Diablo был также адаптирован для чередования кода ПВМ и гостевого приложения, что затрудняет статический анализ. Рисунок 5(a) показывает разметку двоичного кода виртуализованного приложения после нормальной компиляции и линковки. Информированный противник может проанализировать и определить отдельные регионы кода и извлечь полезную информацию. Поэтому в ходе линковки Diablo случайным образом перемешивает блоки кода 256.bzip2 с блоками кода Strata, создавая в результате разметку, изображенную на рисунке 5(b). Статический анализ перемешанного кода эквивалентен проблеме отделения кода от данных, которая в общем случае нерешаема [26]. Блоки кода приложения шифруются алгоритмом AES.

6.3 Реализации атаки

Данный раздел описывает две реализации предложенной методологии атаки, которая делает приложение, P, уязвимым к анализу, и последующей атаке, основанной на информации, полученной из проведенного анализа. Первая демонстрационный пример использует фреймворк динамической инструментации (Pin) для замещения Strata атакующей ПВМ (построенной с помощью ПВМ HD-Trans, которую мы расширили для выполнения AES-шифрования). Вторая реализация использует архитектурный симулятор, PTLsim [55], как фреймворк интроспекции кода и атакующую ПВМ. Хотя для демонстрации методологии мы использовали эти конкретные инструменты, подойти могут любые подобные им.

6.3.1 Атака с использованием динамического двоичного транслятора

Данный прототип использует фреймворк инструментации двоичного кода во время выполнения от Intel – Pin [33] – для замещения Strata другим двоичным транслятором, HD-Trans [47]. Pin предлаuает богатый API для динамической проверки и изменения инструментированных исходных инструкций приложения. Функционал инструментирования реализован в модуле Pintool. Во время выполненеия фреймворк Pin принимает на входе Pintool и целевую программу и выполняет необходимое инструментирование.

Так как защищенное приложение зашифровано, мы сначала должны найти процедуру расшифровки защитной ПВМ и расширить атакующую ВМ для использования того же алгоритма. Криптографические примитивы находятся в ПВМ, которая защищена не так сильно, как приложение, и позволяет более легкий анализ. Были предложены методы, которые могут автоматически выводить данные криптографические примитивы из двоичного кода [8, 21, 31]. Данные схемы включают профилирование виртуализованных приложений и анализ трассировки для нахождения криптографических примитивов. Мы успешно использовали метод Грёберта для определения включенного алгоритма шифрования (AES) и извлечения ключа [21]. HDTrans был затем модифицирован для расшифровки по алгоритму AES блоков кода до трансляции. Раздел 8.2 описывает методы, которые можно использовать для получения более подробной криптографической информации.

Pintool, который реализует данную атаку, действует следующим образом: он начинает с загрузки и запуска защищенного приложения. Когда происходит запуск приложения, Pintool отслеживает функцию точки входа защитной ПВМ. В случае Strata вызов функции входа следует за следующим фрагментом кода:

pop %eax sub 0x1c, %esp pusha pushf push %eax ; contains application start address push <address> ; return address jmp <address> ; jump to entry point function

Когда вызывается функция входа, Pintool извлекает из списка ее аргументов стартовый адрес приложения. Затем он динамически загружает расширенную версию HDTrans, приступает к инициализации HDTrans и передает ей стартовый адрес приложения. Таким образом, HDTrans получает управление защищенным приложением, а Strata так и не запускается. Приложение теперь может быть проанализировано любым способом. Мы модифицировали HDTrans для выгрузки всех выполняемых инструкций на диск.

(a) Разметка регионов кода виртуализованного приложения при стандартной компиляции. Регионы кода P и ПВМ четко выделяются.

(b) Разметка регионов кода после перемешивания блоков инструкций.

Рисунок 5. Разметка секции .text виртуализованного приложения 256.bzip2. Блоки кода ВМ и приложения перемешаны случайным образом, что затрудняет их статическое разделение.

По сути, данная атака не позволяет охранникам контрольных сумм проверить целостность кода. Охранники, расположенные в Strata, никогда не вызываются, а те, что расположены в P, продолжают проверять целостность P и Strata, которая остается нетронутой. На данном этапе код P доступен для анализа.

6.3.2 Атака с использованием архитектурного симулятора

Второй прототип этаки использует архитектурный симулятор PTLSim [55]. В данной реализации PTLSim действует как фреймворк инструментирования и как атакующая ПВМ. PTLSim моделирует ядро современного суперскалярного процессора, совместимого с Intel x86, вместе с полной иерархией кэша, подсистемой памяти и поддерживающими устройствами. Он моделирует все значительные компоненты современного процессора, включая различные конвейерные стадии, функциональные блоки и набор регистров. PTLSim поддерживает полный набор инструкций x86-64 вместе со всеми расширениями (SSE, SSSE и т. д.). Более подробно о симуляторе можно прочесть в руководстве пользователя.

ISA Intel x86 является двухоперандным CISC ISA, однако PTLSim не симулирует инструкции этого набора напрямую. Вместо этого, каждая инструкция x86 сначала транслируется в серию RISC-подобных микроопераций (uops). Для дальнейшего улучшения эффективности PTLSim поддерживает локальный кэш, содержащий транслированную последовательность uop для ранее декодированных основных блоков программы.

Атака происходит следующим образом: криптографические примитивы получаются так же, как в разделе 6.3.1, и PTLSim расширяется для расшифровки инструкций после выборки их из памяти. Во время загрузки PTLSim инициализирует свои внутренние структуры данных и считывает двоичный файл PV. На стадии выборки PTLSim обращается к инструкциям по адресу в памяти, на который указывает его программный счетчик, называемый виртуальным программным счетчиком (Virtual Program Counter, VPC). Мы модифицировали стадию выборки для проверки последовательности инструкций (изображенных ранее в разделе 6.3.1) и поиска функции входа Strata. После того, как стадия выборки распознает функцию входа, симулятор получает ее аргументы, которые содержат стартовый адрес кода приложения. Симулятор затем отбрасывает свою текущую инструкцию, ждет очищения конвейера и приступает к выборке инструкций, начиная с полученного стартового адреса приложения. Симулятор расшифровывает инструкции, используя извлеченный ключ, перед декодированием их в составляющие uop-ы. Таким образом, Strata никогда не запускается и PTLSim может выбирать и симулировать инструкции P напрямую.

Как и в случае с предыдущем прототипом, охранники контрольных сумм не могут обеспечить адекватной защиты. Охранники проверяют только оригинальный код, который никогда не запускается. Мы можем анализировать P в локальном кэше PTLSim и модифицировать его так же по желанию.

7. Следствия атаки

В данном разделе обсуждается то, как методология замещения ВМ помогает анализу и реверс-инженерии приложений, защищенных ПВМ. Первый шаг любой атаки включает получение базового понимания приложения. Полезной информацией в понимании и анализе программы является граф потока управления (CFG). CFG, полученный из хранящегося на диске двоичного файла, содержит надмножество всех возможных путей выполнения программы. Получение CFG из двоичного кода при наличии статических защит может быть очень затратным в вычислительном плане [49]. По существу атакующие все больше фокусируются на методах получения CFG во время выполнения. Хотя CFG, полученные динамически, могут быть неполными, они все же дают атакующему полезную информацию о приложении. ПВМ предоставляет защиту, делая динамическое построение и анализ CFG очень ресурсо- и времяемкими задачами. Таким образом, для успешного реверсирования приложения защитная ПВМ должна быть замещена с помощью описанной в данной работе методологии.

Чтобы продемонстрировать данное утверждение, мы изучили динамические схемы реверс-инженерии, которые показали себя успешными при атаке программ [34, 50] и сравнили их эффективность при наличии защитной ПВМ. Как правило, данные методы включали инструментирование защищенного приложения для получения трассировки инструкций. Трассировка анализируется для определения отдельных базовых блоков. В результате выполняется анализ потока управления для получения динамического CFG приложения. Атакующий затем выполняет профилирование различных структур вроде базовых блоков и вызовов процедур, чтобы изолировать важные фрагменты кода. Например, Маду и пр. использовали частотность вызова базовых блоков и полустепень захода функций для определения функции ватермаркинга [34]. Подобным образом Удупа и пр. использовали профилирование ребер для определения и удаления ненужных ребер из статического CFG [50].

Чтобы показать, что защитная ПВМ должна быть замещена, мы исследовали применимость подобных методов профилирования в трех различных сценариях выполнения:

  • Приложение запускается без каких-либо защит (Не защищено).
  • Приложение запускается при наличии защитной ПВМ (Защищено).
  • Защищенное приложение было подвергнуто атаке замещения ПВМ, то есть приложение запущено под управлением скомпрометированной ПВМ (Атаковано).

Рисунок 6. Частота запусков для блоков приложения в трех сценариях выполнения (Не защищено, Защищено, Атаковано). Периодическое очищение и перетрансляция блоков кода приложения резко изменяет характеристики частоты выполнения. Под управлением ВМ блоки перестают запускаться с очень большой частотой (107), вместо этого существенно большее количество блоков запускается со средней частотой (103 и 105), вынуждая атакующего расширять пространство поиска. Замещение защитной ВМ восстанавливает исходные характеристики выполнения.

Чтобы облегчить сбор виртуализованных блоков кода приложения, приложение во всех трех сценариях было запущено под фреймворком инструментирования. Далее мы будем называть такие блоки динамическими. Динамические блоки определяются на основе их стартового виртуального адреса. Защитная ПВМ также была модифицирована, чтобы генерировать отображение между транслированными блоками кода приложения и теми, что находятся на диске. Эта модификация была выполнена только в целях данного исследования и обычно не будет доступна атакующему. Целью данного исследования было продемонстрировать, что методология замещения показывает исходные характеристики выполнения приложения, которые были обфусцированы ПВМ, что облегчает анализ и понимание программы.

Мы начали со сравнения генерации трассировки инструкций и анализа блоков в трех указанных сценариях. В ходе сравнения мы выяснили, что упаковка приложения вместе с защитной ПВМ делает анализ динамической трассировки и генерацию CFG гораздо труднее. Во-первых, периодическое очищение и перетрансляция кода приложения преумножают количество индивидуальных базовых блоков. В данном исследовании, использующем программу 256.bzip2, количество динамических блоков кода возросло с приблизительно 3.7 тысяч при незащищенном запуске до более чем 160 тысяч при использовании защитной ПВМ. Число различных ребер графа CFG также возросло с 6.4 до 290 тысяч. Хотя большое количество данных динамических блоков исходит из одних и тех же блоков приложения, ПВМ может использовать методы вроде полиморфизма кода, перераспределения инструкций и вставки бесполезного кода, заставляя блоки выглядеть по-разному [6]. Таким образом, атакующему пришлось бы выполнить анализ потока управления и данных для гораздо более масштабной трассировки инструкций. Использование атаки замещения позволяет атакующему получить исходные инструкции из приложения и значительно уменьшить пространство поиска.

Периодическое очищение и рандомизация кодового кэша ВМ изменяет множество динамических характеристик приложения вроде частоты запуска блоков и полустепеней исхода и захода узлов CFG. Рисунок 6 показывает частоту запуска динамических блоков в трех сценариях. Когда приложение запускается без защиты, мы наблюдаем, что некоторые блоки запускаются очень часто (порядка 107 раз). Атакующий сконцентрировался бы на реверс-инженерии этих блоков, поскольку они находятся на горячих путях приложения. Маду и пр. использовали такую эвристику для нахождения функции ватермаркинга [34]. Запуск приложения под управлением защитной ПВМ обфусцирует подобные блоки из-за периодического очищения и перетрансляции по разных адресам. Частота выполнения защищенного приложения показывает, что больше не наблюдается столь часто запускаемых блоков (то есть, никакие блоки не запускаются с частотой выше 107). Вместо этого появляется больше блоков кода, запускаемых с меньшими частотами (то есть, между 102 и 105). Например, 15% блоков запускаются по меньшей мере 104 раз при запуске под управлением защитной ПВМ, по сравнению с лишь 4% при незащищенном запуске. Таким образом, не существует очевидных блоков, с которых атакующий мог бы начать анализ. Чтобы найти горячие пути приложения, атакующему придется увеличить пространство поиска.

ПВМ также дает атакующему дезориентирующую информацию. В упомянутых ранее трех сценариях мы проранжировали все блоки кода на основе частоты их вызова. Ранг 1 был присвоен наиболее часто вызываемым блокам. Таблица 2 показывает десятку наиболее часто вызываемых блоков при запуске приложения без защиты. Обычно атакующий сначала фокусируется на анализе таких блоков. Столбец 2 отображает ранг этих блоков, когда приложение запускается под управлением ПВМ. Например, наиболее часто запускаемые блоки приложения при незащищенном запуске появляются на 121 позиции, когда приложение запущено под управлением защитной ПВМ. Таким образом, ПВМ может переупорядочивать блоки в плане частотности вызова. Мы наблюдали подобное переупорядочвивание и в том случае, когда ранжирование проводилось на основе полустепеней захода блоков кода. Как показано четвертом столбце таблицы 2, замещение защитной ПВМ восстанавливает исходные динамические свойства приложения. Таблица 3 показывает список десяти наиболее часто вызываемых блоков при запуске под защитой ПВМ, вместе с их соответствующими рангами для незащищенного запуска. Итак, данное исследование показывает, что при наличии виртуализации частотный анализ бесполезен для атакующего. Критичная информация (в данном случае – основанный на частотности ранг) рассеивается защитной ПВМ, усложняя для атакующего ее поиск и эксплуатацию.

Адрес приложения Ранг (Не защищено) Ранг (Защищено) Ранг (Атаковано)
0x8048830 1 121 1
0x804ac3c 2 45 2
0x804ae1b 3 13 3
0x80507d4 4 9 4
0x80507d9 5 173 5
0x80507c0 6 18 6
0x80507fa 7 29 7
0x805082c 8 351 8
0x8050810 9 139 9
0x804a750 10 779 10

Таблица 2. Исходные адреса 10 наиболее часто запускаемых блоков приложения при незащищенном запуске с их соответствующими рангами при запуске под управлением защитной ПВМ. Стандартное отклонение для данных блоков при защищенном запуске достигает 239, что свидетельствует об очень высокой степени разброса. Значит, для нахождения этих блоков понадобится больше усилий. Успешная атака замещения восстанавливает ранги частотности.

Адрес приложения Ранг (Не защищено) Ранг (Защищено) Ранг (Атаковано)
0x804bec0 162 1 162
0x804ae21 17 2 17
0x804ac74 36 3 36
0x804bed3 21 4 21
0x804a7a0 42 5 42
0x804abaf 164 6 164
0x804a81a 88 7 88
0x804a99b 126 8 126
0x80507d4 4 9 4
0x804a7ca 63 10 63

Таблица 3. Исходные адреса десяти наиболее часто вызываемых блоков при запуске приложения под защитой ПВМ вместе с их соответствующими рангами при незащищенном запуске и при атаке замещения.

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

Итак, ПВМ может предоставлять сильную защиту против динамического анализа программ. ПВМ может серьезно увеличить пространство поиска для атакующего, обеспечивать его дезориентирующей информацией и постоянно менять положение критичного кода, чрезвычайно затрудняя проведение динамического анализа. Существует исследование, целью которого было реверсировать приложения, защищенные ПВМ, определив код, относящийся к ВМ в ходе трассировки выполнения [15, 44]. Однако, подобные методологии обычно включают комплексный анализ информации трассировки и нацелены на приложения, которые обычно малы в размерах (несколько сотен инструкций, что характерно, например, для вредоносных программ). Данные методологии не могут дать удовлетворительные результаты применительно к приложениям, защищенным ВМ, поскольку не могут обрабатывать сложные потоки данных и управления, обычно связанные с крупными приложениями. Поэтому методология атаки замещения является ключевой для успешной реверс-инженерии приложений, защищенных ПВМ. Когда защитная ПВМ замещена, приложение может быть проанализировано, а его истинные характеристики беспрепятственно изучены.

Ссылки

[1] ANCKAERT, B., JAKUBOWSKI, M., AND VENKATESAN, R. Proteus: virtualization for diversified tamper-resistance. In DRM ’06: Proceedings of the ACM Workshop on Digital Rights Management (New York, NY, USA, 2006), ACM Press, pp. 47–58.

[2] APPLE. Mac OS X ABI Mach-o file format reference, 2009.

[3] BELLARD, F. QEMU, a fast and portable dynamic translator. In ATEC’05: Proceedings of the USENIX Annual Technical Conference (Berkeley, CA, USA, 2005), USENIX Association, pp. 41–41.

[4] BILLET, O., GILBERT, H., AND ECH-CHATBI, C. Cryptanalysis of a white box AES implementation. In Selected Areas in Cryptography (Hiedelberg, 2004), Springer-Verlag, pp. 227–240.

[5] BIONDI, P., AND FABRICE, D. Silver needle in the skype. In Black Hat Europe (Amsterdam, the Netherlands, 2006).

[6] BORELLO, J.-M., AND M` E, L. Code obfuscation techniques for metamorphic viruses. Journal in Computer Virology 4 (2008), 211–220. 10.1007/s11416-008-0084-2.

[7] BRUENING, D., GARNETT, T., AND AMARASINGHE, S. An infrastructure for adaptive dynamic optimization. In CGO ’03: Proceedings of the IEEE/ACM International Symposium on Code Generation and Optimization (Los Alamitos, CA, USA, 2003), IEEE Computer Society, pp. 265–275.

[8] CABALLERO, J., JOHNSON, N. M., MCCAMANT, S., AND SONG, D. Binary code extraction and interface identification for security applications. In NDSS ’10: Proceedings of the Network and Distributed System Security Symposium (2010), The Internet Society.

[9] CAPPAERT, J., PRENEEL, B., ANCKAERT, B., MADOU, M., AND DE BOSSCHERE, K. Towards tamper resistant code encryption: practice and experience. In ISPEC’08: Proceedings of the 4th International Conference on Information Security Practice and Experience (Berlin, Heidelberg, 2008), Springer-Verlag, pp. 86–100.

[10] CHANG, H., AND ATALLAH, M. Protecting software code by guards. In Proceedings of the ACM Workshop on Security and Privacy in Digital Rights Management (2000), pp. 160–175.

[11] CHEN, X., GARFINKEL, T., LEWIS, E. C., SUBRAHMANYAM, P., WALDSPURGER, C. A., BONEH, D., DWOSKIN, J., AND PORTS, D. R. Overshadow: a virtualization-based approach to retrofitting protection in commodity operating systems. In ASPLOS XIII: Proceedings of the 13th International Conference on Architectural Support for Programming Languages and Operating Systems (New York, NY, USA, 2008), ACM Press, pp. 2–13.

[12] CHOW, S., EISEN, P. A., JOHNSON, H., AND OORSCHOT, P. C. V. White-box cryptography and an AES implementation. In SAC ’02: Revised Papers from the 9th Annual International Workshop on Selected Areas in Cryptography (London, UK, 2003), Springer-Verlag, pp. 250–270.

[13] COLLBERG, C., THOMBORSON, C., AND LOW, D. A taxonomy of obfuscating transformations. University of Auckland Technical Report (1997), 170.

[14] COLLBERG, C., THOMBORSON, C., AND LOW, D. Manufacturing cheap, resilient and stealthy opaque constructs. In POPL’98:Proceedings of the 25th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (New York, NY, USA, 1998), ACM Press, pp. 184–196.

[15] COOGAN, K., LU, G., AND DEBRAY, S. Deobfuscating virtualization-obfuscated software: A semantics-based approach. CCS ’11: Proceedings of the ACM Conference on Computer and Communications Security (October 2011). To appear.

[16] DE BUS, B., DE SUTTER, B., VAN PUT, L., CHANET, D., AND DE BOSSCHERE, K. Link-time optimization of ARM binaries. In LCTES’04: Proceedings of the 2004 ACM SIGPLAN/SIGBED Conference on Languages, Compilers, and Tools for Embedded Systems (Washington D.C., U.S.A, 7 2004), ACM Press, pp. 211–220.

[17] DEHNERT, J. C., GRANT, B. K., BANNING, J. P., JOHNSON, R., KISTLER, T., KLAIBER, A., AND MATTSON, J. The Transmeta code morphing software: using speculation, recovery, and adaptive retranslation to address real-life challenges. In CGO’03: Proceedings of the International Symposium on Code Generation and Optimization (Washington, DC, USA, 2003), IEEE Computer Society, pp. 15–24.

[18] EAGLE, C. The IDA Pro Book: The Unofficial Guide to the World’s Most Popular Disassembler. No Starch Press, San Francisco, CA, USA, 2008.

[19] GARFINKEL, T., PFAFF, B., CHOW, J., ROSENBLUM, M., AND BONEH, D. Terra: a virtual machine-based platform for trusted computing. In SOSP’03: Proceedings of the 19th ACM Symposium on Op- erating Systems Principles (New York, NY, USA, 2003), ACM Press, pp. 193–206.

[20] GHOSH, S., HISER, J. D., AND DAVIDSON, J. W. A secure and robust approach to software tamper resistance. In IH ’10: Proceedings of the 12th International Conference on Information Hiding (Berlin, Heidelberg, 2010), Springer-Verlag, pp. 33–47.

[21] GR ¨ OBERT, F., WILLEMS, C., AND HOLZ, T. Automatic identification of cryptographic primitives in binary programs. In RAID ’11: Proceedings of the 14th International Symposium on Recent Advances in Intrusion Detection (London, UK, 2011), Springer-Verlag, pp. 45–65.

[22] HALDERMAN, J. A., SCHOEN, S. D., HENINGER, N., CLARKSON, W., PAUL, W., CALANDRINO, J. A., FELDMAN, A. J., APPELBAUM, J., AND FELTEN, E. W. Lest we remember: cold-boot attacks on encryption keys, May 2009.

[23] HISER, J. D., COLEMAN, C. L., CO, M., AND DAVIDSON, J. W. Meds: The memory error detection system. In ESSoS ’09: Proceedings of the 1st International Symposium on Engineering Secure Software and Systems (Berlin, Heidelberg, 2009), Springer-Verlag, pp. 164–179.

[24] HISER, J. D., WILLIAMS, D., FILIPI, A., DAVIDSON, J. W., AND CHILDERS, B. R. Evaluating fragment construction policies for SDT systems. In VEE ’06: Proceedings of the 2nd International Conference on Virtual Execution Environments (New York, NY, USA, 2006), ACM Press, pp. 122–132.

[25] HISER, J. D., WILLIAMS, D., HU, W., DAVIDSON, J. W., MARS, J., AND CHILDERS, B. R. Evaluating indirect branch handling mechanisms in software dynamic translation systems. In CGO’07: Proceedings of the International Symposium on Code Generation and Optimization (Washington, DC, USA, 2007), IEEE Computer Society, pp. 61–73.

[26] HORSPOOL, R. N., AND MAROVAC, N. An approach to the problem of detranslation of computer programs. Computer Journal 23, 3 (1980), 223–229.

[27] HU, W., HISER, J. D., WILLIAMS, D., FILIPI, A., DAVIDSON, J. W., EVANS, D., KNIGHT, J. C., NGUYEN-TUONG, A., AND ROWANHILL, J. Secure and practical defense against code-injection attacks using software dynamic translation. In Proceedings of the 2nd International Conference on Virtual Execution Environments (New York, NY, USA, 2006), ACM Press, pp. 2–12.

[28] KANZAKI, Y., MONDEN, A., NAKAMURA, M., AND MATSUMOTO, K.-I. Exploiting self-modification mechanism for program protection. In COMPSAC’03: Proceedings of the 27th Annual International Conference on Computer Software and Applications (Washington, DC, USA, 2003), IEEE Computer Society, pp. 170–176.

[29] KC, G. S., KEROMYTIS, A. D., AND PREVELAKIS, V. Countering code-injection attacks with instruction-set randomization. In CCS ’03: Proceedings of the 10th ACM Conference on Computer and Communications Security (New York, NY, USA, 2003), ACM Press, pp. 272–280.

[30] KIRIANSKY, V., BRUENING, D., AND AMARASINGHE, S. P. Secure execution via program shepherding. In USENIX’02: Proceedings of the 11th USENIX Security Symposium (Berkeley, CA, USA, 2002), USENIX Association, pp. 191–206.

[31] LEDER, F., MARTINI, P., AND WICHMANN, A. Finding and extracting crypto routines from malware. In Proceedings of the IEEE 28th International Performance Computing and Communications Conference (IPCCC) (Washington, DC,USA, December 2009), IEEE, pp. 394 –401.

[32] LINN, C., AND DEBRAY, S. Obfuscation of executable code to improve resistance to static disassembly. In CCS’03: Proceedings of the 10th ACM Conference on Computer and Communications Security (CCS) (Washington D.C., U.S.A, 2003), ACM Press, pp. 290–299.

[33] LUK, C.-K., COHN, R., MUTH, R., PATIL, H., KLAUSER, A., LOWNEY, G., WALLACE, S., REDDI, V. J., AND HAZELWOOD, K. Pin: building customized program analysis tools with dynamic instrumentation. In PLDI ’05: Proceedings of the 2005 ACM SIGPLAN Conference on Programming Language Design and Implementation (New York, NY, USA, 2005), ACM Press, pp. 190–200.

[34] MADOU, M., ANCKAERT, B., DE SUTTER, B., AND DE BOSSCHERE, K. Hybrid static-dynamic attacks against software protection mechanisms. In DRM ’05: Proceedings of the 5th ACM workshop on Digital Rights Management (New York, NY, USA, 2005), ACMPress, pp. 75–82.

[35] MADOU, M., ANCKAERT, B., MOSELEY, P., DEBRAY, S., DE SUTTER, B., AND DE BOSSCHERE, K. Software protection through dy- namic code mutation. In The 6th International Workshop on Information Security Applications (WISA 2005) (August 2005), vol. LNCS, Springer Verlag.

[36] OREANS TECHNOLOGIES. Themida. http://oreans.com/ themida.php, 2009.

[37] OREONS TECHNOLOGY. Codevirtualizer. http://oreans.com/ codevirtualizer.php, 2009.

[38] PAYER, M., AND GROSS, T. R. Fine-grained user-space security through virtualization. In VEE’11: Proceedings of the 7th ACM SIG- PLAN/SIGOPS International Conference on Virtual Execution Environments (New York, NY, USA, 2011), ACM Press, pp. 157–168.

[39] POPEK, G. J., AND GOLDBERG, R. P. Formal requirements for virtualizable third generation architectures. Communications of the ACM 17 (July 1974), 412–421.

[40] PORTOKALIDIS, G., AND KEROMYTIS, A. D. Fast and practical instruction-set randomization for commodity systems. In ACSAC’10: Proceedings of the 26th Annual Computer Security Applications Conference (New York, NY, USA, 2010), ACM Press, pp. 41–48.

[41] ROLLES, R. Unpacking virtualization obfuscators. In WOOT’09: Proceedings of the 3rd USENIX Conference on Offensive Technologies (Berkeley, CA, USA, 2009), USENIX Association, pp. 1–10.

[42] SCOTT, K., AND DAVIDSON, J. Safe virtual execution using software dynamic translation. In ACSAC ’02: Proceedings of the 18th Annual Computer Security Applications Conference (Los Alamitos, CA, USA, 2002), IEEE Computer Society, p. 209.

[43] SCOTT, K., KUMAR, N., VELUSAMY, S., CHILDERS, B., DAVIDSON, J. W., AND SOFFA, M. L. Retargetable and reconfigurable soft- ware dynamic translation. In CGO ’03: Proceedings of the International Symposium on Code Generation and Optimization (Washington D.C., U.S.A, 2003), IEEE Computer Society, pp. 36–47.

[44] SHARIF, M., LANZI, A., GIFFIN, J., AND LEE, W. Automatic reverse engineering of malware emulators. In SP’07: Proceedings of the 2009 30th IEEE Symposium on Security and Privacy (Washington, DC, USA, 2009), IEEE Computer Society, pp. 94–109.

[45] SITES, R. L., CHERNOFF, A., KIRK, M. B., MARKS, M. P., AND ROBINSON, S. G. Binary translation. Communcations of the ACM 36 (February 1993), 69–81.

[46] SMITH, J., AND NAIR, R. Virtual Machines: Versatile Platforms for Systems and Processes (The Morgan Kaufmann Series in Computer Architecture and Design). Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 2005.

[47] SRIDHAR, S., SHAPIRO, J. S., NORTHUP, E., AND BUNGALE, P. P. HDTrans: an open source, low-level dynamic instrumentation system. In VEE’06: Proceedings of the 2nd International Conference on Virtual Execution Environments (New York, NY, USA, 2006), ACM, pp. 175–185.

[48] STARFORCE. Starforce crypto. http://www.star-force.com/, 2008.

[49] SZOR, P. The Art of Computer Virus Research and Defense. Addison-Wesley Professional, 2005.

[50] UDUPA, S., DEBRAY, S., AND MADOU, M. Deobfuscation: reverse engineering obfuscated code. In WCRE ’05: Proceedings of the International Working Conference on Reverse Engineering (Los Alamitos, CA, USA, Nov. 2005), vol. 0, IEEE Computer Society, pp. 45–54.

[51] VMPROTECT SOFTWARE. VMProtect. http://vmpsoft.com/, 2008.

[52] WANG, C., DAVIDSON, J., HILL, J., AND KNIGHT, J. Protection of software-based survivability mechanisms. In DSN’01: Proceedings of the International Conference on Dependable Systems and Networks (Goteborg, Sweden, 2001), IEEE Computer Society, pp. 193–202.

[53] WANG, C., HILL, J., KNIGHT, J., AND DAVIDSON, J. Software tamper resistance: Obstructing static analysis of programs. Tech. rep., Charlottesville, VA, USA, 2000.

[54] YOUNGDALE, E. Kernel korner: The ELF object file format: Introduction. Linux Journal 1995 (April 1995).

[55] YOURST, M. PTLsim: A cycle accurate full system x86-64 microarchitectural simulator. In ISPASS’07: Proceedings of the IEEE International Symposium on Performance Analysis of Systems and Software (2007), IEEE, pp. 23–34.

[56] ZAMBRENO, J., CHOUDHARY, A., SIMHA, R., NARAHARI, B., AND MEMON, N. SAFE-OPS: An approach to embedded software security. Transactions on Embedded Computing Systems 4, 1 (2005), 189–210. 

VMProtect
для Windows

Описание

share

VMProtect скриншот № 1

VMProtect — защита программного обеспечения нового поколения. Защищённые участки кода исполняются на виртуальной машине, что очень сильно осложняет анализ и взлом защищённой программы. Встроенный дизассемблер и подключение MAP файла быстро позволят Вам выбрать необходимые участки кода для защиты от взлома. Программа может по-разному обрабатывать защищаемый код в зависимости от выбранного типа компиляции.

VMProtect поддерживает следующие компиляторы: Delphi, Borland C Builder, Visual C/C++, Visual Basic (native), Virtual Pascal. А также форматы: EXE, DLL, SYS.

ТОП-сегодня раздела «Защита ПО»

скачать VMProtectVMProtect 3.1.2.840

VMProtect — Защита программного обеспечения нового поколения. Защищённые участки кода…

скачать Hide PEHide PE 1.1

Hide PE — программа предназначена для защиты ваших программ от взломщиков. Суть защиты…

скачать MPRESSMPRESS 2.19

MPRESS бесплатный, высокопроизводительный упаковщик, упаковывает PE32(x86 exe,dll), PE32+(x64, amd64, exe,dll) и .NET exe-шники…

скачать R.I.P.ExeR.I.P.Exe 1.5.6

R.I.P.Exe — программа для защиты приложений от «взлома», т.е. от их модификации и анализа…

Отзывы о программе VMProtect

Admin

Отзывов о программе VMProtect 3.1.2.840 пока нет, можете добавить…

Понравилась статья? Поделить с друзьями:
  • Vmmem windows 10 что это как отключить
  • Vmmem windows 10 docker жрет память
  • Vmliteworkstation xp mode для windows 10
  • Vmlite xp mode windows 10 скачать
  • Vmd драйвер для установки windows 10