Введение

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

  • Словарь терминов
  • Изучение, взлом и защита программного обеспечения
  • Что такое VMProtect?
  • Рекомендации по защите

© 2006-2012 Copyright VMProtect Software


Словарь терминов

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

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

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

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

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

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

Обфускация - совокупность методик и средств, направленных на затруднение анализа программного кода. В зависимости от языка программирования, на котором написана защищаемая программа, используются различные типы программ-обфускаторов. При обфускации приложений, написанных на интерпретируемых языках (Perl, PHP и др.), изменения вносятся в исходные тексты: удаляются комментарии, переменным присваиваются бессмысленные имена, выполняется шифрация строковых констант и т.д. При обфускации программ, написанных на Java / .NET, выполняют преобразования байт-кода, обрабатываемого виртуальной машиной. При обфускации скомпилированных программ модифицируют машинный код: в него вносятся "мусорные" команды, "мертвый код", случайные переходы, выполняется мутация исходных команд, часть операций переносится в стек, а также выполняются различные структурные (реже математические) преобразования. При реинжиниринге обфусцированых участков кода их пытаются привести в исходный вид, что при определенном подходе к обфускации становится очень трудоемкой задачей.

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

Точка входа в программу (EntryPoint) - представляет собой начальный адрес, с которого начинается исполнение файла.

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

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


Изучение, взлом и защита программного обеспечения

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

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

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

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

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

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

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

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

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


Что такое VMProtect?

Программа VMProtect относится к новому поколению средств защиты программного обеспечения. VMProtect поддерживает компиляторы Delphi, Borland C Builder, Visual C/C++, Visual Basic (native), Virtual Pascal, при этом VMProtect содержит встроенный дизассемблер, позволяющий работать с файлами форматов EXE, DLL, BPL, OCX, SYS и подключать MAP-файл, создаваемый компилятором, для быстрого выбора участков кода, которые следует защитить. Для автоматизации операций по защите приложения в VMProtect реализован встроенный скриптовый язык. Программа VMProtect обладает полной поддержкой всех 32/64-разрядных операционных систем семейства Windows: Windows 95/98/ME, Windows NT, Windows 2000, Windows XP, Windows 2003, Windows Vista, Windows 7, Windows 8.

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

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

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

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

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

Программа VMProtect доступна в 4 версиях:

  • Demo (демонстрационная версия с ограниченными функциональными возможностями);
  • Lite (облегченная версия);
  • Professional (базовая версия);
  • SenseLock Edition (возможна привязка кода к аппаратными ключами SenseLock).

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

Возможности | Версия VMProtect
 | Demo | Lite | Professional | SenseLock Edition | Ultimate
Методы защиты
Мутация | +* | + | + | + | +
Виртуализация | +* | + | + | + | +
Ультра (мутация+виртуализация) | +* | + | + | + | +
Виды интерфейса
Режим "Мастер" | + | + | + | + | +
Режим "Эксперт" | + | - | + | + | +
Консольная версия | + | - | + | + | +
Опции защиты
Защита памяти | + | + | + | + | +
Защита импорта | - | + | + | + | +
Защита ресурсов | + | - | + | - | +
Упаковка | - | + | + | + | +
Определение отладчика | +** | + | + | + | +
Определение средств виртуализации | +** | + | + | + | +
Сервисные функции
Водяные знаки | +*** | - | + | + | +
Скриптовый язык | + | - | + | + | +
Система лицензирования | + | - | - | + | +
Система администрирования ключей SenseLock | - | - | - | + | -
Менеджер лицензий | + | - | - | - | +
DLL Box | + | - | - | - | +

* - В Demo-версии доступна защита только одной процедуры.
** - Функционал Demo-версии ограничен.
*** - В Demo-версии доступен только один водяной знак.

SenseLock Edition

VMProtect SenseLock Edition (VMProtect SE) - это наша совместная разработка с компанией "Секьюлэб". В этой версии реализованы все новейшие достижения в области защиты программного обеспечения от пиратства:

  • виртуализация исполняемого кода;
  • упаковка и шифрование защищаемого файла;
  • выполнение кода защиты в ядре электронного ключа SenseLock;
  • возможность создавать демонстрационные лицензии, ограничивающие количество запусков, устанавливать ограничения по времени работы программы, лицензировать разные участки кода с привязкой к различным лицензиям;
  • протокол обмена данными с электронным ключом на основе асимметричного алгоритма RSA-1024, исключающий появление эмуляторов.

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

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

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

Совместное использование VMProtect и электронных ключей SenseLock позволяет получить максимально возможную степень защиты исполняемых файлов, на порядки превосходящую обычные протекторы и конверты, работающие с электронными ключами других производителей. При этом, сохраняются все преимущества использования SenseLock, такие как возможность работы без установки драйверов в режиме HID-устройства и высокая аппаратная надежность.

Важно:
Для работы с VMProtect SE необходим мастер ключ, с помощью которого осуществляется защита программного обеспечения, создаются пользовательские ключи и лицензии. Для получения мастер ключа необходимо отправить запрос на info@vmpsoft.com.


Рекомендации по защите

Программа VMProtect представляет собой надежный инструмент защиты кода приложения от изучения и взлома. Однако его эффективное использование возможно лишь в том случае, если встроенные в приложение защитные механизмы спроектированы по определенным правилам и не содержат типичных ошибок. Рассмотрим некоторые моменты, связанные с созданием защитных механизмов приложения.

Процедура регистрации

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

function CheckRegistration(const RegNumber: String): Boolean;
begin
  if RegNumber='123' then
   Result:=True
  else
   Result:=False;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ...
  if not CheckRegistration(RegNumber) then
   exit;
  Application.CreateForm(TForm2, Form2);
  Form2.ShowModal;
  ...
end;

При подобном подходе к проверке регистрационного кода взломщику нет необходимости вникать в алгоритм проверки ключа, а будет достаточно изменить код в начале процедуры проверки таким образом, чтобы она всегда возвращала значение флага, соответствующее корректному регистрационному ключу:

function CheckRegistration(const RegNumber: String): Boolean;
begin
  Result:=True;
  exit;
  ...
end;

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

function CheckRegistration(const RegNumber: String): Boolean;
begin
  if RegNumber='123' then
   begin
    Application.CreateForm(TForm2, Form2);
    Result:=True
   end
  else
    Result:=False;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ...
  Form2:=nil;
  if not CheckRegistration(RegNumber) then
   exit;
  Form2.ShowModal;
  ...
end;

При подобной реализации функции CheckRegistration для "обхода" проверки регистрационного ключа взломщику будет необходимо досконально разбирать работу всей функции проверки ключа. При защите данного приложения с помощью VMProtect рекомендуется выполнить виртуализацию не только функции CheckRegistration, но и процедуры TForm1.Button1Click. Для еще большего усложнения процесса взлома приложения можно воспользоваться режимом защиты "Ультра", сочетающим мутацию кода приложения с его последующей виртуализацией.

Проверка регистрационных ключей

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

var ValidRegNumber: String;
...
function CheckRegistration(const RegNumber: String): Boolean;
begin
  if RegNumber=ValidRegNumber then
   Result:=True
  else
   Result:=False;
end;

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

var
  HashOfValidRegNumber: Longint;
...
// Пример использования обобщенного алгоритма хеширования Питера Вейнбергера (PJW)
function HashPJW(const Value: String): Longint;
var I:Integer;
    G:Longint;
begin
  Result:=0;
  for I:=1 to Length(Value) do
   begin
    Result:=(Result shl 4)+Ord(Value[I]);
    G:=Result and $F0000000;
    if G<>0 then
     Result:=(Result xor (G shr 24)) xor G;
   end;
end;

function CheckRegistration(const RegNumber: String): Boolean;
begin
  if HashPJW(RegNumber)=HashOfValidRegNumber then
   Result:=True
  else
   Result:=False;
end;
...
initialization
  HashOfValidRegNumber:=HashPJW(ValidRegNumber);

end.

При защите программы с помощью VMProtect целесообразно защитить функции HashPJW и CheckRegistration для усложнения работы взломщика.

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

Как правило, программисты, затратившие много времени на реализацию самой процедуры регистрации, не уделяют достаточного внимания защите самого результата регистрации программы. В приведенном ниже примере перед вызовом функции проверки корректности регистрационного номера контролируется состояние глобальной переменной, хранящей результат проверки регистрации. Для взломщика поиск глобальной переменной не представляет трудностей - достаточно будет сравнить сегменты данных ДО и ПОСЛЕ регистрации. Кстати, аналогичный принцип лежит в основе работы известной программы ArtMoney.

var IsRegistered: Boolean;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
  ...
  if not IsRegistered then
   IsRegistered:=CheckRegistration(RegNumber);
  if  not IsRegistered then
   exit;
  ...
end;

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

type PBoolean = ^Boolean;

var IsRegistered: PBoolean;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
  ...
  if not IsRegistered^ then
   IsRegistered^:=CheckRegistration(RegNumber);
  if  not IsRegistered^ then
   exit;
  ...
end;
...
initialization
  New(IsRegistered);

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


История изменений

Версия 2.13

[!] PE: Добавлена поддержка Delay Import
[!] PE: Улучшена совместимость с VS2012
[!] PE: Улучшена совместимость с Embarcadero RAD Studio XE3
[!] PE: Улучшена совместимость с MinGW
[!] Ultimate: Улучшена совместимость DLL Box с Windows 8
[+] WebLM: Добавлена поддержка mbstring.func_overload = 2
[+] WebLM: В коде активации добавлено поле "Дата окончания" (VMProtectActivateLicense теперь может возвращать ACTIVATION_EXPIRED если текущая дата больше даты окончания)
[+] WebLM: В моде для полей "Дата окончания" и "Максимальная дата сборки" добавлено значение "дней с даты покупки"

Версия 2.12

[!] PE: Исправлены ошибки, возникающие в x64 приложениях
[!] PE: Исправлена ошибка, возникающая при наличии у приложения флага IMAGE_DLLCHARACTERISTICS_NX_COMPAT
[+] PE: Добавлена поддержка Windows 8
[+] WebLM: Добавлен русский и китайский язык

Версия 2.11

[!] Исправлена ошибка, возникающая при использовании упаковки в х64 приложениях
[!] PE: Исправлена ошибка, возникающая при использовании опции "Защита импорта"
[!] PE: Исправлена ошибка, возникающая при обработке TLS секции, имеющей релоки
[+] Ultimate: Добавлена активация/деактивация
[+] Mach-O: Добавлена упаковка
[*] PE: Поддержка DriverVerifier
[*] PE: Улучшена поддержка SEH для MSVC
[*] Mach-O: Улучшена поддержка IndirectSymbols

Версия 2.10

[!] Исправлена ошибка, возникающая при использовании опции "Защита ресурсов"
[!] Исправлена ошибка, возникающая при использовании опции "Защита импорта"
[!] Исправлена ошибка, возникающая при защите x64 драйверов, не имеющих релоков
[+] Добавлена поддержка исполняемых файлов от Mac OS X (Mach-O формат)
[+] Добавлена поддержка команды HLT
[+] В SDK добавлено новое API - VMProtectFreeString
[*] Улучшено опеределение имен маркеров и строк, помеченных с помощью VMProtectDecryptStringA/VMProtectDecryptStringW
[*] В имени выходного файла и параметрах лицензирования теперь можно использовать относительные пути
[*] Улучшена поддержка SEH для Delphi
[*] SenseLock Edition: Добавлена опция "Защита ресурсов"
[*] SenseLock Edition: В GUI версии добавлен менеджер лицензий
[*] SenseLock Edition: В GUI версии добавлено программирование ключей
[*] Лицензии в менеджере лицензий теперь можно копировать и вставлять из буфера обмена

Версия 2.09

[!] Исправлена ошибка, возникающая при обнаружении отладчика под Windows 2000
[!] Исправлена ошибка, возникающая при дизассемблировании команд PMULHUW и LDMXCSR
[!] Исправлена ошибка, возникающая при использовании защиты памяти (не проверялась целостность файла на диске)
[!] Ultimate: Исправлена ошибка, возникающая при запуске защищенных файлов под Windows 2000 (требовалось наличие EncodePointer/DecodePointer из kernel32.dll)
[*] Улучшена виртуализация обработчиков исключений для Visual C
[*] Защита ресурсов теперь доступна в Lite и Professional версиях
[*] При использовании защиты ресурсов добавлена поддержка EnumResourceTypes, EnumResourceNames и EnumResourceLanguages (доступно только при вызове из главного модуля)
[+] Добавлена поддержка исключений для Borland C Builder
[+] Ultimate: Ускорена работа VMProtectGetSerialNumberState и VMProtectGetSerialNumberData
[+] В скрипт для TLicenseManager добавлен метод Save
[-] Убрана опция "Перемещать ресурсы в конец файла" - теперь секция ресурсов всегда размещается последней

Версия 2.08

[!] Исправлена ошибка, возникающая при ассемблировании команды TEST [XXXX], REG
[!] Исправлена ошибка, возникающая при диассемблировании х64 команд с префиксом 0x67
[*] Улучшена поддержка файлов, имеющих небольшие занчения для SectionAlignment
[*] Улучшено опеределение имен маркеров и строк для Intel Compiler 12 и Lazarus
[*] Улучшена защита импорта
[*] Ultimate: Теперь можно загружать файлы проекта с параметрами лицензирования
[+] Ultimate: Добавлена защита ресурсов
[+] Ultimate: В скрипт добавлены классы TLicense и TLicenseManager

Версия 2.07

[!] Исправлена ошибка, возникающая при работе с менеджером лицензий если в настройках Windows установлен язык отличный от английского
[+] В режиме эксперта в опциях теперь можно изменять стандартные сообщения "Обнаружен отладчик", "Обнаружены инструменты виртуализации", "Файл поврежден" и "Необходим серийный номер"
[+] В скрипт для TVMProtector добавлено свойство Messages
[*] Улучшена совместимость защиты памяти с некоторыми антивирусами

Версия 2.06

[!] Исправлена ошибка с определением имен маркеров для MinGW
[!] Исправлена ошибка, возникающая при защите импорта в х64 файлах
[!] Исправлена ошибка, возникающая при виртуализации FILD WORD PTR [XXXX]
[!] Исправлена ошибка, возникающая при дизассемблировании PSRLW
[!] Исправлена ошибка, возникающая в DLL Box при активном антивирусе
[*] Файл проекта теперь сохраняется в XML формате
[*] Скрипт теперь хранится в файле проекта
[*] Ultimate: Менеджер лицензий теперь находится в GUI версии
[*] Ultimate: Значительно ускорено выполнение кода, привязанного к серийному номеру
[+] Добавлена поддержка МАР файлов для MinGW
[+] Добавлена поддержка JCLDEBUG
[+] В скрипт для TPEDirectory добавлен метод Clear
[+] В скрипт для TVMProcedure добавлены свойства InputSize и OutputSize
[+] Ultimate: В SDK добавлены новые API - VMProtectBeginVirtualizationLockByKey и VMProtectBeginUltraLockByKey

Версия 2.05

[!] Исправлены ошибки с определением строк для программ на MinGW
[!] Исправлены ошибка "Out of system resources", возникающая при добавлении большого количества процедур в проект
[!] Исправлены ошибка, возникающая при выводе строк в консольной версии
[*] Дата MAP файла теперь берется из содержимого файла
[+] Ultimate: Добавлены примеры по работе с системой лицензирования (на MSVC и Delphi)
[+] Ultimate: Добавлены исходные коды генератора серийных номеров на MSVC
[+] Ultimate: Добавлен DLL Box (доступен только для EXE файлов)
[+] Ultimate: В скрипт добавлен класс TInternalDLLs
[+] Ultimate: В скрипт для класса TVMProtector добавлено свойство InternalDlls
[+] В скрипт для класса TMapRecord добавлено свойство SegmentName
[+] В скрипт для класса TIntelRecord добавлено свойство Operands
[+] Добавлен чешский язык

Версия 2.04

[!] Исправлена ошибка, возникающая при обфускации FDIVR [ESP+XXXX]
[!] Исправлена ошибка, возникающая при обфускации SWITCH в x64 приложениях
[*] При использовании опции "Защита памяти" теперь проверяется целостность файла на диске
[*] Ultimate: Улучшена интеграция модуля лицензирования с защищаемой программой
[+] Добавлена поддержка файлов, имеющих оверлей
[+] Ultimate: Добавлен пример генератора серийных номеров на C#
[+] Ultimate: В менеджере лицензий добавлен экспорт параметров продукта для генератора на C#
[+] Ultimate: В консольную версию добавлен параметр "-lf", с помощью которого можно указать имя файла с параметрами лицензирования
[+] Добавлен французский файл помощи

Версия 2.03

[!] Исправлена ошибка, возникающая при использовании защиты импорта в SYS файлах (под Windows 7 не определялся базовый адрес hal.dll)
[!] Исправлена ошибка, возникающая при обфускации команд с использованием CRx регистров в SYS файлах
[!] Исправлены мелкие ошибки
[*] Улучшения в системе лицензирования Ultimate версии
[+] Добавлен французский язык

Версия 2.02

[!] В SenseLock Edition исправлена ошибка, возникающая при одновременной работе нескольких программ, которые используют разные лицензии в ключе (пользователям SenseLock Edition необходимо обновить прошивку в мастер ключе)
[!] Исправлена ошибка при дизассемблировани EntryPoint у программ на Delphi
[!] Исправлены мелкие ошибки
[*] Важные улучшения в системе лицензирования Ultimate версии
[*] SenseLock Edition теперь использует систему лицензирования Ultimate версии (пользователям SenseLock Edition необходимо обновить лицензию в мастер ключе)
[*] Улучшено определение строк, помеченных с помощью VMProtectDecryptStringA/VMProtectDecryptStringW
[+] В GUI добавлена поддержка Unicode
[+] Добавлен традиционный китайский язык

Версия 2.01

[!] Исправлены мелкие ошибки в инсталляторе, менеджере лицензий и SDK
[*] Для регистрации VMProtect сейчас используется собственная система лицензирования
[+] Добавлен упрощенный китайский язык

Версия 2.0

[!] Исправлены ошибки при дизассемблировании и виртуализации команд CALL FWORD PTR [XXXX] и JMP FWORD PTR [XXXX]
[!] В версии SenseLock Edition исправлена ошибка, возникающая при запрое серийного номер ключа после смены стандартного PIN пользователя
[!] Исправлена ошибка, возникающая при виртуализации SWITCH в х64 приложениях
[!] Исправлена ошибка, возникающая при дизассемблировании команд ROL, ROR, RCL, RCR, SHL, SHR, SAL и SAR, имеющих REX преффикс в х64 приложениях
[!] Исправлена ошибка, возникающая при виртуализации команды CMOVxx
[!] Исправлены мелкие ошибки
[*] Обновлен файл помощи на русском
[*] Обновлен файл помощи на английском
[+] Добавлена система лицензирования (доступна только в Ultimate версии)
[+] В SDK добавлены новые API - VMProtectDecryptStringA и VMProtectDecryptStringW
[+] В скрипт для класса TVMProcedure добавлено свойство CompilationOptions
[+] В скрипт для класса TVMProcedure добавлены свойства SELicense и SEVersion (доступны только в SenseLock Edition)
[+] В скрипт для класса TVMProtector добавлены свойства VMSectionName и VMExecutorCount
[+] В скрипт для класса TVMProtector добавлены свойства SEMasterPassword, SEUserPIN, SEKeyID, SELicense и SEVersion (доступны только в SenseLock Edition)
[-] Убрана опция "Динамическое создание онлайн команд"

Версия 1.81

[!] Исправлена ошибка, возникающая при исполнении промутированного кода на процессорах Pentium 4
[!] Исправлена ошибка, возникающая при исполнении завиртуализированного кода на некоторых процессорах Athlon
[!] Исправлены ошибки, возникающие при дизассемблировании и виртуализации структур, описывающих обработчики исключений с фильтром на класс исключения (для программ на Delphi и Borland C++ Builder)
[!] Исправленa ошибкa, возникающая при использовании нескольких ВМ в 64-битных файлах
[!] Исправлены мелкие ошибки
[*] Улучшено определение имен маркеров (для программ на Visual C)
[*] Значительно снижено потребление памяти в процессе компиляции
[*] Ресурс "HTML" теперь не упаковывается
[*] Обновлен файл помощи на русском
[+] В режиме "Мастер" добавлен поиск процедур по маске
[+] Добавлен новый режим упаковки с более высокой степенью сжатия
[+] Добавлена поддержка файлов, имеющих TLS-калбеки
[+] В SDK добавлено новое API - VMProtectIsValidImageCRC

Версия 1.8

[!] Исправлены ошибки, возникающие при защите импорта в SYS-файлах
[!] Исправлены ошибки, возникающие при защите памяти в DLL/SYS-файлах
[!] Исправлены ошибки, возникающие при использовании нескольких ВМ в DLL/SYS-файлах
[!] Исправлена ошибка, возникающая при мутации кода, имеющего обработчики исключений
[!] Исправлена ошибка, возникающая при одновременном использовании защиты памяти и водяных знаков
[!] Исправлены мелкие ошибки
[*] Улучшено определение ссылок на строки, помеченных маркерами (теперь не нужно помечать маркерами все ссылки на защищаемую строку)
[*] Улучшена защита импорта
[*] Улучшено определение отладчика
[*] Обновлен файл помощи на русском
[+] В GUI-версии теперь можно сохранять файлы проекта под разными именами
[+] Теперь можно открывать файлы проекта
[+] Добавлено обнаружение Sandboxie
[+] Добавлена поддержка файлов, скомпилированных с ключем /SAFESEH
[+] Добавлена поддержка CodeGear 2009
[+] Теперь можно задавать имена маркерам (при использовании VMProtectBegin/VMProtectBeginMutation/VMProtectBeginVirtualization/VMProtectBeginUltra из VMProtectSDKxx.dll/VMProtectDDKxx.sys)
[+] В SDK добавлены новые API (VMProtectIsDebuggerPresent и VMProtectIsVirtualMachinePresent)
[+] Для версии SenseLock Edition добавлена утилита "Система удаленного обновления и лицензирования"
[+] Для версии SenseLock Edition добавлена привязка к идентификатору ключа

Версия 1.7

[!] Секции, имеющие флаги SHAREABLE+WRITABLE, теперь не упаковываются
[!] Исправлены мелкие ошибки
[*] Улучшена защита импорта для SYS-файлов
[*] Улучшена упаковка SYS-файлов (упакованным секциям больше не устанавливается флаг WRITABLE)
[*] Улучшено определение отладчика
[+] Добавлена опция "Защита памяти" (данная опция защищает память от изменений во время работы приложения)
[+] Добавлена опция "Определение инструментов виртуализации (VMware/Virtual PC)"
[+] В скрипт для класса TPEFile добавлено свойство EntryPoint

Версия 1.65

[!] Исправлены мелкие ошибки
[*] Улучшена защита импорта
[*] Улучшено определение отладчика
[+] Добавлено определение непарных маркеров (непарные маркеры доступны в общем списке объектов)
[+] Добавлены маркеры с предустановленным типом компиляции: VMProtectBeginVirtualization, VMProtectBeginMutation и VMProtectBeginUltra
[+] Добавлен традиционный китайский язык

Версия 1.64

[!] Исправлена ошибка, возникающая при работе упакованного файла, имеющего ресурсы "TYPELIB", "REGISTRY" и "MUI"
[!] Исправлена ошибка R6002, возникающая при работе упакованного файла (для программ на Visual C)
[!] Исправлены ошибка, возникающая при использовании опции "Защита импорта" в 64-битных SYS-файлах
[!] Исправлены мелкие ошибки
[*] Файл настроек теперь сохраняется в каталог %APPDATA%/PolyTech/VMProtect
[*] Улучшена защита импорта
[+] Добавлена опция "Виртуальные Машины" (разные завиртуализированные участки кода будут выполняться на разных виртуальных машинах с разной архитектурой)
[+] Добавлена опция "Определение отладчика" (при обнаружении отладчика защищенное приложение будет показывать соответствующее сообщение и закрываться)
[+] В скрипт для класса TPESection добавлено свойство IncludedInPacking
[+] Добавлена поддержка команды IRETD
[+] Добавлен пример c использованием маркеров в Lazarus

Версия 1.63

[!] Исправлены ошибки, возникающие при использовании опции "Защита импорта"
[!] Исправлены мелкие ошибки
[+] В скрипт добавлено событие OnBeforePackFile
[+] В скрипт для класса TPEFile добавлено свойство Handle
[+] Добавлен пример c использованием маркеров в Free Pascal
[*] Улучшен контроль целостности ВМ объектов

Версия 1.62

[!] Исправлены ошибки, возникающие при использовании опции "Защита импорта"
[!] Исправлена ошибка, возникающая при обработке исключения в TRY/EXCEPT/END блоке в режиме "Ультра" (для программ на Delphi и Borland C++ Builder)
[*] Улучшена защита импорта для SYS-файлов
[+] Каждый объект из проекта теперь может иметь свои опции компиляции для более тонкой настройки быстродействия (индивидуальные опции настраиваются в дереве проекта)
[+] Добавлена опция "Перемещать ресурсы в конец файла"
[+] Добавлен пример использования расширенных маркеров (папка Examples\Scripts\UseExtMarkers)

Версия 1.61

[!] Исправлена ошибка, возникающая при запуске упакованных файлов под управлением Windows 95/98
[!] Исправлены мелкие ошибки
[+] В Lite-версии добавлена возможность передачи имени файла через командную строку
[+] В режиме "Мастер" добавлена история
[+] В режиме "Мастер" добавлен выбор языка
[+] Добавлена опция "Защита импорта" (рекомендуется использовать совместно с упаковкой выходного файла)
[+] В консольной версии добавлен параметр "-we" (при использовании этого параметра все предупреждения будут отображаться как ошибки)
[+] В скрипт добавлен класс TPEResourceRecords
[+] В скрипт для класса TPEFile добавлено свойство ResourceRecords

Версия 1.6

[!] Исправлены мелкие ошибки
[*] Улучшена работа с одинаковыми строками (теперь одинаковые строки отображаются как один объект, содержащий несколько записей)
[+] Добавлена возможность упаковки выходного файла
[+] В скрипт добавлен класс TPEExportRecords
[+] В скрипт для класса TPEFile добавлено свойство ExportRecords
[+] В скрипт добавлено событие OnBeforeSaveFile
[+] Добавлен файл помощи на китайском языке (перевод Wang Xiaopeng)
[+] При загрузке MAP-файла добавлена проверка даты и времени модификации (если дата и время модификации MAP-файла отличается от даты и времени модификации защищаемого файла, то MAP-файл не будет загружен)

Версия 1.56

[!] Исправлена ошибка, возникающая при виртуализации команд BT, BTC, BTR и BTS
[!] Исправлены мелкие ошибки
[*] Список опций защиты отсортирован в порядке убывания приоритета
[*] Улучшена поддержка SEH для VC8
[*] Улучшено динамическое создание онлайн-команд
[*] Улучшен контроль целостности объектов ВМ
[+] Добавлена поддержка команд PREFETCH и PREFETCHW
[+] В скрипт добавлены функции CompareText и AnsiCompareText
[+] В скрипт для класса TPEFile добавлено свойство Size
[+] Добавлен пример c использованием маркеров в MinGW
[+] Добавлен китайский язык (перевод Wang Xiaopeng)
[+] В окне добавления процедуры добавлен поиск по маске с использованием символов "*" и "?"
[+] В сообщения компилятора теперь выводится информация о размере выходного файла

Версия 1.55

[!] Исправлена ошибка, возникающая при работе с файлом, имеющим "невыровненное" значение ImageSize
[!] Исправлена ошибка, возникающая при определении начала блока TRY для программ на Delphi
[!] Исправлена ошибка, возникающая при обработке "смешанного" кода из Discardable и NonDiscardable секций в SYS-файлах
[!] Исправлена ошибка, возникающая при виртуализации 64-битных DLL/SYS-файлов
[*] Восстановлена опция "Шифрация регистров при выходе из ВМ"
[+] В скрипт добавлен класс TIniFile
[+] В скрипт для класса TPEFile добавлены дополнительные свойства и методы
[+] В GUI-версию добавлен калькулятор для расчета смещений в файле по адресам
[+] Добавлен итальянский язык (перевод Gabriele Lanza)

Версия 1.54

[!] Исправлена ошибка, возникающая при мутации команды PUSH ESP
[!] Исправлена ошибка, возникающая при виртуализации команды FLDCW [ESP+xxxx]
[!] Исправлена ошибка, возникающая при обфускации байт-кода
[*] Улучшения в GUI
[+] Добавлены немецкий и испанский языки
[+] Добавлена поддержка скриптов
[+] В консольной версии добавлен параметр, с помощью которого можно указать имя файла со скриптом

Версия 1.53

[!] Исправлены ошибки, возникающие при виртуализации кода в DLL/SYS-файлах
[*] Улучшения в виртуализации кода
[*] В консольной версии при перенаправлении вывода текста в файл убраны бегущие проценты
[+] Добавлена возможность вставки маркеров для x86-64
[+] Добавлена возможность создавать папки в проекте
[+] В консольной версии добавлен параметр, с помощью которого можно указать имя файла проекта

Версия 1.52

[!] Исправлена ошибка, возникающая при мутации команд CMOVxx, CMPXCHG и CMPXCHG8B
[!] Исправлена ошибка, возникающая при мутации команды LEA ESP,[ESP+xxxx]
[*] Команды RCL и RCR добавлены в базовую логику ВМ
[+] Добавлена поддержка x86-64
[+] В GUI-версии на закладке "Опции" теперь можно указать имя выходного файла
[+] Добавлена справка на русском и английском языках

Версия 1.51

[!] Исправлена ошибка, возникающая при создании "Relocation Table" нулевой длины
[!] Исправлена ошибка, возникающая при мутации команды CALL [xxxx] в режиме "Мутация" в DLL/SYS-файлах
[!] В примере на Delphi в определение VMProtectBegin добавлена команда, препятствующая оптимизации VMProtectBegin (оптимизация VMProtectBegin могла приводить к неработоспособности защищенного файла)
[+] Добавлена опция "Автосохранение проекта после компиляции"

Версия 1.5

[!] Исправлена ошибка, возникающая при перетаскивании файлов в главное окно
[!] Исправлена ошибка, возникающая при работе с файлами, имеющими атрибут "Read only"
[!] Исправлены мелкие недочеты при компиляции в режиме "Ультра"
[*] Защищенный файл теперь имеет точно такие же атрибуты как оригинальный файл
[+] В список защищаемых процедур теперь можно включать строки (присвоение строк необходимо отмечать маркерами)
[+] В дистрибутив добавлены примеры c использованием маркеров в Borland C++ Builder, Delphi, MASM, Visual Basic и Visual C++
[+] Добавлена опция "Интегрировать в оболочку"

Версия 1.4

[!] Исправлена ошибка, возникающая при мутации команд IMUL, MUL, IDIV, DIV
[!] Исправлена ошибка, возникающая при виртуализации нескольких CASE таблиц, идущих друг за другом
[!] Исправлена ошибка, возникающая при виртуализации команд POP word/dword ptr [esp+xxxx] и IMUL reg8
[!] Исправлена ошибка, возникающая при дизассемблировании команд MOVNTPS и MOVNTPD
[+] Добавлен новый тип компиляции "Ультра (мутация + виртуализация)"
[+] Добавлен выбор типа компиляции в окне добавления/изменения процедуры
[+] Добавлен поиск водяных знаков в процессах (используется для поиска водяных знаков в упакованных файлах)
[*] Улучшена совместимость с ASM компиляторами (TASM, MASM и т.п.)

Версия 1.3

[!] Исправлена ошибка, возникающая при эмуляции команды FILD word ptr [xxxx]
[+] Для каждой процедуры можно указать тип компиляции ("Мутация" или "Виртуализация")

Версия 1.25

[+] Добавлена поддержка SEH для VC7-8
[+] Добавлена поддержка маркеров в Visual Basic (начало и конец участка маркируются вставками на бейсике - Call VarPtr("VMProtect begin") и Call VarPtr("VMProtect end"))
[*] В зарегистрированной версии программы исполнитель ВМ теперь полностью видоизменяется (препятствует автоматическому определению защиты сигнатурными сканерами PEiD, RDG Packer Detector и т.п.)

Версия 1.24

[+] Добавлена возможность вставлять в защищаемый файл водяные знаки
[*] Исполнитель ВМ теперь полностью поддерживает многопоточные приложения (теперь нет необходимости создавать процедуру выделения памяти для нужд исполнителя)
[-] Убрана опция с выбором функции выделения памяти для нужд исполнителя

Версия 1.23

[!] Исправлены мелкие недочеты [+] Добавлен режим работы "Мастер" [+] В дереве процедур добавлен показ ссылок [*] Опции "Скрывать константы", "Динамическое создание онлайн-команд", "Шифрация регистров при переходах", "Контроль целостности объектов ВМ" и многое другое теперь доступны только для зарегистрированных пользователей

Версия 1.22.2

[!] Исправлена ошибка, возникающая при компиляции проектов с включенной опцией "Выделение памяти в исполнителе"

Версия 1.22

[!] Исправлены мелкие ошибки
[+] Добавлена возможность контроля целостности байт-кода (при включенной опции "Контроль целостности объектов ВМ")

Версия 1.21

[!] Исправлена ошибка, возникающая при эмуляции CMPXCHG8b, если в операндах участвуют регистры EAX и EDX
[+] В исполнителе присутствуют только те команды, которые будут участвовать при исполнении байт-кода
[-] Из базовой логики ВМ убраны некоторые команды (затрудняет анализ и декомпиляцию байт-кода)

Версия 1.2

[!] Исправлена ошибка, возникающая при перемещении директории Bound Import
[+] Добавлена возможность включения в проект участков кода, помеченных маркерами (начало и конец участка маркируются вставками на ассемблере - "db $EB,$10,'VMProtect begin',0" и "db $EB,$0E,'VMProtect end',0")
[+] Добавлена возможность включать в проект экспортируемые функции
[-] Убрана опция "Шифрация байт-кода" (шифрация байт-кода теперь всегда включена)

Версия 1.1

[!] Исправлены ошибки, возникающие при: эмуляции SBB (неправильное состояние некоторых флагов); эмуляции CMOVxx, CMPXCHG и CMPXCHG8b (в случаях, когда в качестве второго аргумента использовалась адресация с применением регистра ESP или DWORD значения)
[+] Добавлена поддержка SEH в VB6 (native)
[+] Добавлена поддержка MAP-файлов, генерируемых IDA
[+] Добавлена возможность пакетного добавления процедур
[+] Добавлен показ ссылок на команды прямо в листинге процедур
[+] Добавлена новая опция "Шифрация регистров при переходах"

Версия 1.09

[*] Доработан процесс определения конца процедуры при дизассемблировании циклов
[+] Для совместимости с UPX добавлена очистка флага Sharable у Writable-секций (в случаях, когда старая таблица релоков используется для нужд ВМ)

Версия 1.08

[*] Ускорен процесс дизассемблирования больших участков кода
[*] Доработан механизм метаморфизма исполнителя байт-кода

Версия 1.07

[!] Исправлены ошибки, возникающие при работе с PE файлами (в случаях, когда виртуальный размер секций кода равен нулю); обфускации FPU команд (ошибки приводили к неправильному результату); дизассемблировании (вместо CWDE распознавалась команда CWD); эмуляции команд ROL и ROR (если аргумент имел размер в 1 байт)
[+] Добавлена поддержка группы команд SSE2

Версия 1.06

[!] Исправлены ошибки, приводящие к неправильному состоянию A флага после эмуляции команд SUB, CMP, DEC, NEG, CMPXCHG, SCAS и CMPS.
[!] Исправлена ошибка, возникающая при работе с "невыровненным" файлом
[+] Добавлена поддержка инструкций BSF, BSR и CMPXCHG8b
[+] Для многопоточных приложений теперь можно указать процедуру выделения памяти для исполнителя ВМ (процедура выделения памяти должна быть определена как fastcall)

Версия 1.05

[+] Добавлена поддержка инструкций FUCOMPP и FCMOVxx
[+] При просмотре участков кода добавлен показ типов строк (ANSI/Unicode/Pascal) и комментарии для полей структур, создаваемых компиляторами
[+] Добавлена процедура метаморфизма исполнителя байт-кода
[+] Добавлены новые опции компиляции: "Шифрация байт-кода" и "Удаление настраиваемых элементов (только для EXE файлов)"
[-] Убрана опция "Обфускация байт-кода" (обфускация байт-кода теперь всегда включена)

Версия 1.04

[!] Исправлена ошибка при определении обработчиков SEH для С++
[*] Полностью переработан обфускатор байт-кода
[+] Добавлена подцветка настраиваемых элементов при просмотре участков кода

Версия 1.03

[+] Использование "свободного места" из-под оригинальных участков кода для хранения байт-кода
[+] Добавлено распознавание "коротких" строк в pascal-формате (первый байт - длина строки)

Версия 1.02

[!] Исправлены ошибки с выполнением онлайн-команд в DLL/SYS
[*] Изменён алгоритм подсчёта контрольной суммы байт-кода
[+] Добавлена опция "Контроль целостности исполнителя ВМ" (также работает как противоотладочный приём при отладке исполнителя если отладчик использует в качестве точек остановки INT 03).
[+] Использование "старой" таблицы настраиваемых элементов для хранения байт-кода

Версия 1.01

[!] Исправлена ошибка при создании файлов с настраиваемыми элементами (ошибка возникала при работе DLL/SYS-файлов в операционных системах Windows 95/98/Me)
[!] Исправлены мелкие ошибки в дизассемблере
[+] Добавлена поддержка инструкций F2XM1, FABS, FCLEX, FCOS, FDECSTP, FINCSTP, FINIT, FLDCW, FLDLN2, FLDLG2, FPREM, FREM1, FPTAN, FRNDINT, FSIN, FSTCW, FTST, FYL2X, FPATAN, FLDZ, FISTP
[+] Добавлена поддержка MAP-файлов от Borland C Builder
[+] В настройках программы теперь можно выбирать язык интерфейса и сообщений (доступны русский и английский языки)

Версия 1.0

[!] Исправлены мелкие ошибки, связанные с интерфейсом
[+] Добавлена поддержка инструкций CMOV и CMPXCHG

Версия 0.97

[!] Исправлены мелкие ошибки, связанные с интерфейсом
[+] Добавлена поддержка Virtual Pascal

Версия 0.96

[!] Исправлены ошибки в компиляторе (некорректная работа с C флагом при компиляции инструкций NEG и SBB; более корректная обработка POPF; неправильная проверка CRC байт-кода при обработке внешних условных переходов)
[!] Исправлены ошибки в исполнителе ВМ, связанные с обфускацией байт-кода
[+] Улучшена обработка MAP-файла, генерируемого C/C++ компиляторами (имена процедур содержат информацию об аргументах)

Версия 0.94

[!] Исправлено множество ошибок


Работа с VMProtect

Работа с программой VMProtect возможна в трех режимах:

  • Подготовка проекта
  • Режим GUI
    • Главное меню
    • Главное окно
  • Режим работы из командной строки (консольная версия)


Подготовка проекта

Для примера рассмотрим простейшее приложение, состоящее из одной формы (Form1), элемента ввода текста (Edit1) и кнопки (Button1). Работа приложения заключается в следующем: при нажатии на кнопку Button1 проверяется корректность введенного пароля и при этом выдается соответствующее сообщение.

Пароль проверяется по простейшему алгоритму: сначала он преобразуется в число, после чего определяется остаток от деления данного числа на 17. Пароль считается правильным, если остаток от деления числового представления пароля на 17 равен 13. Процедура проверки пароля на языке программирования Delphi реализуется следующим образом:

function TForm1.CheckPassword: Boolean;
begin
  Result:=(StrToIntDef(Edit1.Text, 0) mod 17=13);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if CheckPassword then
   MessageDlg('Correct password', mtInformation, [mbOK], 0)
  else
   begin
    MessageDlg('Incorrect password', mtError, [mbOK], 0);
    Edit1.SetFocus;
   end;
end;

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

  • С использованием MAP-файла, который формируется компилятором при создании исполняемого модуля программы. В MAP-файле содержится вся необходимая информация об именах и адресах всех процедур и функций приложения. В случае использования MAP-файла при работе с VMProtect выбор защищаемых процедур и функций может осуществляться по имени. Используя МАР-файл, после перекомпиляции проекта VMProtect автоматически определит новые адреса процедур и функций.
  • С использованием маркеров, которые вставляются в исходный код приложения. Маркеры представляют собой специальные метки, по которым VMProtect определяет границы защищаемого блока. Также VMProtect поддерживает маркеры с предустановленным типом компиляции. Использование маркеров целесообразно, если следует защитить только часть процедуры или функции. При использовании маркеров можно указать части кода, в которых в дальнейшем можно будет защитить строковые константы.
  • По адресам защищаемых процедур в исполняемом файле. По сравнению с предыдущими данный способ менее удобен в использовании, так как при любой доработке программы и, соответственно, ее перекомпиляции будет необходимо определять все адреса повторно. Данный способ рекомендуется лишь для защиты программных продуктов, для которых отсутствуют исходные тексты.

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

push ebp      \
mov ebp, esp   \ пролог
push 00        /
push ebx      /

...

pop ebx       \
pop ecx        \ эпилог
pop ebp        /
ret           /

Из-за особенностей реализации современных компиляторов маркеры кода никогда не охватывают пролог и эпилог (даже если маркерами будет охвачен весь текст функции CheckPassword, находящийся между begin и end). Для взлома будет достаточно откорректировать пролог функции таким образом, чтобы виртуалированный код не был выполнен. Для функции CheckPassword это можно сделать следующим образом:

mov eax, 1
ret

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


Использование MAP-файла

Для создания MAP-файла необходимо включить соответствующую опцию в настройках компилятора.

Borland Delphi

Если для разработки приложения используется Borland Delphi, эту операцию выполняют следующим образом: используя главное меню IDE Delphi, входят в свойства проекта (Project - Options), где на вкладке "Linker" переводят переключатель секции "MAP file" в положение "Detailed":

Visual Studio

Если для разработки приложения используется Visual Studio, эту операцию выполняют следующим образом: используя главное меню IDE Visual Studio, входят в свойства проекта (Project - Properties), где на вкладке "Linker - Debugging" переводят переключатель "Generate MAP File" в положение "Yes (/MAP)":

После включения генерации MAP-файла необходимо полностью перекомпилировать проект.

При загрузке MAP-файла VMProtect сравнивает дату и время модификации защищаемого файла и MAP-файла, и в случае их отличия MAP-файл не будет загружен.


Использование маркеров

Для защиты отдельных участков кода, а также для защиты строковых констант в текст программы вставляют маркеры, которые представляют собой вызов импортируемых процедур, находящихся во внешней DLL-библиотеке (для 32-разрядных приложений используется VMProtectSDK32.dll, а для 64-разрядных - VMProtectSDK64.dll; для защиты драйверов используются VMProtectDDK32.sys и VMProtectDDK64.sys соответственно) - далее по тексту VMProtectSDK. Процедуры и функции, находящиеся в VMProtectSDK, не выполняют каких-либо действий и выступают лишь в роли меток, по которым VMProtect определяет границы защищаемого кода. Начало и конец защищаемого блока, соответственно, маркируется следующим образом:

  • Delphi
    • uses VMProtectSDK;

VMProtectBegin(НАЗВАНИЕ_МАРКЕРА);
...
VMProtectEnd;
  • C/C++
    • #include "VMProtectSDK.h"

VMProtectBegin(НАЗВАНИЕ_МАРКЕРА);
...
VMProtectEnd();
  • MASM
    • include VMProtectSDK.inc

invoke VMProtectBegin,SADD(НАЗВАНИЕ_МАРКЕРА)
...
invoke VMProtectEnd
  • Visual Basic
    • Call VarPtr("VMProtect begin")
...
Call VarPtr("VMProtect end")

Также вместо VMProtectBegin можно использовать маркеры с предустановленными типами компиляции:

  • VMProtectBeginVirtualization - маркер будет использовать тип компиляции "Виртуализация".
  • VMProtectBeginMutation - маркер будет использовать тип компиляции "Мутация".
  • VMProtectBeginUltra - маркер будет использовать тип компиляции "Ультра".

Важно:
Для защиты маркеров необходимо добавить их в проект в режиме GUI. Также возможно автоматическое добавление всех маркеров с помощью скриптов.

Обработка маркеров производится следующим образом: при анализе кода защищаемого приложения VMProtect находит все вызовы процедур и функций VMProtectSDK. Границы защищаемых блоков определяются по парам маркеров VMProtectBegin/VMProtectBeginVirtualization/VMProtectBeginMutation/VMProtectBeginUltra и VMProtectEnd. В процессе обработки проекта с помощью VMProtect из кода защищаемой программы удаляются как сами маркеры, так и упоминание о библиотеках VMProtectSDK, поэтому нет необходимости включать данные библиотеки в дистрибутив программы. Маркеры удаляются независимо от того, были они включены в компиляцию или нет. При использовании именованных маркеров их имена также удаляются.

Если задано название маркера, ему будет присвоено имя вида "VMProtectMarker НАЗВАНИЕ_МАРКЕРА". Если название маркера не задано, ему будет присвоено уникальное имя вида "VMProtectMarker"+номер маркера по порядку. Однако использование неименованных маркеров имеет один серьезный недостаток, заключающийся в том, что при вставке в исходный код программы нового маркера нумерация неименованных маркеров изменится, поэтому при защите программ всегда целесообразно использовать именованные маркеры.

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

Важно:
При защите приложений с помощью VMProtect не допускается вложенность маркеров, и границами блока будут считаться только внутренние маркеры. Для приведенного ниже примера границами блока будут считаться лишь VMProtectBegin2 и VMProtectEnd1. Остальные маркеры будут считаться непарными, т.е. VMProtectBegin1 будет считаться непарным маркером с именем UnpairedVMProtectBegin1, а VMProtectEnd2 - UnpairedVMProtectEnd1:

VMProtectBegin // 1
...
VMProtectBegin // 2
...
VMProtectEnd // 1
...
VMProtectEnd // 2

Непарные маркеры можно просмотреть в режиме "Эксперт" на вкладке "Дамп":


Функции SDK

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

Маркеры кода

  • VMProtectBegin
  • VMProtectBeginVirtualization
  • VMProtectBeginMutation
  • VMProtectBeginUltra
  • VMProtectBeginVirtualizationLockByKey
  • VMProtectBeginUltraLockByKey
  • VMProtectEnd

Сервисные функции

  • VMProtectIsDebuggerPresent
  • VMProtectIsVirtualMachinePresent
  • VMProtectIsValidImageCRC
  • VMProtectDecryptStringA
  • VMProtectDecryptStringW
  • VMProtectFreeString

Функции лицензирования

  • VMProtectSetSerialNumber
  • VMProtectGetSerialNumberState
  • VMProtectGetSerialNumberData
  • VMProtectGetCurrentHWID

VMProtectBegin

procedure VMProtectBegin(MarkerName: PAnsiChar);

Маркер начала защищаемой области кода. Вызов VMProtectBegin должен находиться перед первой командой (вызовом процедуры, функции) защищаемого блока кода. MarkerName задает имя маркера, которое в VMProtect будет иметь вид "VMProtectMarker"+MarkerNamе. Например, маркер VMProtectBegin('CheckRegistration') будет выглядеть как VMProtectMarker "CheckRegistration". Если имя маркера не задано, ему будет присвоено уникальное имя вида "VMProtectMarker"+номер маркера по порядку. Тип компиляции защищаемой области задается при обработке проекта в VMProtect.

VMProtectBeginVirtualization

procedure VMProtectBeginVirtualization(MarkerName: PAnsiChar);

Маркер начала защищаемой области кода с предустановленным типом компиляции "виртуализация". MarkerName задает имя маркера (см. VMProtectBegin). При дальнейшей работе с VMPotect тип компиляции, заданный маркером, изменить нельзя.

VMProtectBeginMutation

procedure VMProtectBeginMutation(MarkerName: PAnsiChar);

Маркер начала защищаемой области кода с предустановленным типом компиляции "мутация". MarkerName задает имя маркера (см. VMProtectBegin). При дальнейшей работе с VMPotect тип компиляции, заданный маркером изменить, нельзя.

VMProtectBeginUltra

procedure VMProtectBeginUltra(MarkerName: PAnsiChar);

Маркер начала защищаемой области кода с предустановленным типом компиляции "ультра (мутация + виртуализация)". MarkerName задает имя маркера (см. VMProtectBegin). При дальнейшей работе с VMPotect тип компиляции, заданный маркером, изменить нельзя.

VMProtectBeginVirtualizationByKey

procedure VMProtectBeginVirtualizationLockByKey(MarkerName: PAnsiChar);

Маркер начала защищаемой области кода с предустановленным типом компиляции "виртуализация" и опцией "Привязать к ключу". MarkerName задает имя маркера (см. VMProtectBegin). При дальнейшей работе с VMPotect тип компиляции, заданный маркером, изменить нельзя.

VMProtectBeginUltraLockByKey

procedure VMProtectBeginUltraLockByKey(MarkerName: PAnsiChar);

Маркер начала защищаемой области кода с предустановленным типом компиляции "ультра (мутация + виртуализация)" и опцией "Привязать к ключу". MarkerName задает имя маркера (см. VMProtectBegin). При дальнейшей работе с VMPotect тип компиляции, заданный маркером, изменить нельзя.

VMProtectEnd

procedure VMProtectEnd;

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

VMProtectIsDebuggerPresent

function VMProtectIsDebuggerPresent(CheckKernelMode: Boolean): Boolean;

Функция VMProtectIsDebuggerPresent позволяет определить факт запуска приложения из-под отладчика. Результат ее работы (True/False) может быть обработан с помощью защитных механизмов, встроенных в приложение. При CheckKernelMode=False функция проверяет наличие отладчика User-mode ( OllyDBG, WinDBG и т.п.). При CheckKernelMode=True будет производиться определение как отладчиков User-mode, так и отладчиков Kernel-mode (SoftICE, Syser и т.п.). При защите драйверов значение CheckKernelMode не имеет значения, так как драйвера работают в режиме ядра, поэтому для них всегда проверяется наличие отладчиков Kernel-mode.

VMProtectIsVirtualMachinePresent

function VMProtectIsVirtualMachinePresent: Boolean;

Функция VMProtectIsVirtualMachinePresent позволяет определить факт запуска приложения в виртуальной среде: VMware, Virtual PC, VirtualBox, Sandboxie. Результат ее работы (True/False) может быть обработан с помощью защитных механизмов, встроенных в приложение.

VMProtectIsValidImageCRC

function VMProtectIsValidImageCRC: Boolean;

Функция VMProtectIsValidImageCRC позволяет определить факт изменения памяти приложения. Результат ее работы (True/False) может быть обработан с помощью защитных механизмов, встроенных в приложение.

VMProtectDecryptStringA

function VMProtectDecryptStringA(Value: PAnsiChar): PAnsiChar;

Функция VMProtectDecryptStringA позволяет дешифровать строковую ANSI константу Value. Для шифрации константы её необходимо включить в список защищаемых объектов.

VMProtectDecryptStringW

function VMProtectDecryptStringW(Value: PWideChar): PWideChar;

Функция VMProtectDecryptStringW позволяет дешифровать строковую Unicode константу Value. Для шифрации константы её необходимо включить в список защищаемых объектов.

VMProtectFreeString

function VMProtectFreeString(Value: Pointer): Boolean;

Функция VMProtectFreeString позволяет освободить расшифрованную строку.


Главное меню

Главное меню состоит из следующих пунктов:

  • Файл
  • Текст
  • Проект
  • Водяные знаки
  • Настройка
  • Помощь

Меню "Файл"
  • Открыть - выбрать исполняемый файл приложения, которое необходимо защитить, или файл проекта (*.vmp). В этом же пункте меню доступна история защищенных приложений и возможность ее очистки ("Очистить историю"). Доступ к функции открытия файлов также возможен с помощью кнопки [image] на панели инструментов. Нужный исполняемый файл также можно перетащить в окно VMProtect;
  • Сохранить проект - сохранить установки защиты приложения в файл формата "*.vmp". Файл настроек проекта будет сохранен в ту же папку, где находится исполняемый модуль защищаемого приложения. Функция сохранения проекта доступна по кнопке [image] на панели инструментов;
  • Сохранить проект как - сохранить файл проекта в файл с новым именем;
  • Закрыть - завершить работу с текущим проектом. Для закрытия текущего проекта также можно нажать кнопку [image] на панели инструментов;
  • Свойства - просмотреть свойства защищаемого приложения. Данная функция также может быть вызвана нажатием кнопки [image] на панели инструментов;

  • Калькулятор - вычислить абсолютные и относительные адреса, смещение и определить секцию. Исходными данными для расчетов является значение одного из параметров.

  • Выход - завершить работу с программой.

Меню "Текст"
  • Вернуть - отменить последние исправления;
  • Повторить - восстановить последние исправления;
  • Вырезать - переместить выделенные данные в буфер обмена;
  • Копировать - скопировать выделенные данные в буфер обмена;
  • Вставить - вставить из буфера обмена;
  • Выделить все - выделить все доступные элементы. Может использоваться, например, для выделения всего дизассемблированного кода приложения.
  • Найти - поиск фрагмента текста

  • Найти дальше - продолжить поиск фрагмента текста;
  • Перейти на адрес - перейти на заданный адрес защищаемого приложения. Возможен переход на введенный адрес либо на адрес объекта, выбранного в списке "Все процедуры". На вкладке "Поиск" возможен поиск объекта по названию;

Меню "Проект"
  • Добавить процедуру - добавить в проект новый объект;

  • Добавить папку - добавить папку в проект:

    Использование папок не влияет на защиту находящихся в них объектов, а служит для систематизации отображения защищаемых объектов в дереве проекта.Перемещать объекты в папку можно простым перетаскиванием (drag-n-drop);

  • Изменить - изменить параметры защищаемого объекта;
  • Удалить - удалить объект из списка защищаемых;
  • Компиляция - компилировать защищаемое приложение с текущими параметрами защиты. Для компиляции проекта также можно нажать кнопку [image] на панели инструментов;
  • Запустить - запустить защищаемое приложение. Исходный/защищенный исполняемый файл приложения также можно запустить нажатием кнопки [image] на панели инструментов.

Меню "Водяные знаки"

Пункт меню "Водяные знаки" служит для вызова диалогового окна управления водяными знаками:

Работа с этим окном рассмотрена в разделе "Водяные знаки".

Меню "Настройка"

  • Язык - выбрать язык пользовательского интерфейса;
  • Режим эксперта - выбрать режим работы "Эксперт";
  • Автосохранение проекта после компиляции - включить/выключить автоматическое сохранение проекта после его компиляции;
  • Интегрировать в оболочку - интегрировать вызов VMProtect в оболочку Windows Explorer.

Меню "Помощь"
  • Содержание - открыть справку VMProtect. Для вызов справки также можно нажать кнопку [image] на панели инструментов;
  • Легенда - просмотреть условные обозначения;
  • Домашняя страница - перейти на домашнюю страницу VMProtect. Для перехода на домашнюю страницу также можно нажать кнопку [image] на панели инструментов;
  • О программе - просмотреть основные сведения о программе VMProtect.


Главное окно

В режиме просмотра по умолчанию главное окно состоит из следующих вкладок:

  • Процедуры для защиты
  • Лицензии
  • Опции

В режиме "Эксперт" главное окно состоит из следующих вкладок:

  • Процедуры для защиты
  • Лицензии
  • DLL Box
  • Скрипт
  • Опции
  • Дамп

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


Вкладка "Процедуры для защиты"

Вкладка "Процедуры для защиты" предназначена для выбора защищаемых объектов. В левой части окна находится дерево проекта:

  • Добавить процедуру - добавить в проект новый объект;
  • Добавить папку - добавить в проект новую папку:

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

  • Изменить - изменить параметры защищаемого объекта;
  • Удалить - удалить объект из списка защищаемых объектов;
  • Включить в компиляцию - включить/исключить выделенный объект из списка защищаемых объектов. Если выделенный объект является папкой, то переключение данного признака будет влиять на все объекты в этой папке;
  • Тип компиляции - установить тип компиляции для выделенного объекта (мутация, виртуализация, ультра). Если выделенный объект является папкой, то переключение данного признака будет влиять на все объекты в этой папке;
  • Переместить вверх - переместить выделенный объект вверх по дереву проекта;
  • Переместить вниз - переместить выделенный объект вниз по дереву проекта;
  • Открыть все папки - открыть все папки в дереве проекта;
  • Закрыть все папки - закрыть все папки в дереве проекта.

В правой части окна отображается содержимое выбранного объекта. Если в дереве проекта выбран защищаемый объект, то с правой стороны отображается дизассемблированное представление этого объекта:

  • Внешний адрес - включить выбранный адрес в список внешних адресов;
  • Окончание процедуры - ограничить список команд, которые будут обработаны в процессе компиляции;
  • Перейти на адрес - переход на заданный адрес защищаемого приложения;
  • Копировать - копировать выделенные данные в буфер обмена;
  • Выделить все - выделить все содержимое;
  • Найти - найти фрагмент текста в выбранном объекте;
  • Найти дальше - продолжить поиск фрагмента текста в выбранном объекте;

Если в дереве проекта выбрана папка, то с правой стороны отображается список объектов, входящих в эту папку:

  • Открыть - перейти к выделенному объекту в дереве проекта;
  • Включить в компиляцию - включить/исключить выделенный объект из списка защищаемых объектов;
  • Тип компиляции - установить тип компиляции для выделенного объекта;
  • Копировать - копировать выделенные данные в буфер обмена;
  • Выделить все - выделить все содержимое;
  • Найти - найти фрагмент текста в названии объектов из данной папки;
  • Найти дальше - продолжить поиск фрагмента текста в названии объектов из данной папки;

Перемещать объекты из папки в папку можно простым перетаскиванием при помощи мыши.

Добавление/изменение защищаемого объекта

Опции

  • Тип компиляции - выбрать тип компиляции объекта. Правильный выбор способа защиты от изучения и взлома для каждого защищаемого объекта (процедуры, функции, фрагмента кода) обеспечит оптимальное соотношение между защищенностью кода и его быстродействием. VMProtect позволяет пользователю установить следующие типы компиляции для каждого защищаемого объекта:
    • Мутация. Исполняемый код видоизменяется на уровне команд процессора - существующие команды видоизменяются, добавляются различные мусорные команды и т.д. Данный тип компиляции относительно слабо защищает обрабатываемый код от взлома и анализа и главным образом препятствует определению обрабатываемых функций с помощью сигнатурных анализаторов (PEiD+KANAL, IDA+FLIRT и т.д.). Этот тип компиляции целесообразно применять при защите библиотечных функций, так как они, как правило, не нуждаются в защите от взлома и анализа и достаточно будет только изменить их сигнатуры, чтобы взломщик не смог автоматически определить, какие именно библиотеки используются в приложении. При использовании мутации степень защиты от взлома и анализа низкая, скорость исполнения кода высокая.
    • Виртуализация. Исполняемый код переводится в байт-код, исполняемый на виртуальной машине. Данный тип компиляции следует применять для всех критичных участков кода, где помимо противодействия взлому и анализу важна скорость работы. При использовании виртуализации степень защиты от взлома и анализа средняя, скорость исполнения кода средняя.
    • Ультра (мутация + виртуализация). Исполняемый код видоизменяется на уровне команд процессора и уже после этого переводится в байт-код, исполняемый на виртуальной машине. Данный тип компиляции следует применять для всех участков кода, где не важна скорость работы. Степень защиты от взлома и анализа высокая, скорость исполнения кода низкая.
  • Использовать опции проекта - при включенном признаке текущий объект будет использовать общие опции защиты. При выключенном признаке для текущего объекта можно установить индивидуальные опции защиты.

Для версии SenseLock Edition доступны дополнительные опции

Привязать к ключу

  • Без привязки к лицензии - при использовании данной опции в процессе исполнения кода текущего объекта будет производиться проверка наличия подключенного ключа SenseLock без использования системы лицензирования;
  • С привязкой к лицензии - при использовании данной опции в процессе исполнения кода текущего объекта будет производиться проверка наличия в ключе SenseLock лицензии с указанным номером и версией.

Все процедуры

На вкладке "Все процедуры" отображается список объектов, доступных для включения в проект:

В режиме "Добавить процедуру" из общего списка объектов можно выбрать сразу несколько объектов, которые будут добавлены в проект с установленными параметрами защиты.

Поиск

С помощью вкладки "Поиск" можно найти необходимые объекты по их имени:

В строке поиска можно использовать стандартные символы подстановки:

  • * - заменяет группу символов произвольной длины;
  • ? - заменяет любой одиночный символ.

В режиме "Добавить процедуру" из списка найденных объектов можно выбрать сразу несколько объектов, которые будут добавлены в проект с установленными параметрами защиты.


Управление лицензиями

Инициализация

По-умолчанию функции лицензирования выключены, чтобы включить их, нужно открыть закладку "лицензии" и создать пару ключей. В случае, если проект ссылается на базу данных менеджера лицензий (в старых версиях VMProtect лицензии управлялись отдельной программой), то будет предложено импортировать эти лицензии в проект. По окончании инициализации будут доступны опции привязки кода к серийному номеру, а также появится возможность создавать и обрабатывать серийные номера.

Интерфейс

Закладка "лицензии" отображает полный список лицензий и параметры выбранного элемента. Над списком располагаются команды создания, удаления и редактирования лицензий:

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

Создание лицензии

Для того, чтобы создать новую лицензию, необходимо кликнуть соответствующую кнопку над списком лицензий или нажать Ctrl+N. Появится окно создания лицензии:

В верхней части окна задаются основные параметры лицензии. В нижней можно выбрать какие из основных параметров попадут в серийный номер, а также добавить дополнительные параметры, например идентификатор оборудования или пользовательские данные. По окончании заполнения полей нажмите ОК и VMProtect создаст новую лицензию.

Черный список и удаление лицензий

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

Импорт серийных номеров

Серийные номера могут быть созданы не только в VMProtect, но и в сторонних программах - генераторах номеров. Обычно они применяются для автоматизированной выдачи серийных номеров регистраторам при продаже лицензий. Чтобы корректно занести такие номера в базу данных, существует функция импорта серийных номеров. Для ее активации необходимо нажать сочетание клавиш Ctrl+I или кликнуть соответствующую кнопку над списком лицензий.

Первым шагом при импорте серийного номера является окно ввода номера:

В случае, если такая лицензия уже есть в базе данных, будет открыто окно редактирования лицензии. Если лицензии в базе нет, то будет открыто окно создания лицензии, где можно внести необходимые правки, после чего, нажатием кнопки ОК, лицензия заносится в базу данных.

Экспорт параметров лицензирования

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

Поле "Формат экспорта" позволяет выбрать один из поддерживаемых форматов, а поле "Результат экспорта" содержит данные в виде, наиболее удобном для соответствующего генератора ключей. Кнопка "Копировать" позволяет скопировать содержимое поля "Результат экспорта" в буфер обмена.


Вкладка "Программирование ключей"

Данный раздел предназначен для программирования пользоватльских ключей и доступен только в SenseLock Edition.


Вкладка "DLL Box"

Вкладка "DLL Box" позволяет включать в защищаемый EXE файл необходимые для его работы DLL, которые в процессе работы защищенного EXE файла будут загружаться из памяти процесса без предварительной записи на диск:

  • Добавить DLL - добавить новую DLL. Доступ к функции добавления новой DLL также возможен с помощью кнопки [image] на панели инструментов;
  • Изменить DLL - изменить свойства выбранной DLL. Доступ к функции изменения свойств DLL также возможен с помощью кнопки [image] на панели инструментов;
  • Удалить DLL - удалить выбранную DLL. Доступ к функции удаления DLL также возможен с помощью кнопки [image] на панели инструментов;

При добавлении/изменении DLL откроется диалоговое окно:

  • Название - название DLL;
  • Имя файла - имя файла, из которого будет загруженна DLL в процессе защиты;
  • Загрузить на старте - определяет будет ли DLL автоматически загружена при запуске защищенного приложения.


Вкладка "Скрипт"

Вкладка "Скрипт" предназначена для создания программ на встроенном скриптовом языке:

Левая панель отображает состав скрипта и предназначена для быстрой навигации по коду скрипта (двойной щелчок по объекту скрипта). Правая панель предназначена для редактирования самого скрипта:

  • Вырезать - переместить выделенный текст в буфер обмена. Данная функция также может быть вызвана нажатием на кнопку [image] на панели инструментов;
  • Копировать - копировать выделенный текст в буфер обмена. Данная функция также может быть вызвана нажатием на кнопку [image] на панели инструментов;
  • Вставить - вставить текст из буфера обмена. Данная функция также может быть вызвана нажатием на кнопку [image] на панели инструментов;
  • Выделить все - выделить весь текст;
  • Вернуть - отменить последние исправления. Данная функция также может быть вызвана нажатием на кнопку [image] на панели инструментов;
  • Повторить - восстановить последние исправления. Данная функция также может быть вызвана нажатием на кнопку [image] на панели инструментов;
  • Найти - найти фрагмент текста. Данная функция также может быть вызвана нажатием на кнопку [image] на панели инструментов;
  • Найти дальше - продолжить поиск фрагмента текста.


Вкладка "Опции"

Вкладка "Опции" предназначена для установки опций защиты:

Файл

  • Защита памяти - данная опция позволяет защитить образ файла в памяти от каких-либо изменений (целостность данных проверяется по всем секциям, не имеющим атрибут WRITABLE). Проверка целостности образа происходит до передачи управления оригинальной точке входа в программу. В случае нарушения целостности будет показано соответствующее сообщение с полным прекращением дальнейшего выполнения программы.
  • Защита импорта - данная опция позволяет спрятать от взломщика список API, которые использует защищенная программа (рекомендуется использовать совместно с упаковкой выходного файла).
  • Защита ресурсов - данная опция позволяет зашифровать ресурсы программы (кроме иконок, манифеста и других служебных ресурсов).
  • Упаковать выходной файл - данная опция позволяет упаковать защищенный файл, что уменьшает его размер. Распаковка происходит автоматически при запуске защищенного файла. Весь процесс распаковки происходит в оперативной памяти без какой-либо записи данных на диск. Доступно два режима упаковки:
    • Быстрый - режим имеет минимальную степень сжатия и максимальную скорость распаковки;
    • Лучший - режим имеет максимальную степень сжатия и минимальную скорость распаковки.

    При использовании данной опции рекомендуется включить EntryPoint в список защищаемых объектов. Адрес EntryPoint отображается в строке состояния главного окна:

    Важно:
    При старте программы после распаковки кода управление будет передано EntryPoint. Если код EntryPoint будет завиртуализирован, то этот код будет выполнен на том же исполнителе ВМ, что и код самого распаковщика. Совместная виртуализация EntryPoint с упаковкой защищенного файла препятствует "ручной" распаковке защищенного файла, т.к. взломщику надо будет восстановить код EntryPoint для получения работоспособного образа файла.

Компиляция

  • Виртуальные Машины - Каждая ВМ будет иметь свой уникальный набор свойств. При виртуализации различных участков кода будут использованы различные ВМ из указанного числа. Данная опция затрудняет анализ и декомпиляцию байт-кода, т.к. взломщику придется проанализировать все исполнители ВМ, используемые в защищенном файле.
  • Режим отладки (определение внешних адресов) - при защите кода место, освободившееся в процессе компиляции, VMProtect использует для своих нужд, в том числе для хранения различных данных (завиртуализированного, промутированного кода, исполнителей ВМ и т.п.). В связи с этим могут возникать различные ситуации, когда "внутрь" защищенного кода может происходить переход из других частей программы (адреса, на которые может происходить переход, называются внешними). Подобные ситуации могут возникать при использовании маркеров кода, когда происходит переход из незащищенного участка программы внутрь маркера:
    VMProtectBegin(nil);
for  I:=0 to 10 do
begin
  Inc(J);
  VMProtectEnd;
end;

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

    ----------------- начало маркера ----------------
0044D12C 	mov eax, 0000000B
0044D131 	inc ebx
----------------- конец маркера -----------------
0044D132 	dec eax
0044D133 	jnz 0044D131

    Очевидно, что после виртуализации команд с адресами 0044D12C и 0044D131 при выполнении незавиртуализированной команды условного перехода по адресу 0044D133 возникнет ошибка (т.к. по адресу 0044D131 вместо оригинального кода будут записаны различные данные или просто мусор). Для рассматриваемого примера адрес 0044D131 будет являться внешним.

    При включении опции "Режим отладки" на место оригинального кода будет записана команда INT 03, являющаяся обычной точкой останова и служебной командой при работе под отладчиком. При запуске защищенной программы из-под отладчика в случае передачи управления по адресу 0044D131 произойдет вызов точки останова, а при открытии окна отладчика можно будет увидеть, по какому адресу произошел данный переход.

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

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

Обнаружение

  • Обнаружение отладчика - данная опция препятствует отладке защищенного файла. Существуют 2 типа отладчиков: User-mode (отладчики пользовательского режима: OllyDBG, WinDBG и т.п.) и Kernel-mode (отладчики режима ядра: SoftICE, Syser и т.п.). Обнаружение отладчика происходит до передачи управления оригинальной точке входа в программу. В случае обнаружения отладчика будет показано соответствующее сообщение с прекращением дальнейшего выполнения программы.
  • Обнаружение инструментов виртуализации - данная опция препятствует запуску защищенного файла в различных виртуальных средах: VMware, Virtual PC, VirtualBox, Sandboxie. Обнаружение виртуальной среды происходит до передачи управления оригинальной точке входа в программу. В случае обнаружения виртуальной среды будет показано соответствующее сообщение с прекращением дальнейшего выполнения программы.

Секции

  • Название секций ВМ - при компиляции в файл будут добавлены дополнительные секции, в которые будут записаны различные данные (завиртуализированный, промутированный код, исполнители ВМ, водяные знаки и т.п.). Данная опция позволяет задать названия для новых секций. Рекомендуется изменять стандартное название секций ".vmp" (например, на ".UPX").
  • Удалять настраиваемые элементы - некоторые компиляторы (в частности Delphi) создают для EXE файлов список настраиваемых элементов, которые не используются операционной системой при загрузке EXE файлов. При включенной опции область, занятая под список настраиваемых элементов, будет использоваться под нужды ВМ.

Водяные знаки

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

Параметры лицензирования

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

Дополнительные опции для версии SenseLock Edition

Процедуры используют единые опции лицензирования

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

  • Без привязки к лицензии - при использовании данной опции в процессе исполнения кода объектов, имеющих признак "Привязать к ключу", будет производиться проверка наличия подключенного ключа SenseLock без использования системы лицензирования;
  • С привязкой к лицензии - при использовании данной опции в процессе исполнения кода объектов, имеющих признак "Привязать к ключу", будет производиться проверка наличия в ключе SenseLock лицензии с указанным номером и версией.

SenseLock

  • Пароль к мастер ключу - в данном поле необходимо указать пароль, используемый VMProtect для доступа к мастер-ключу, содержащему модуль защиты программного обеспечения. Мастер-ключ входит в комплект поставки VMProtect SenceLock Edition и используется только разработчиком программного продукта;
  • PIN код пользователя - в данном поле необходимо указать пароль на доступ защищенного приложения к пользовательскому ключу, который поставляется конечному пользователю вместе с защищаемым приложением. Пользовательские ключи не входят в комплект поставки VMProtect SenceLock Edition;
  • Идентификатор ключа - данный параметр может содержать идентификатор ключа, к которому привязана конкретная копия защищаемого приложения. Для автоматического ввода идентификатора подсоедините необходимый пользовательский ключ к компьютеру и нажмите кнопку [image].


Вкладка "Дамп"

Вкладка "Дамп" предназначена для просмотра дампа памяти защищаемого приложения в виде машинных кодов и ассемблерных команд:

  • Перейти на адрес - перейти на заданный адрес защищаемого приложения;
  • Копировать - копировать выделенные данные в буфер обмена;
  • Выделить все - выделить все содержимое.


Водяные знаки

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

Диалоговое окно "Водяные знаки" содержит две вкладки:

  • Настройка
  • Поиск


Вкладка "Настройка"

Вкладка "Настройка" служит для управления водяными знаками:

  • Добавить - добавить новый водяной знак. Доступ к функции добавления нового водяного знака также возможен с помощью кнопки [image] на панели инструментов;
  • Изменить - изменить свойства выбранного водяного знака. Доступ к функции изменения свойств водяного знака также возможен с помощью кнопки [image] на панели инструментов;
  • Удалить - удалить выбранный водяной знак. Доступ к функции удаления водяного знака также возможен с помощью кнопки [image] на панели инструментов;
  • Доступен для использования - определяет возможность использованияводяного знака в защищаемых файлах.

При добавлении/изменении водяного знака откроется диалоговое окно:

  • Название - название водяного знака;
  • Значение - значение водяного знака. При помощи кнопки "Сгенерировать" можно задать случайное значение водяного знака. Поле "Значение" состоит из двух столбцов: в левом столбце можно ввести значение в Hex режиме, в правом столбце - в виде текста. При вставке водяного знака в защищаемый файл позиции, имеющие символ "?", будут иметь случайные значения.


Вкладка "Поиск"

Вкладка "Поиск" служит для поиска водяных знаков в исполняемом файле или определенном процессе защищенного приложения:

  • Поиск в файле - искать в исполняемом файле приложения. В поле "Имя файла" выберите файл, в котором необходимо произвести поиск водяных знаков;
  • Поиск в процессе - искать в модуле выбранного процесса. После выбора в выпадающем списке "Процесс" нужного процесса в выпадающем списке "Модуль" выберите модуль этого процесса, в котором необходимо произвести поиск водяных знаков.

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

  • Название - название найденного водяного знака;
  • Количество - количество копий найденного водяного знака.

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


Консольная версия

После создания проекта в GUI режиме в дальнейшем может использоваться консольная версия (VMProtect_Con.exe), вызов которой осуществляется следующим образом:

VMProtect_Con Файл [Выходной файл] [-pf Файл проекта] [-sf Файл скрипта] [-wm Название водяного знака] [-we]

  • Файл - имя исполняемого файла, который необходимо защитить (*.exe, *.dll и т.д.), или имя файла проекта (*.vmp). Если в качестве исходного файла указан файл проекта, то имя исполняемого файла будет браться из файла проекта.
  • Выходной файл - имя и путь к защищенному файлу, который должен быть создан после обработки исходного файла. Если параметр не задан, его значение берется из файла проекта.
  • Файл проекта - имя и путь к файлу проекта, созданного в GUI режиме. Если параметр не задан, поиск файла проекта *.vmp будет осуществляться в папке с исполняемым файлом.
  • Файл скрипта - имя файла, содержащего скрипт, с помощью которого будет обработан защищаемый файл. Если параметр не задан, поиск файла скрипта *.vms будет осуществляться в папке с исполняемым файлом.
  • Название водяного знака - название водяного знака, который будет вставлен в защищаемый файл. Если название водяного файла не указано, в защищенный файл будет вставлен водяной знак, который был указан при создании проекта.
  • we - при использовании данного параметра все предупреждения будут отображаться как ошибки.


Ответы на вопросы

Приобретение VMProtect

Приобретая VMProtect, смогу ли я использовать его на разных компьютерах?
Вы можете установить и использовать VMProtect на любом количестве компьютеров, которые необходимы вам для работы.

В чем заключается разница между Personal и Company лицензиями и какую лицензию мне нужно выбрать при покупке?
Эти лицензии отличаются только типом владельца. Если Вы являетесь частным разработчиком, то выберите Personal лицензию. Если Вы являетесь руководителем организации или планируете использовать продукт в своей организации, то выберите Company лицензию.

Каким образом осуществляется доставка полной версии VMProtect после покупки?
Доставка полной версии (и всех последующих обновлений VMProtect, входящих в ежегодную подписку) осуществляется с помощью электронной почты. Поэтому перед покупкой VMProtect рекомендуется отключить все спам фильтры и ограничения на размер входящих писем (либо увеличить лимит до 15 МБ) в настройках своего почтового ящика.

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

Возможно ли приобрести VMProtect с помощью кредитной карты или PayPal?
Вы можете приобрести VMProtect с помощью кредитной карты или PayPal. Для этого необходимо на странице покупки в качестве способа оплаты выбрать Plimus и на следуюшей странице (после нажатия кнопки "Отправить запрос") в качестве метода оплаты выбрать кредитную карточку или PayPal.

Общие вопросы

Есть ли возможность автоматически зашифровать строки и массивы данных?
В VMProtect существует возможность скрывать ANSI константы и Unicode константы. Все остальные данные, с которыми работает обрабатываемый код, остаются в оригинальном виде. Рекомендуется всю конфиденциальную информацию хранить в зашифрованном виде и расшифровывать непосредственно перед использованием. Сам расшифровщик можно завиртуализировать.

Есть ли возможность защищать процедуры, которые вызываются из разных потоков моего приложения?
VMProtect полностью поддерживает многопоточные приложения и для защиты этих процедур не существует никаких дополнительных ограничений.

Возможно ли совместное использование VMProtect с другим протектором (упаковщиком)?
Использование каких-либо упаковщиков (протекторов) после защиты файла с помощью VMProtect может привести к неработоспособности защищенного приложения.

Необходимо ли включать VMProtectSDK32.dll/VMProtectSDK64.dll в дистрибутив программы?
Данные библиотеки используются только на этапе отладки программы (до её защиты). После защиты программы с помощью VMProtect вся информация об использовании этих DLL полностью удаляется и поэтому нет необходимости включать данные библиотеки в дистрибутив программы.

Сообщения компилятора

Что означает данная ошибка: VMProtectMarker "ToolButton1Click.1".0044327D: Адрес используется процедурой "TForm1.ToolButton1Click"?
Данная ошибка означает, что одна и таже команда с адресом 0044327D используется в двух процедурах, которые в данный момент включены в список защищаемых объектов. Для решения этой проблемы необходимо исключить одну из процедур (в данном случае это VMProtectMarker "ToolButton1Click.1" и TForm1.ToolButton1Click) из списка защищаемых объектов.

Что означает данная ошибка: [Ошибка] TForm1.Test.004433F4: Минимальный размер процедуры для компиляции 5 байт?
Данная ошибка означает, что процедура TForm1.Test слишком мала и не может быть защищена. Для решения этой проблемы необходимо исключить эту процедуру из списка защищаемых объектов.

Что означает данное предупреждение: TForm1.ToolButton1Click.00443368: Переход на внутренний адрес: 00443337?
Данное предупреждение означает, что возможен переход из незащищенного кода внутрь защищенного (этот переход может привести к неработоспособности защищенной программы). Для решения этой проблемы необходимо пометить адрес 00443337 как внешний.

Что означает данная ошибка: Секция ".text" находится в области создания новой секции?
Данная ошибка обычно возникает при защите драйверов и означает, что свободное место между первой секций файла и служебной информацией, расположенной в заголовке файла, слишком мало для создания новой секции. Для решения этой проблемы необходимо увеличить величину выравнивания для секций в исходных текстах драйвера и полностью его пересобрать (например: если драйвер собирается с помощью WDK/DDK, то необходимо добавить строку DRIVER_ALIGNMENT=0x200 в файл SOURCES).

При использовании демоверсии VMProtect SenseLock Edition возникает ошибка "Master key not found". Что она означает?
Для работы VMProtect SenseLock Edition необходим мастер ключ, который представляет из себя SenseLock EL со специальной прошивкой. По всем вопросам приобретения и доставки мастер ключа обращайтесь по адресу senselock@vmpsoft.com.


Система лицензирования

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

  • Возможности системы лицензирования
  • Схема работы системы лицензирования
  • Управление лицензиями: создание, изменение, блокировка итп
  • Пример интеграции в приложение
  • Автоматическое создание серийных номеров

    • © 2006-2012 Copyright VMProtect Software


Возможности системы лицензирования VMProtect

Надежные серийные номера

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

Привязка кода к серийному номеру

VMProtect позволяет исполнять часть кода программы на виртуальной машине. Набор команд виртуальной машины меняется при каждой сборке защищаемой программы. Система лицензирования позволяет зашифровать часть команд виртуальной машины ключом, который хранится в серийном номере. Таким образом, даже если хакер исправит условный переход в программе, код все равно не будет работать без корректного серийного номера. А так как расшифровка кода также происходит под управлением виртуальной машины, то даже наличие серийного номера не дает возможности легко проанализировать алгоритм расшифровки.

Ограничение периода бесплатных обновлений

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

Срок годности ключа

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

Ограничение времени работы программы

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

Привязка к оборудованию

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

Черный список

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

Хранение данных

Система лицензирования позволяет хранить в серийном номере и выдавать защищаемой программе следующие данные: имя пользователя, e-mail, а также до 255 байт произвольных данных (т.н. "данные пользователя"). Это может быть использовано для показа в диалоге "О Программе", для реализации дополнительных проверок серийного номера, для хранения констант, доступных только в зарегистрированной версии продукта итп.

Ограниченние работы демо-версии по времени

Используя систему активации производитель ПО может автоматически создавать серийные номера ограниченные по времени и привязанные к оборудованию пользователя. Это позволяет организовать надежно защищенный демо-период для приложения, так как VMProtect не прячет триальные метки на компьютере пользователя, а вместо этого генерирует полноценный, но ограниченный по времени серийный номер. Активация производится через интернет, однако API активации позволяет проводить ее и при отсутствии доступа в интернет с данного конкретного компьютера.

Чего система лицензирования не умеет и почему?

100%-но надежная привязка к оборудованию

Несмотря на то, что система лицензирования позволяет привязаться к идентификатору оборудования, следует понимать, что большинство данных об оборудовании получается средствами операционной системы, который могут быть перехвачены хакером с целью изменения данных. Система лицензирования применяет ряд схем, позволяющих минимизировать такие риски, однако если вам требуется 100%-но надежная привязка к оборудованию, мы рекомендуем воспользоваться решением на базе USB-ключей, с которыми также может работать VMProtect.


Как работает система лицензирования?

Защита приложения

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

Создание серийных номеров

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

Проверка серийного номера в программе

Модуль лицензирования предоставляет защищаемой программе несколько функций для работы с серийными номерами. Программа передает модулю лицензирования серийный номер и запрашивает информацию о нем. Модуль лицензирования выдает краткое состояние серийного номера (рабочий/нерабочий и почему), а также может выдать подробную информацию о серийном номере, такую как имя пользователя, e-mail, срок действия номера итд. Защищаемая программа анализирует информацию о серийном номере и принимает решение о продолжении или завершении работы, а также об ограничениях функционала.


Интеграция в приложение

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

Режимы работы системы лицензирования

В создании защиты всегда есть два этапа: разработка и использование. В случае с лицензированием, вы сначала создаете приложение, потом интегрируете механизмы защиты, добавляете проверки и блокируете функции. И только потом, после всестороннего тестирования, продукт передается пользователям и начинается второй этап. Тестирование защищаемого приложения - процесс сложный, необходимо убедиться в работоспособности всех проверок и условных переходов. Создание "настоящих" серийных номеров на все случаи жизни на этапе тестирования может оказаться неудобным, поэтому у системы лицензирования есть т.н. "режим разработки" или "тестовый" режим, при котором никакой защиты не осуществляется, а реакция системы на серийные номера описывается в конфигурационном файле. Когда приложение отлажено и корректно работает с системой лицензирования, VMProtect заменяет "тестовый" модуль лицензирования на реальный, проверяющий реальные серийные номера. Это происходит в момент защиты приложения, поэтому пропустить этот этап по ошибке не получится.

Этап 1: Тестовый режим работы

В тестовом режиме реакция системы лицензирования на серийные номера, все возвращаемые статусы и данные описываются в конфигурационном файле. Файл называется VMPLicense.ini и должен располагаться в рабочем каталоге приложения. В 10 шагах, представленных ниже, мы пройдем от создания простейшего приложения до полноценного использования системы лицензирования в тестовом режиме с привязкой к оборудованию и ограничением срока бесплатных обновлений.

  • Шаг 1: Создаем защищаемое приложение
  • Шаг 2: Добавляем код проверки лицензии
  • Шаг 3: Получаем флаги состояния номера
  • Шаг 4: Получаем имя и e-mail пользователя
  • Шаг 5: Проверяем срок действия номера
  • Шаг 6: Ограничиваем время работы программы
  • Шаг 7: Ограничиваем бесплатные обновления
  • Шаг 8: Номера из "черного списка"
  • Шаг 9: Привязка к оборудованию
  • Шаг 10: Пользовательские данные
Этап 2: Реальный режим работы

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

  • Шаг 1: Создаем новое защищаемое приложение
  • Шаг 2: Создаем проект защиты в VMProtect
  • Шаг 3: Первый запуск защищенного продукта
  • Шаг 4: Тестируем результат
  • Шаг 5: Привязываем код к серийному номеру
Дополнительная информация

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


Шаг 1: Создаем защищаемое приложение

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

#include <windows.h>
#include <stdio.h>

bool is_registered(const char *serial)
{
    return serial && serial[0] == 'X';
}

int main(int argc, char **argv)
{
    char *serial = "Xserialnumber"; // зададим номер непосредственно в коде для простоты
    if (!is_registered(serial))
    {
        printf("please register!\n");
        return 0;
    }
    printf("We are registered.\n");
    return 0;
}

Программа реализует очень простую схему проверки серийного номера. Функция is_registered() сравнивает первую букву номера с 'Х' и считает номер корректным в случае совпадения. Для некорректного номера выдается просьба о регистрации, а в случае правильного номера выводится сообщение "We are registered."

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


Шаг 2: Добавляем код проверки лицензии

Подключаем VMProtect SDK

Если вы этого еще не сделали, то сейчас самое время подключить к проекту VMProtect SDK. SDK это три файла: заголовочный файл (VMProtectSDK.h), файл библиотеки (VMProtectSDK32.lib) и dll-файл с реализацией (VMProtectSDK32.dll). Для 64-битной платформы есть отдельные реализации библиотеки и dll-файла.

Помещаем dll-файл в рабочий каталог нашего приложения, кладем файл заголовков и библиотечный файл рядом с исходниками, включаем файл заголовков в основной файл:

#include <windows.h>
#include <stdio.h>
#include "VMProtectSDK.h"

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

Подаем серийный номер в систему лицензирования

Теперь непосредственно после строки с серийным номером добавим вызов функции SDK системы лицензирования:

char *serial = "Xserialnumber"; // зададим номер непосредственно в коде для простоты
int res = VMProtectSetSerialNumber(serial);
printf("res = 0x%08X\n", res);

Если после этого программа будет завершаться с ошибкой об отсутствии dll-файла, убедитесь, что вы положили соответствующий DLL-файл в рабочий каталог нашего приложения. В случае успешного запуска, вы должны увидеть сообщение:

res = 0x00000002

Число 2 соответствует флагу SERIAL_STATE_FLAG_INVALID, описанному в API. Т.е. система лицензирования посчитала наш ключ некорректным, что вполне логично, т.к. мы до сих пор не "объяснили" системе лицензирования какие ключи считать правильными, а какие - нет.

Задаем "правильный" серийный номер

В тестовом режиме система лицензирования анализирует файл VMProtectLicense.ini, и реагирует на вызовы функций согласно указанным там настройкам. Подробно файл разбирается в следующих шагах, а здесь мы просто создадим такой файл и поместим туда следующий текст:

[TestLicense]
AcceptedSerialNumber=Xserialnumber

Запустим нашу программу еще раз. Если вы снова получили код ошибки "2", то убедитесь, что ini-файл находится в рабочем каталоге приложения. В этот раз вы должны получить результат "0". Признак того, что система лицензирования приняла и одобрила серийный номер. Теперь мы можем убрать функцию is_registered() - проверкой серийного номера займется система лицензирования:

#include <windows.h>
#include <stdio.h>
#include "VMProtectSDK.h"

int main(int argc, char **argv)
{
    char *serial = "Xserialnumber"; // зададим номер непосредственно в коде для простоты

    int res = VMProtectSetSerialNumber(serial);
    printf("res = 0x%08X\n", res);

    if (res)
    {
        printf("please register!\n");
        return 0;
    }
    printf("We are registered.\n");
    return 0;
}


Шаг 3: Получаем флаги состояния серийного номера

Удобная функция для печати флагов

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

#define PRINT_HELPER(state, flag) if (state & flag) printf("%s ", #flag)
void print_state(INT state)
{
    if (state == 0)
    {
        printf("state = 0\n");
        return;
    }

    printf("state = ");
    PRINT_HELPER(state, SERIAL_STATE_FLAG_CORRUPTED);
    PRINT_HELPER(state, SERIAL_STATE_FLAG_INVALID);
    PRINT_HELPER(state, SERIAL_STATE_FLAG_BLACKLISTED);
    PRINT_HELPER(state, SERIAL_STATE_FLAG_DATE_EXPIRED);
    PRINT_HELPER(state, SERIAL_STATE_FLAG_RUNNING_TIME_OVER);
    PRINT_HELPER(state, SERIAL_STATE_FLAG_BAD_HWID);
    PRINT_HELPER(state, SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED);
    printf("\n");
}

Несмотря на свой размер, функция очень простая - она последовательно проверяет все битовые флаги и печатает те, что присутствуют в переменной состояния. Заменим printf после проверки номера на вызов print_state, заодно временно поправим серийный номер, который подается в систему лицензирования:

char *serial = "Xserialnumber1"; // зададим номер непосредственно в коде для простоты
int res = VMProtectSetSerialNumber(serial);
print_state(res);

Если запустить эту программу, то на консоль выведется следующее:

state = SERIAL_STATE_FLAG_INVALID
please register!

Вернем теперь старый серийный номер, убрав "1", и запустим программу еще раз:

state = 0
We are registered.

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

Получение состояния номера

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


Шаг 4: Получаем имя и e-mail пользователя

Начнем с простого: получим из серийного номера имя и e-mail пользователя (часто показывается в окне About). Для этого нам придется прописать их в ini-файл. Добавим еще две строчки к нашим двум:

[TestLicense]
AcceptedSerialNumber=Xserialnumber
UserName=John Doe
EMail=john@doe.com

А в самой программе, в случае успешной регистрации, добавим получение этих данных и вывод их на экран:

VMProtectSerialNumberData sd = {0};
VMProtectGetSerialNumberData(&sd, sizeof(sd));
printf("name = %ls,\ne-mail = %ls\n", sd.wUserName, sd.wEMail);

Структура содержит данные в UNICODE, поэтому в printf() использованы маркеры %ls вместо %s. Программа должна вывести на экран текст:

state = 0
We are registered.
name = John Doe,
e-mail = john@doe.com


Шаг 5: Проверяем срок действия серийного номера

Поместите в ini-файл строчку вида ExpDate=YYYYMMDD, например такую:

ExpDate=20000101

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

state = SERIAL_STATE_FLAG_DATE_EXPIRED
please register!

Добавим получение дополнительной информации перед выходом с сообщением "please register":

if (res)
{
    VMProtectSerialNumberData sd = {0};
    VMProtectGetSerialNumberData(&sd, sizeof(sd));
    printf("exp. date: y = %d, m = %d, d = %d\n", sd.dtExpire.wYear, sd.dtExpire.bMonth, sd.dtExpire.bDay);
    printf("please register!\n");
    return 0;
}

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

state = SERIAL_STATE_FLAG_DATE_EXPIRED
exp. date: y = 2000, m = 1, d = 1
please register!

Удалите из ini-файла строку ExpDate=..., чтобы она не мешала нам в дальнейшем.


Шаг 6: Ограничиваем время работы программы

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

TimeLimit=1

Модифицируем программу следующим образом:

int main(int argc, char **argv)
{
    char *serial = "Xserialnumber"; // зададим номер непосредственно в коде для простоты

    int res = VMProtectSetSerialNumber(serial);
    print_state(res);
    if (res) return 0;

    VMProtectSerialNumberData sd = {0};
    VMProtectGetSerialNumberData(&sd, sizeof(sd));
    printf("I will run for %d minute(s)\n", sd.bRunningTime);
    print_state(VMProtectGetSerialNumberState());
    Sleep(60 * 1000 * sd.bRunningTime);
    printf("After %d minute(s):\n", sd.bRunningTime);
    print_state(VMProtectGetSerialNumberState());

    return 0;
}

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

state = 0
I will run for 1 minute(s)
state = 0
After 1 minute(s):
state = SERIAL_STATE_FLAG_RUNNING_TIME_OVER

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


Шаг 7: Ограничиваем бесплатные обновления

Схема работы

При защите приложения VMProtect запоминает дату, когда это происходило. Эта дата является для системы лицензирования датой сборки приложения. В серийном номере можно прописать максимальную дату сборки, для которой подойдет этот номер. Таким образом, если при создании ключа вы пропишете в него текущую дату + 1 год, то ключ подойдет ко всем релизам вашей программы, которые вы выпустите в течение года. К релизу, выпущенному через год и 1 день ключ уже не подойдет и у пользователя будет выбор: пользоваться более старой версией или купить новый ключ, который будет работать еще год.

Пробуем на деле

Поместите в ini-файл строчку вида MaxBuildDate=YYYYMMDD, например такую:

MaxBuildDate=20000101

В тестовом режиме система лицензирования считает датой сборки сегодняшнее число, поэтому важно, чтобы дата, указанная в строке, уже прошла. Т.е. максимум - вчера. Модифицируйте исходный код функции main(), чтобы он выглядел так:

int main(int argc, char **argv)
{
    char *serial = "Xserialnumber"; // зададим номер непосредственно в коде для простоты

    int res = VMProtectSetSerialNumber(serial);
    print_state(res);

    if (res)
    {
        VMProtectSerialNumberData sd = {0};
        VMProtectGetSerialNumberData(&sd, sizeof(sd));
        printf("max. build date: y = %d, m = %d, d = %d\n", sd.dtMaxBuild.wYear, sd.dtMaxBuild.bMonth, sd.dtMaxBuild.bDay);
        printf("please register!\n");
        return 0;
    }

    printf("I'm registered\n");

    return 0;
}

Тогда при запуске программы вы увидите следующее:

state = SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED
max. build date: y = 2000, m = 1, d = 1
please register!

Заменив в ini-файле дату на сегодняшнюю или завтрашнюю, мы получим "рабочую" программу:

state = 0
I'm registered

Удалите из ini-файла строку MaxBuildDate=..., чтобы она не мешала нам в дальнейшем.


Шаг 8: Номера из "черного списка"

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

Сократим до минимума код функции main():

int main(int argc, char **argv)
{
    char *serial = "Xserialnumber"; // зададим номер непосредственно в коде для простоты

    int res = VMProtectSetSerialNumber(serial);
    print_state(res);

    return 0;
}

Запустим программу и убедимся, что система лицензирования принимает наш серийный номер:

state = 0

Теперь занесем наш номер в черный список с точки зрения системы лицензирования. Для этого в ini-файл добавим строку:

BlackListedSerialNumber=Xserialnumber

И снова запускаем программу:

state = SERIAL_STATE_FLAG_BLACKLISTED

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


Шаг 9: Привязка к оборудованию

Получение идентификатора оборудования

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

int main(int argc, char **argv)
{
    int nSize = VMProtectGetCurrentHWID(NULL, 0);
    char *buf = new char[nSize];
    VMProtectGetCurrentHWID(buf, nSize);
    printf("HWID: %s\n", buf);
    delete [] buf;
    return 0;
}

Запустив эту программу, мы получим тестовый идентификатор оборудования, используемый по умолчанию:

HWID: myhwid

Чтобы изменить идентификатор, используемый по умолчанию, добавьте в ini-файл строку:

MyHWID=test

Запустив программу после этого, мы увидим, что система лицензирования теперь считает "test" идентификатором оборудования:

HWID: test

Внимание! реальный идентификатор оборудования программа будет выдавать только после ее обработки в VMProtect.

Серийный номер с привязкой к оборудованию

Чтобы наш тестовый серийный номер был привязан к оборудованию, добавим в ini-файл еще одну строчку, на этот раз с идентификатором, "записанным" в номер:

KeyHWID=test

А функцию main() слегка усложним, она снова будет подавать серийный номер и анализировать получаемый результат:

int main(int argc, char **argv)
{
    int nSize = VMProtectGetCurrentHWID(NULL, 0);
    char *buf = new char[nSize];
    VMProtectGetCurrentHWID(buf, nSize);
    printf("HWID: %s\n", buf);
    delete [] buf;

    char *serial = "Xserialnumber";
    int res = VMProtectSetSerialNumber(serial);
    print_state(res);
    return 0;
}

Запустив код, получим следующий результат:

HWID: test
state = 0

Система лицензирования сравнила текущий идентификатор оборудования и тот, который был запиан в серийном номере. Идентификаторы совпали и функция VMProtectSetSerialNumber() вернула 0 - серийный номер подходит.

Попробуем теперь "запустить" нашу программу на другом оборудовании. Для этого поменяем значение "test" параметра MyHWID в ini-файле на "new test". И снова запустим программу:

HWID: new test
state = SERIAL_STATE_FLAG_BAD_HWID

На этот раз система лицензирования вернула флаг SERIAL_STATE_FLAG_BAD_HWID, означающий несовпадение реально идентификатора оборудования с тем, что хранится в серийном номере. Текущий идентификатор оборудования, выведенный на экран - "new test", а в серийном номере хранится "test". Поменяв в ini-файле значение параметра KeyHWID на "new test" можно добиться работы серийного номера и на этом "оборудовании".


Шаг 10: Пользовательские данные

Серийный номер может содержать до 255 байт произвольных данных, которые система лицензирования без изменений передает вызывающей программе. Это может быть какая-то дополнительная информация о продаже, данные, без которых не будет работать полная версия, или еще чего-нибудь. Изменим функцию main(), чтобы она считывала данные из серийного номера и показывала их на экране:

int main(int argc, char **argv)
{
    char *serial = "Xserialnumber";
    int res = VMProtectSetSerialNumber(serial);
    print_state(res);
    if (res) return 0;

    VMProtectSerialNumberData sd = {0};
    VMProtectGetSerialNumberData(&sd, sizeof(sd));
    printf("Serial number has %d byte(s) of data\n", sd.nUserDataLength);
    for (int i = 0; i < sd.nUserDataLength; i++)
        printf("%02X ", sd.bUserData[i]);
    printf("\n");
    return 0;
}

Ini-файл при этом сократим до такого вида:

[TestLicense]
AcceptedSerialNumber=Xserialnumber

Теперь запустим программу и убедимся, что наш серийный номер подходит, но не содержит никаких данных:

state = 0
Serial number has 0 byte(s) of data

Чтобы добавить пользовательские данные в серийный номер, необходимо создать в ini-файле переменную UserData и присвоить ей строчку из HEX-символов. Символы должны идти парами, т.е. длина строки должна быть кратна 2. Строка может быть, например, такой:

UserData=010203A0B0C0D0E0

В этом случае при запуске программы мы получим следующий результат:

state = 0
Serial number has 8 byte(s) of data
01 02 03 A0 B0 C0 D0 E0


Шаг 1: Создаем новое защищенное приложение

В первом этапе мы создавали несколько разных тестовых приложений для изучения API системы лицензирования, в этом этапе приложение будет одно. Оно также будет консольным и в нем будет функция foo(), которая может работать только в зарегистрированной версии. Вот код нашего тестового приложения:

#include <windows.h>
#include <stdio.h>
#include "VMProtectSDK.h"

#define PRINT_HELPER(state, flag) if (state & flag) printf("%s ", #flag)
void print_state(INT state)
{
    if (state == 0)
    {
        printf("state = 0\n");
        return;
    }

    printf("state = ");
    PRINT_HELPER(state, SERIAL_STATE_FLAG_CORRUPTED);
    PRINT_HELPER(state, SERIAL_STATE_FLAG_INVALID);
    PRINT_HELPER(state, SERIAL_STATE_FLAG_BLACKLISTED);
    PRINT_HELPER(state, SERIAL_STATE_FLAG_DATE_EXPIRED);
    PRINT_HELPER(state, SERIAL_STATE_FLAG_RUNNING_TIME_OVER);
    PRINT_HELPER(state, SERIAL_STATE_FLAG_BAD_HWID);
    PRINT_HELPER(state, SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED);
    printf("\n");
}

char *read_serial(const char *fname)
{
    FILE *f;
    if (0 != fopen_s(&f, fname, "rb")) return NULL;
    fseek(f, 0, SEEK_END);
    int s = ftell(f);
    fseek(f, 0, SEEK_SET);
    char *buf = new char[s + 1];
    fread(buf, s, 1, f);
    buf[s] = 0;
    fclose(f);
    return buf;
}

// метод foo() очень короткий, но он нам нужен в виде отдельной функции
// поэтому мы попросим компилятор не делать его inline
__declspec(noinline) void foo()
{
    printf("I'm foo!\n");
}

int main(int argc, char **argv)
{
    char *serial = read_serial("serial.txt");
    int res = VMProtectSetSerialNumber(serial);
    delete [] serial;
    if (res)
    {
        printf("serial number is bad\n");
        print_state(res);
        return 0;
    }
    printf("serial number is correct, calling foo()\n");
    foo();
    printf("done\n");
    return 0;
}

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

serial number is bad
state = SERIAL_STATE_FLAG_INVALID

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


Шаг 2: Создаем проект защиты в VMProtect

Теперь, когда тестовая программа готова, скомпилирована и рядом с ней находится ее MAP-файл, мы можем запустить VMProtect Ultimate и открыть исполняемый файл. Нам нужно будет добавить в проект две функции: _main (так Visual Studio переименовала нашу функцию main()) и foo(). Обе функции видны в списке функций в VMProtect, так что у вас должно получиться такое дерево проекта:

Далее необходимо инициализировать систему лицензирования. Перейдите на вкладку "Лицензии" и создайте пару кючей длиной 2048 бит.


Шаг 3: Первый запуск защищенного продукта

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

C:\test>dummy_app.vmp.exe
serial number is bad
state = SERIAL_STATE_FLAG_INVALID

Запустив программу depends.exe, мы можем убедиться, что наш защищенный файл более не связан с библиотекой VMProtectSDK.dll, а значит модуль лицензирования встроен в приложение. Список используемых DLL можно посмотреть и в VMProtect, открыв защищенный файл и выбрав из меню "Файл" пункт "Свойства".

Наша защищаемая программа читает серийный номер из файла serial.txt, так как файла сейчас нет, то модулю лицензирования подается пустой номер, который также не считается корректным. Перейдем на закладку "лицензии" и сгенерируем серийный номер. Подробно эта процедура описана здесь, мы создадим самый простой серийный номер без каких-либо ограничений.

Затем нажмем кнопку "Скопировать номер", создадим рядом с защищенным приложением файл serial.txt и вставим туда серийный номер. После запуска программы мы увидим следующее:

C:\test>dummy_app.vmp.exe
serial number is correct, calling foo()
I'm foo
done

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


Шаг 4: Тестируем результат

Срок действия серийного номера

Создадим еще один серийный номер с ограничением срока действия, скажем, 2005-м годом. Эта дата уже прошла и наш номер должен быть недействительным. Перейдите на закладку "Лицензии" и нажмите кнопку "Добавить лицензию". В появившемся окне поставьте галочку "Срок годности" и введите "30 сентября 2005 года". Создайте номер, скопируйте его и поместите в файл seral.txt, после чего запустите программу:

C:\test>dummy_app.vmp.exe
serial number is bad
state = SERIAL_STATE_FLAG_DATE_EXPIRED

Модуль лицензирования вернул флаг "срок действия номера закончен". Вернем рабочий номер обратно в файл serial.txt и убедимся, что его модуль лицензирования примет без проблем.

C:\test>dummy_app.vmp.exe
serial number is correct, calling foo()
I'm foo
done
Занесение номера в черный список

Предположим, что наш "хороший" серийный номер выложен в Интернет. Нам необходимо его заблокировать, чтобы в следующих версиях программы он уже не работал. Для этого мы выбираем его в списке номеров и нажимаем кнопку "Заблокировать". Сам факт нажатия кнопки не блокирует серийный номер в защищенном приложении, однако после повторной защиты файла, он откажется принимать этот серийный номер. Проверим. Если сейчас запустить нашу программу, то она примет заблокированный номер без проблем, т.к. это старая версия программы и она еще не знает о блокировке:

C:\test>dummy_app.vmp.exe
serial number is correct, calling foo()
I'm foo
done

Сделаем копию нашей программы и назовем ее "dummy_app1.vmp.exe", а после этого перейдем в VMProtect и повторно защитим наше приложение, после чего запустим новую версию программы:

C:\test>dummy_app.vmp.exe
serial number is bad
state = SERIAL_STATE_FLAG_BLACKLISTED

Теперь для сравнения еще раз запустим старую версию программы:

C:\test>dummy_app1.vmp.exe
serial number is correct, calling foo()
I'm foo
done

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


Шаг 5. Привязка кода к серийному номеру

Одним из наиболее популярных способов взлома программ является поиск проверки серийного номера и последующего условного перехода. Если номер корректен, то ход выполнения программы идет по одной ветке, если некорректен, то по другой. Хакер находит этот переход и заменяет его на безусловный переход на "правильную" ветку. Давайте "взломаем" таким образом нашу тестовую программу. На уровне исходников, конечно же. "Отключим" условный переход:

char *serial = read_serial("serial.txt");
int res = VMProtectSetSerialNumber(serial);
delete [] serial;
if (false && res)
{

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

Привяжем код к серийному номеру

Внимание! демо-версия VMProtect имеет ограничение на количество обрабатываемых функций: обрабатывается только одна функция. Поэтому в случае использования демо-версии, необходимо включать в проект только функцию foo(), т.к. в противном случае демо-версия VMProtect может выбрать для защиты функцию main() и привязка кода к серийному номеру не сработает.

Система лицензирования VMProtect позволяет привязать код одной или нескольких функций к серийному номеру так, что они не будут работать без корректного номера. Тело функции виртуализируется, потом шифруется и может быть расшифровано только при наличии корректного серийного номера. То есть даже если хакер найдет и исправит условный переход в коде проверки номера, функции, привязанные к номеру, все равно не будут работать. Давайте попробуем. В VMProtect выделим функцию foo() и в контекстном меню выберем "Изменить".

В появившемся окне выставим "Да" напротив "Привязать к серийному номеру", после чего защитим наше приложение. С учетом того, что оно "взломано", поместим в файл serial.txt произвольный текст и запустим приложение. В консоли появится текст:

C:\test>dummy_app.vmp.exe
serial number is correct, calling foo()

То есть хакер "исправил" нужный условный переход и программа пошла по "правильной" ветке. Однако в момент вызова foo() программа выводит сообщение:

Так как мы привязали функцию foo() к серийному номеру, а у хакера его нет, то при попытке расшифровать код функции модуль лицензирования выдал сообщение о невозможности продолжить выполнение программы. После нажатия кнопки "ОК" программа завершится и в консоли мы не увидим строчки "done".

Что привязывать к серийному номеру?

Привязывать к серийному номеру имеет смысл те функции, которые вызываются только в зарегистрированной версии программы. Так как привязка возможна только при использовании виртуализации, то необходимо принимать во внимание возможное падение производительности. Скажем, если текстовый редактор не позволяет сохранять результаты своей работы в демо-версии, то к серийному номеру можно привязать функцию сохранения документа. Если эта функция вызывает ряд других в процессе работы, то их привязывать уже не обязательно, так как без основной функции от них будет немного толку.

Также необходимо помнить, что вызов привязанной функции без серийного номера приведет к завершению работы программы без возможности сохранения результатов работы, поэтому тщательно тестируйте защищаемую программу, чтобы она не позволяла вызвать такие функции в демо-режиме. В примере с текстовым редактором убедитесь, что в демо-режиме пункт меню "Сохранить" всегда неактивен, что комбинация Ctrl+S не срабатывает и что при закрытии программы также не предлагается сохранить изменения. В противном случае пользователь может составить негативное мнение о вашей программе.

Привязка к серийному номеру и нерабочие номера

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

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


Описание функций API лицензирования

API системы лицензирования является продолжением API VMProtect и входит в его SDK. API позволяет задать серийный номер и получить информацию о нем: подходит он к программе или нет, истек ли срок действия, на кого зарегистрирован продукт итп. Также API позволяет получить идентификатор оборудования, на котором выполняется программа.

VMProtectSetSerialNumber

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

INT __stdcall VMProtectSetSerialNumber(const char *SerialNumber);

Входной параметр SerialNumber должен содержать указатель на текстовую строку с серийным номером в кодировке base-64 и завершаемую нулем ('\0'). Функция возвращает битовую маску флагов состояния серийного номера, аналогичную той, что возвращает VMProtectGetSerialNumberState(), подробнее о флагах будет написано ниже. Серийный номер считается "хорошим", если функция вернула значение 0.

VMProtectGetSerialNumberState

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

INT __stdcall VMProtectGetSerialNumberState();

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

Флаг | Значение | Описание
SERIAL_STATE_FLAG_CORRUPTED | 0x00000001 | Система лицензирования повреждена. Возможные причины: некорректная настройка проекта защиты, попытка взлома программы.
SERIAL_STATE_FLAG_INVALID | 0x00000002 | Серийный номер некорректен. Флаг выставляется, если система лицензирования не может расшифровать серийный номер.
SERIAL_STATE_FLAG_BLACKLISTED | 0x00000004 | Серийный номер подходит к продукту, однако занесен в черный список в VMProtect.
SERIAL_STATE_FLAG_DATE_EXPIRED | 0x00000008 | Срок действия серийного номера истек. Подробную информацию о сроке действия можно получить вызовом VMProtectGetSerialNumberData()
SERIAL_STATE_FLAG_RUNNING_TIME_OVER | 0x00000010 | Время работы программы истекло. Подробную информацию о времени работы программы можно получить вызовом VMProtectGetSerialNumberData()
SERIAL_STATE_FLAG_BAD_HWID | 0x00000020 | Идентификатор оборудования не совпадает с тем, что записан в серийном номере.
SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED | 0x00000040 | Серийный номер не предназначен для работы с этой версией защищаемой программы. Максимальную дату сборки программы, к которой подходит этот серийны номер можно узнать вызовом VMProtectGetSerialNumberData().

VMProtectGetSerialNumberData

Функция предназначена для получения информации о содержимом серийного номера, установленного вызовом VMProtectSetSerialNumber(). Формат вызова представлен ниже:

BOOL __stdcall VMProtectGetSerialNumberData(VMProtectSerialNumberData *pData, UINT nSize);

Первый параметр - указатель на структуру VMProtectSerialNumberData, в которую будет записана вся необходимая информация. Второй параметр - размер структуры, передаваемой в первом параметре - служит для контроля формата структуры. Функция возвращает FALSE в случае если система лицензирования повреждена (см. флаг состояния SERIAL_STATE_FLAG_CORRUPTED), если на вход подан нулевой адрес структуры или если переданный размер структуры некорректен. Во всех остальных случаях функция возвращает TRUE и записывает всю информацию о серийном номере по указанном адресу. Ниже описываются все элементы структуры:

Элемент | Тип | Описание
nState | INT | Битовая маска флагов проверки ключа. Аналогична той, что возвращает VMProtectGetSerialNumberState().
wUserName | wchar_t[256] | Имя покупателя в кодировке UNICODE, завершаемое нулем.
wEMail | wchar_t[256] | E-Mail покупателя в кодировке UNICODE, завершаемый нулем.
dtExpire | VMProtectDate | Дата окончания срока действия ключа. Формат структуры VMProtectDate описан ниже.
dtMaxBuild | VMProtectDate | Максимальная дата сборки продукта, с которой будет работать этот ключ. Формат структуры VMProtectDate описан ниже.
bRunningTime | INT | Количество минут, которое программа будет работать (максимальная длина сессии). Измеряется в минутах, отсчитывается с момента запуска программы.
nUserDataLength | BYTE | Длина пользовательских данных, размещенных в поле bUserData.
bUserData | BYTE[255] | Пользовательские данные, записанные в ключ. Количество актуальных байт указано в поле nUserDataLength.

Структура VMProtectDate предназначена для компактного хранения даты. Ее поля описаны в таблице:

Элемент | Тип | Описание
wYear | WORD | Год.
bMonth | BYTE | Месяц, нумерация начинается с единицы.
bDay | BYTE | День, нумерация начинается с единицы.

VMProtectGetCurrentHWID

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

INT __stdcall VMProtectGetCurrentHWID(char * HWID, UINT nSize);

Первый параметр - указатель на область памяти, куда будет записан идентификатор. Второй параметр - размер области данных. Функция возвращает количество записанных байт с учетом завершающего нуля ('\0'). Если вместо первого параметра подать NULL, то функция вернет необходимое количество байт для записи номера. Наиболее правильный способ вызова функции следующий:

INT nSize = VMProtectGetCurrentHWID(NULL, 0); // получим необходимый размер буфера
char *pBuf = new char[nSize]; // выделим буфер
VMProtectGetCurrentHWID(pBuf, nSize); // получим идентификатор
// используем его
delete [] pBuf; // освободим память


Генераторы ключей

Зачем они нужны?

Помимо VMProtect, серийные номера могут генерироваться другими программами - генераторами. Это нужно для автоматизации выдачи номеров без участия человека. Покупатель покупает продукт, регистратор делает HTTP-запрос на сайт производителя, там запускается программа-генератор и на основе данных о покупателе формирует серийный номер. Номер отправляется покупателю и производителю. Производитель вносит его в базу VMProtect вручную, через окно импорта лицензий.

Принцип работы

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

Программа-генератор вызывается регистратором через HTTP-запрос. Генератор на PHP может быть вызван напрямую, генератор в виде DLL - косвенно, но алгоритм остается одним и тем же:

  • Получить данные о пользователе от регистратора
  • Добавить необходимую информацию, которую указал производитель
  • Сформировать серийный номер
  • Зашифровать его одним из алгоритмов
  • Передать результат регистратору
Существуют ли готовые генераторы?

С системой лицензирования поставляется три готовых генератора серийных номеров в виде DLL, для платформы .Net и реализованный на PHP.

Можно ли сделать свой генератор?

Можно. Формат серийного номера находится здесь, алгоритмы шифрования серийного номера описаны здесь.

Насколько это безопасно?

В общем случае - вполне безопасно. Однако есть несколько рекомендаций, которым стоит следовать:

  • Используйте протокол HTTPS - если ваш регистратор умеет делать запрос по HTTPS, а ваш хостинг позволяет отвечать на такие запросы, то этот вариант предпочтительнее обычного HTTP, т.к. поток данных будет зашифрован и сгенерированный серийный номер сможет получить только регистратор.
  • "Спрячьте" генератор - убедитесь, что генератор нельзя вызвать случайно. Адрес www.site.com/keygen.php - плохая идея. А вот www.site.com/abc123.php - уже лучше. Убедитесь, что на генератор нет внешних ссылок, что он не показывается при просмотре содержимого директории сайта и не помещайте его в служебные файлы типа robots.txt. Чем меньше известно о расположении генератора - тем лучше. Как вариант - можно вынести генератор на другой сайт.
  • Убедитесь, что вас вызывает регистратор - в программе, обрабатывающей запрос от регистратора, необходимо проверить IP-адрес вызывающей стороны. Обычно регистраторы публикуют список адресов, с которых они могут вызвать генератор. Найдите такой у вашего регистратора и добавьте проверку. В случае несовпадения адреса, лучше не выдавать понятных ошибок. Не возвращайте ничего или верните ошибку 404. Не давайте повода понять почему вызов оказался неудачен.
  • Проверяйте входные параметры - в настройках продукта у регистратора обычно дается возможность указать строку запроса, который регистратор будет делать для получения лицензии. Скажем, вы хотите получать имя пользователя, адрес его электронной почты, дату продажи и номер заказа. Убедитесь при получении запроса, что все параметры переданы и что все они имеют корректный формат. Не отвечайте на запросы с ошибками. О любых ошибках уведомляйте себя отправкой e-mail со всеми параметрами, с которыми был вызван генератор. Это поможет разобраться в причинах проблемы.
  • Добавьте "пароль" - укажите в запросе, посылаемом регистратором, дополнительный параметр - пароль. С неочевидным именем и значением. Проверяйте его на принимающей стороне. В случае несовпадения или отсутствия - не генерируйте номер.


Генератор ключей: Windows-версия

Генераторы ключей: DLL-версия

Описание

Генератор для Windows представляет собой DLL-файлы для платформ x86 и x64, заголовочный файл языка C и lib-файл, совместимый с MSVC. DLL может быть как слинкована статически, так и загружаться динамически.

Все файлы генератора находятся в каталоге Keygen\DLL, там же находится тестовое приложение, которое генерирует серийные номера.

API генератора

Генератор экспортирует всего две функции: одна непосредственно генерирует серийный номер, вторая - освобождает память, выделенную первой. Начнем с первой и основной функции:

VMProtectErrors __stdcall VMProtectGenerateSerialNumber(
                VMProtectProductInfo * pProductInfo,
                VMProtectSerialNumberInfo * pSerialInfo,
                char ** pSerialNumber
            );

Первый параметр - указатель на структуру VMProtectProductInfo, содержимое которой выгружается в VMProtect (см. Экспорт параметров продукта). Структура содержит приватный ключ продукта, используемый алгоритм и количество бит, а также идентификатор продукта. Подробнее о заполнении этой структуры написано ниже.

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

struct VMProtectSerialNumberInfo
{
    INT		flags;
    wchar_t *	pUserName;
    wchar_t *	pEMail;
    DWORD		dwExpDate;
    DWORD		dwMaxBuildDate;
    BYTE		nRunningTimeLimit;
    char *		pHardwareID;
    size_t		nUserDataLength;
    BYTE *		pUserData;
};

Поле flags содержит битовые флаги из набора VMProtectSerialNumberFlags, описанного перед структурой:

  • HAS_USER_NAME - поместить в серийный номер имя пользователя, находящееся в переменной pUserName.
  • HAS_EMAIL - поместить в серийный номер e-mail пользователя, находящийся в переменной pEMail.
  • HAS_EXP_DATE - серийный номер перестанет действовать поле окончания дня, указанного в переменной dwExpDate.
  • HAS_MAX_BUILD_DATE - серийный номер будет подходить только к версиям продукта, созданным до даты, указанной в переменной dwMaxBuildDate.
  • HAS_TIME_LIMIT - программа перестанет работать по истечении времени, указанного в переменной nRunningTimeLimit (время указывается в минутах и не может превышать 255).
  • HAS_HARDWARE_ID - программа будет работать только на оборудовании с идентификатором, указанным в переменной pHardwareID.
  • HAS_USER_DATA - поместить в серийный номер пользовательские данные длинной nUserDataLength, расположенные по адресу, указанному в переменной pUserData.

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

void __stdcall VMProtectFreeSerialNumberMemory(char * pSerialNumber);

Функция VMProtectGenerateSerialNumber возвращает значение типа VMProtectErrors, содержащее 0 в случае успешной генерации номера или код ошибки в противном случае. Возможны следующие коды ошибок:

  • ALL_RIGHT - ошибок нет, номер сгенерирован успешно.
  • UNSUPPORTED_ALGORITHM - в первом параметре функции передан некорректный алгоритм шифрования ключа.
  • UNSUPPORTED_NUMBER_OF_BITS - в первом параметре функции передано некорректное количество бит в ключе.
  • USER_NAME_IS_TOO_LONG - длина имени пользователя в кодировке UTF-8 превысила 255 байт.
  • EMAIL_IS_TOO_LONG - длина e-mail пользователя в кодировке UTF-8 превысила 255 байт.
  • USER_DATA_IS_TOO_LONG - длина пользовательских данных превышает 255 байт.
  • HWID_HAS_BAD_SIZE - идентификатор оборудования имеет некорректный размер.
  • PRODUCT_CODE_HAS_BAD_SIZE - идентификатор продукта, переданный в первом параметре функции, имеет неверный размер.
  • SERIAL_NUMBER_TOO_LONG - серийный номер слишком длинный и не влезает в указанное в алгоритме количество бит.
  • BAD_PRODUCT_INFO - первый параметр функции некорректен или NULL.
  • BAD_SERIAL_NUMBER_INFO - второй параметр функции некорректен или NULL.
  • BAD_SERIAL_NUMBER_CONTAINER - третий параметр функции не указывает на ячейку памяти для записи адреса серийного номера.
  • NOT_EMPTY_SERIAL_NUMBER_CONTAINER - третий параметр функции не указывает на пустую ячейку, в ячейке должно быть значение NULL.
  • BAD_PRIVATE_EXPONENT - значение приватной экспоненты в первом параметре функции некорректно.
  • BAD_MODULUS - значение модуля в первом параметре функции некорректно.

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

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

Пример использования

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

//////////////////////////////////////////////////////////////////////////
// !!! this block should be generated by VMProtect !!! ///
//////////////////////////////////////////////////////////////////////////

VMProtectAlgorithms g_Algorithm = ALGORITHM_RSA;
size_t g_nBits = 0;
byte g_vModulus[1];
byte g_vPrivate[1];
byte g_vProductCode[1];

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

int _tmain(int argc, _TCHAR* argv[])
{
    VMProtectProductInfo	pi;
    pi.algorithm = g_Algorithm;
    pi.nBits = g_nBits;
    pi.nModulusSize = sizeof(g_vModulus);
    pi.pModulus = g_vModulus;
    pi.nPrivateSize = sizeof(g_vPrivate);
    pi.pPrivate = g_vPrivate;
    pi.nProductCodeSize = sizeof(g_vProductCode);
    pi.pProductCode = g_vProductCode;

    VMProtectSerialNumberInfo si = {0};
    si.flags = HAS_USER_NAME | HAS_EMAIL;
    si.pUserName = L"John Doe";
    si.pEMail = L"john@doe.com";
    char * pBuf = NULL;
    VMProtectErrors res = VMProtectGenerateSerialNumber(&pi, &si, &pBuf);
    if (res == ALL_RIGHT)
    {
        printf("Serial number:\n%s\n", pBuf);
        VMProtectFreeSerialNumberMemory(pBuf);
    }
    else
    {
        printf("Error: %d\n", res);
    }

    return 0;
}

Этот пример с проектом для Microsoft Visual Studio находится в каталоге Keygen\DLL\Example, ниже будут описаны наиболее интересные места кода.

Первые строчки функции main заполняют структуру VMProtectProductInfo данными, выгруженными из VMProtect. Этот код стандартен, его не следует менять во избежание появления ошибок. Далее создается структура VMProtectSerialNumberInfo, в поле флагов выставляется комбинация из битов имени пользователя и e-mail. Строчкой ниже в соответствующие поля структуры заносятся значения имени пользователя и пароля. Обратите внимание, что значения принимаются в кодировке UNICODE. Генератор ключей внутри преобразует их в кодировку UTF-8.

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

Остальные поля структуры VMprotectSerialNumberInfo

Некоторые поля структуры требуют дополнительных поясненений. К примеру поля dwExpDate и dwMaxBuildDate содержат даты в определенном формате: 0xYYYYMMDD, т.е. год хранится в старшем слове, а месяц и день в старшем и младшем байтах младшего слова. Для формирования такого числа описан макрос MAKEDATE(y, m, d), который можно вызвать так: MAKEDATE(2010, 05, 12).

В поле pHardwareID должен быть записан указатель на строку, которую вернул метод VMProtectGetCurrentHWID из SDK лицензирования.


Генератор ключей: .Net-версия

Генераторы ключей: .Net-версия

Описание

.Net-версия генератора ключей преставляет собой сборку, содержащую все необходимое для генерации серийных номеров. Исходные коды находится в каталоге Keygen\Net в виде двух проектов: KeyGen - непосредственно генератор серийных номеров, и Usage - пример использования генератора.

Генератор поставляется в исходных кодах, чтобы иметь возможность пересобрать его под нужную версию .Net Framework, однако крайне не рекомендуется вносить изменения в его код. В будущих версиях VMProtect возможно добавление новых возможностей в генератор, что приведет к необходимости повторной модификации кода. Также возможно появление трудноуловимых ошибок. В случае нахождения ошибки в оригинальном коде генератора или пожеланий по его улучшению - обращайтесь в службу поддержки.

Использование генератора

Возьмите за основу код из проекта Usage, добавьте в Ваш проект ссылку на сборку VMProtect.KeyGen.dll, после чего Вы сможете генерировать серийные номера в вашем приложении. Для нормальной работы генератора, он должен понимать к какому продукту Вы собираетесь генерировать серийные номера. Для этого на закладке "Лицензии" VMProtect нажмите кнопку «Экспорт» и в выпадающем списке выберите «Параметры для KeyGen.Net». В текстовом поле ниже появится несколько строк текста, которые необходимо скопировать и перенести в Ваше приложение в виде строковой константы.

Пример вызова генератора представлен ниже:

try
{
    string data = @""; // put the exported data here
    Generator g = new Generator(data);
    g.UserName = "John Doe";
    g.EMail = "john@doe.com";
    g.ExpirationDate = DateTime.Now.AddMonths(1);
    g.MaxBuildDate = DateTime.Now.AddYears(1);
    g.RunningTimeLimit = 15;
    g.HardwareID = "AQIDBAgHBgU=";
    g.UserData = new byte[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
    string serial = g.Generate();
    Console.WriteLine("Serial number:\n{0}\n", serial);
}
catch (Exception ex)
{
    Console.WriteLine("Error: {0}", ex);
}

Строку, скопированную в окне экспорта VMProtect, необходимо поместить в переменную data, которая будет передана параметром в конструктор класса генератора серийных номеров. В случае каких-либо проблем с разбором данных продукта, конструктор бросит исключение с описанием проблемы. Если конструктор отработал успешно, то генератор готов к созданию серийных номеров.

Серийный номер может содержать различную информацию, которая задается через свойства генератора. Пример выше показывает как заполнить абсолютно все поля серийного номера. Отдельные поля имеют ограничения. Например поля User Name и E-Mail не могут принять строки, которые в кодировке UTF-8 будут иметь длину, превышающую 255 символов. В случае подачи некорректных данных, свойства бросают исключения с описанием проблемы.

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

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


Генератор ключей: UNIX-версия

Генераторы ключей: UNIX-версия

Описание

UNIX-версия генератора ключей преставляет собой PHP-файл, содержащий все необходимое для генерации серийных номеров. Файл находится в каталоге Keygen\PHP, ниже рассмотрены основные моменты его использования.

Настройка генератора

В самом начале PHP-файла располагается код настройки генератора:

//////////////////////////////////////////////////////////////////////////////////////////////
// The following lines should be generated by VMProtect License Manager
$exported_algorithm = "RSA";
$exported_bits = 2048;
$exported_private = "PJvj4kEpoQMIpYK+9wEt......xKeiSZgzdiln8Q==";
$exported_modulus = "rOlny/3QgZb/VmGr3CmY......I6ESAUmtQ+RBqQ==";
$exported_product_code = "oLQdGUn8kVk=";
//////////////////////////////////////////////////////////////////////////////////////////////

Этот код генерируется автоматически в VMProtect (см. Экспорт параметров продукта) и уникален для каждого конкретного продукта. Очень важно скопировать его без ошибок, в противном случае генератор будет работать некорректно.

Содержимое ключа

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

$params = array(
    user_name => "John Doe", // UTF-8!
    email => "john@doe.com",
    hwid => "vHGMdMRvGCPjWcCQ", // Exactly as returned by VMProtectGetCurrentHWID
    expire_date => array(year => 2009, month => 10, day => 1),
    maxbuild_date => array(year => 2009, month => 10, day => 1),
    time_limit => 10,
    user_data => base64_decode("CGCvRvMWcPHGdMjQ"), // string of bytes
    );
Функция обработки успешной генерации

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

function OnSerialGenerated($serial)
{
    $serial = wordwrap($serial, 75, "\n", true);
    echo $serial;
}
Функция обработки ошибок генерации

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

function OnSerialGenerationFailed($details)
{
    echo "You will receive serial number in the next 24 hours"; // message to the customer
//	mail("support@vendor.com", "Houston, we have a problem", $details); // message to vendor
}

Причин возникновения ошибки может быть несколько: некорректные параметры алгоритма, некорректные параметры ключа, слишком длинное имя пользователя или e-mail. Слишком длинный серийный номер, который не влезает в количество бит, указанное в алгоритме. Поэтому функция OnSerialGenerationFailed должна отправить производителю максимально подробную информацию о случившемся, дабы тот мог вручную сгенерировать серийный номер и отправить его покупателю.

На что еще следует обратить внимание

Генератор ключей в примерах максимально упрощен. Он не учитывает рекомендации по разработке веб-генераторов, данные здесь. Он не проверяет IP-адрес вызывающей стороны и не анализирует входные параметры. Имейте это в виду, когда будете создавать свой собственный генератор на основе этого.

Имя пользователя и e-mail должны быть переданы в кодировке UTF-8. Уточните у вашего регистратора в какой кодировке он передает информацию о покупателе и, при необходимости, проведите конвертацию. Неправильная кодировка не приведет к неработоспособности серийного номера, однако номер будет содержать совершенно другое имя пользователя и он может быть удивлен, если вы показываете его, скажем, в окне "О Программе".

Асимметричное шифрование - достаточно сложный в математическом плане процесс. Если он реализован на чистом PHP, без применения сторонних библиотек, то генерация серийного номера может занять десятки секунд. Генератор использует функции gmp_powm, bi_powmod, bcpowod в случае их наличия. Все эти функции имеют разную скорость. Если генерация ключей на вашем хостинге занимает слишком большое время, рекомендуется пообщаться со службой поддержки на предмет подключения описаных выше функций. К примеру gmp_powm работает в десятки раз быстрее функции bcpowmod.


Формат серийного номера

Структура серийного номера

Серийный номер состоит из блоков (чанков). Каждый блок начинается с байта идентификатора, который определяет его содержимое и, возможно, длину. Последним всегда идет блок с идентификатором 255, содержащий контрольную сумму всего серийного номера, за исключением последнего блока.

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

Формат блоков
ID | Название | Размер (байт) | Описание | Пример
0x01 | Версия | 1 | Блок содержит версию серийного номера размером в 1 байт. Версия должна быть равна 1. | 01 01
0x02 | Имя пользователя | 1 + N | Блок содержит имя пользователя в кодировке UTF-8. Перед именем пользователя располагается 1 байт длины имени. Таким образом, длина имени пользователя не может превышать 255 байт. Завершающий 0 не требуется. | 02 04 4A 5F 48 4E
0x03 | E-Mail | 1 + N | Блок содержит e-mail пользователя в кодировке UTF-8. Перед e-mail'ом располагается 1 байт длины. Таким образом, длина e-mail не может превышать 255 байт. Завершающий 0 не требуется. | 03 07 61 40 62 2E 63 6F 6D
0x04 | Идентификатор оборудования | 1 + N | Блок содержит идентификатор оборудования, возвращенный функцией VMProtectGetCurrentHWID(). Функция возвращает строку в кодировке base-64, в серийный номер помещается раскодированный вариант строки. Длина блока данных должна быть кратна 4. Перед блоком данных располагается байт длины. Максимальная длина блока - 32 байта. | 04 08 E1 E2 E3 E4 A1 A2 A3 A4
0x05 | Дата окончания лицензии | 4 | Блок содержит дату окончания действия серийного номера. Формат хранения даты описан ниже. | 05 01 0A 07 DA
0x06 | Максимальное время работы | 1 | Блок содержит 1 байт с временем работы программы в минутах. Максимальное время работы, таким образом, может составлять 255 минут. | 06 05
0x07 | Код продукта | 8 | Блок содержит код продукта - 8 байт, которые создаются VMProtect и выгружаются при экспорте параметров продукта. Выгружаются они в кодировке base-64, перед помещением в серийный номер строку необходимо преобразовать в массив байт. Размер массива должен быть строго равен 8 байтам. Этот блок является обязательным, без него защищенная программа будет работать некорректно! | 07 01 02 03 04 05 06 07 08
0x08 | Данные пользователя | 1 + N | Блок содержит до 255 байт пользовательских данных. Система лицензирования не анализирует эти данные и возвращает их при вызове функции VMProtectGetSerialNumberData(). Перед блоком данных располагается байт, содержащий размер пользовательских данных. | 08 05 01 02 03 04 05
0x09 | Максимальная дата сборки | 4 | Блок содержит максимальную дату сборки приложения, с которым будет работать этот серийный номер. Формат хранения даты описан ниже. | 09 01 0A 07 DA
0xFF | Контрольная сумма | 4 | Блок содержит контрольную сумму серийного номера. Блок располагается последним и контрольная сумма считается для всех блоков, идущих перед этим. Подробнее о расчете контрольной суммы написано ниже. | FF 01 02 03 04

Формат хранения даты

Даты в серийном номере хранятся в виде двойного слова - 0xYYYYMMDD. Старшее слово содержит год, а младшее - день и месяц. Байты расположены в порядке Little Endian - от младшего к старшему. В случае, если имеется указатель на первый байт записи, то дата может быть считана и записана следующим кодом:

byte *pDate = 0xNNNNNN; // адрес даты
*(DWORD *)pDate = (2010 << 16) | (10 << 8) | 1; // October 1, 2010
DWORD dwExp = *(DWORD *)pDate;
Подсчет контрольной суммы

Контрольная сумма серийного номера вычисляется при помощи алгоритма хеширования SHA-1. Результатом работы алгоритма являются пять 32-битных слов. Первое слово используется в качестве контрольной суммы серийного номера. Обратите внимание: слово записывается в формате Little Endian (от младшего байта к старшему). В строковом представлении хеша SHA-1 используется формат Big Endian - числа записываются от старшего байта к младшему, поэтому если SHA-1 генерируется функцией, которая возвращает строку (как в PHP), то первые четыре байта хеша необходимо предварительно развернуть.

Дополнительная информация

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

Серийный номер не содержит "соли" (SALT) - случайной информации, предназначенной для того, чтобы ключи для одних и тех же данных получались разными. Эта задача возлагается на алгоритм шифрования. Если необходимы отличия на уровне серийных номеров, скажем для серии ключей, проданных в организацию, можно записать номер ключа строкой в поле имени пользователя (ООО "Компания", ключ 1 из 10) или занести эту информацию в поле User Data в любом удобном для разработчика формате.


Алгоритмы шифрования серийных номеров

Надежность ключей системы лицензирования основывается на применении асимметричных алгоритмов шифрования. В текущей версии реализован алгоритм RSA с длиной ключа от 1024 до 16384 бит. В будущих версиях планируется добавление других алгоритмов, основанных на ECC, а также схем, совмещающих симметричные и асимметричные шифры.

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

Алгоритм RSA

Шифрование серийного номера алгоритмом RSA происходит по следующей схеме:

  • Добавление случайных данных в начало номера - метод основывается на RFC2313, однако реализация немного отличается от описаной там. В начало серийного номера добавляютя байты: 00 02 NN...NN 00, где NN..NN - от 8 до 16 случайных ненулевых байт. Количество байт выбирается случайным, однако принимается во внимание длина серийного номера и максимальная вместительность ключа.
  • Добавление случайных данных в конец номера - окончательное количество байт в серийном номере должно равняться количеству бит в ключах алгоритма, деленному на 8. Серийный номер дополняется до этого числа байт случайными данными. В результате получается следующий формат серийного номера: 00 02 NN..NN 00 DD..DD MM..MM, где NN - набор случайных ненулевых байтов, DD - исходный серийный номер, MM -набор случайных байтов (в т.ч. и нулевых). Суммарная длина последовательности должна равняться количеству бит, применяемому в алгоритме, деленному на 8.
  • Шифрование - производится по стандатной схеме, реализованной во многих библиотеках, работающих с большими числами. Генератор на PHP содержит всю необходимую информацию в открытом виде.
  • Упаковка - полученный после шифрования набор байт кодируется в base-64 - это и есть серийный номер, который отправляется покупателю.


Система Активации

Что Это?

Активация это двухэтапный процесс регистрации приложения. На первом этапе (обычно после покупки) покупатель получает код активации (выглядит как XXXX-YYYY-ZZZZ). На втором этапе покупатель вводит этот код в приложение, приложение соединяется через Интернет с сервером производителя ПО, сервер проверяет код активации и выдает приложению серийный номер, связанный с этим кодом активации и обычно привязанный к оборудованию пользователя.

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

Зачем Она Мне Нужна?

Система аткивации дает производителю ПО достаточно широкие возможности, например:

  • Короткие серийные номера - длинные серийные номера хороши всем, кроме длины. Пользователи могу ошибиться при вводе, почтовые программы иногда портят некоторые символы однако только длинные серийные номера позволяют хранить достаточный объем дополнительной информации. Коды активации позволяют решить все проблемы длинных серийных номеров, не влияя на их положительные стороны.
  • Контроль количества инсталляций - все активации доступны производителю онлайн в реальном времени. Их можно отслеживать, анализировать, блокировать, оказывать поддержку пользователям итд.
  • Триалы - можно создавать серийные номера, ограниченные по времени, используя "режимы" продуктов - "modes".
  • Подписки - подписки похожи на триалы, однако стоят денег. Вы можете продавать пользователю право работать с программой в течение определенного времени, как это делают производители антивирусов - просто продавайте коды активации на очередной год обслуживания.

Более подробная информация о системе активации доступна в соответствующем разделе нашего сайта.

Что Потребуется Сделать?

VMProtect Ultimate дополнен несколькими функциями, которые позволяют сравнительно легко реализовать как онлайн, так и оффлайн активацию. Вам также потребуется установленный на сервере Web License Manager. Наконец, потребуется настроить проект защиты в VMProtect, чтобы модуль активации знал адрес сервера WebLM.


Настройка Активации В VMProtect

Для работы API Активации требуется адрес сервера WebLM, так что необходимо задать его в настройках проекта VMProtect. Чтобы сделать это запустите VMProtect и перейдите в раздел настроек:

Введите адрес в поле "Сервер активации". Адрес должен выглядеть так: http://yourserver/weblm path/activate.php. Если в дальнейшем вы будете получать ошибки в процессе онлайн-активации - адрес это первое место, которое нужно проверить.


Поддержка Активации В Web License Manager

Подробно Web License Manager описан на сайте, здесь даны минимальные инструкции по созданию кода активации для последующего использования с API Активации.

Для начала войдите в Web License Manager (можно использовать демо-версию на нашем сайте) и создайте продукт. Затем экспортируйте продукт как проект VMProtect, чтобы иметь возможность использовать его для настроек лицензирования и активации. После того, как проект настроен, необходимые проверки добавлены в код и исполняемый файл защищен, нажмите ссылку "Add New Code" в левой панели WebLM:

Выберите нужный продукт в верхнем выпадающем списке и заполните остальные поля формы данными, которые Вы хотите поместить в серийный номер. Затем нажмите кнопку "Save". Вы увидите код активации, который можно использовать для отладки API Активации.

Web License Manager позволяет генерировать коды активации автоматически, можеть быть интегрирован с регистраторами и многое другое. Рекомендуем посетить онлайн-версию руководства пользователя для получения более подробной информации.


API Активации

API Активации содержит всего 4 функции. Две предназначены для онлайн-активации и две для оффлайн, когда на компьютере отсутствует доступ в Интернет. API Активации предназначено для взаимодействия с Web License Manager, так что разработчику все равно необходимо вызывать API системы лицензирования, чтобы использовать серийные номера, полученные от WebLM.

VMProtectActivateLicense

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

INT __stdcall VMProtectActivateLicense(const char *code, char *serial, int size);

Параметр code содержит код активации, который получен от Web License Manager в процессе покупки лицензии. Параметр serial указывает на блок памяти размера size, в который будет помещен сгенерированный WebLM серийный номер.

VMProtectDeactivateLicense

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

INT __stdcall VMProtectDeactivateLicense(const char *serial);

Параметр serial содержит серийный номер (не код активации), полученный ранее от WebLM в процессе активации.

VMProtectGetOfflineActivationString
VMProtectGetOfflineDeactivationString

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

INT __stdcall VMProtectGetOfflineActivationString(const char *code, char *buf, int size);
INT __stdcall VMProtectGetOfflineDeactivationString(const char *serial, char *buf, int size);

Параметры code и serial аналогичны параметрам онлайн-версий функций, параметр buf должен указывать на буфер размером 1000 байт или больше, туда будет скопирован текстовый блок для формы оффлайн-активации. Возможные коды ошибок перечислены здесь.

Возможные Коды Ошибок

Код | Значение | Описание
ACTIVATION_OK | 0 | Активация прошла успешно, переменная serial содержит серийный номер.
ACTIVATION_SMALL_BUFFER | 1 | Буфер для серийного номера слишком маленький. Минимальный размер буфера вычисляется по формуле: bits / 8 * 3 / 2 + N, где bits - длина RSA ключа в битах, а N - "константа безопасности" - дополнительное количество байт на случай переносов строк и прочих спец-символов. Рекомендуется использовать как минимум 10.
ACTIVATION_NO_CONNECTION | 2 | Модуль активации не смог подключиться к Web License Manager.
ACTIVATION_BAD_REPLY | 3 | Сервер активации вернул неожиданный результат. Это означает проблемы конфигурации сервера, неправильный url сервера активации или попытку взлома.
ACTIVATION_BANNED | 4 | Этот код активации заблокирован на сервере. Это не значит, что счетчик возможных активаций дошел до нуля, это значит, что данный код активации специально заблокирован производителем ПО через интерфейс WebLM. Например из-за утечки кода активации с пиратской копией.
ACTIVATION_CORRUPTED | 5 | Что-то пошло совсем не так. Ошибка выдается системой самопроверки модуля активации и с высокой вероятностью означает попытку взлома. В случае получения этой ошибки дальнейшие операции с серийными номерами и активацией не могут считаться безопасными.
ACTIVATION_BAD_CODE | 6 | Данный код не найден в базе данных сервера активаций. Возможно пользователь ошибся при вводе кода, так что можно попросить его проверить правильность ввода данных.
ACTIVATION_ALREADY_USED | 7 | Счетчик активаций этого кода дошел до нуля и дальнейшие активации невозможны. Это не обозначает, что код плохой или заблокирован, код хороший. Пользователю нужно связаться с производителем ПО и докупить необходимое количество лицензий или же деинсталлировать программу на других компьютерах, чтобы увеличить значение счетчика в базе данных.
ACTIVATION_SERIAL_UNKNOWN | 8 | Это ошибка деактивации, означает что данный серийный номер не найден в базе данных сервера. Соответственно, деактивация не может быть проведена успешно.

Маленькие хитрости

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


Использование скриптов

Программа VMProtect обладает мощным встроенным скриптовым языком, использование которого позволяет расширить возможности VMProtect на каждом этапе обработки защищаемого файла. Встроенный язык VMProtect является объектно-ориентированным языком с поддержкой:

  • Классов
  • Встроенных функций
  • Вызовов функций из внешних DLL
  • Событий

Синтаксис скриптового языка во многом схож с Object Pascal. Примеры использования скриптов находятся в каталоге "VMProtect/Examples/Scripts".


Классы

Встроенный скриптовый язык программы VMProtect является объектно-ориентированным, и во многом он похож на Object Pascal. В состав скриптового языка входят как стандартные классы, обеспечивающие базовую функциональность, так и специализированные классы, предоставляющие доступ к функционалу защиты приложения. Ниже представлена иерархия всех доступных классов:

  • TObject
    • TPersistent
      • TStrings
        • TStringList
    • TStream
      • THandleStream
        • TFileStream
    • TIniFile
    • TVMObject
      • TPESection
      • TMapRecord
      • TPEDirectory
      • TPEDirectories
      • TPEExportRecord
      • TPEResourceRecord
        • TPEResourceRecords
      • TPEFile
      • TIntelRecord
      • TWatermark
      • TInternalDll
      • TVMObjectList
        • TPESections
        • TMapRecords
        • TPEExportRecords
        • TExtAddresses
        • TVMProcedure
        • TWatermarks
        • TInternalDlls
        • TVMProtector

TObject

Базовый класс, являющийся предком всех классов языка.

TObject = class
  constructor Create;
  procedure Free;
end;
constructor TObject.Create;

Конструктор Create служит для создания экземпляра класса TObject или его потомков.

procedure TObject.Free;

Метод Free представляет собой безопасный метод освобождения ресурсов и уничтожения объекта.

TPersistent

Класс, обеспечивающий метод копирования содержимого одного объекта (source) в другой (self), причем объект-получатель остается самим собой. Указанная особенность не позволяет использовать простое присвоение объектных переменных, а требует отдельного метода.

TPersistent = class(TObject)
  procedure Assign(Source: TPersistent);
end;
procedure TPersistent.Assign(Source: TPersistent);

Метод Assign позволяет продублировать объект - присвоить одному объекту значения всех свойств другого.

TStrings

Класс, обеспечивающий набор методов и свойств, необходимых для работы со строками и наборами строк.

TStrings = class(TPersistent)
  function Add(const S: String): Integer;
  procedure Append(const S: String);
  procedure AddStrings(Strings: TStrings);
  procedure Clear;
  procedure Delete(Index: Integer);
  function IndexOf(const S: String): Integer;
  procedure Insert(Index: Integer; const S: String);
  procedure LoadFromFile(const FileName: String);
  procedure SaveToFile(const FileName: String);
  property Count: Integer; read;
  property Text: String; read write;
  property CommaText: String; read write;
  property Strings[Index: Integer]: String; read write;
  property Objects[Index: Integer]: TObject; read write;
end;
function TStrings.Add(const S: String): Integer;

Добавляет строку в набор и возвращает ее индекс.

procedure TStrings.Append(const S: String);

Добавляет строку в конец набора строк.

procedure TStrings.AddStrings(Strings: TStrings);

Добавляет к текущему набору строк новый набор.

procedure TStrings.Clear;

Очищает набор данных и освобождает связанную с ним память.

procedure TStrings.Delete(Index: Integer);

Уничтожает элемент набора с индексом Index и освобождает связанную с ним память.

function TStrings.IndexOf(const S: String): Integer;

Для строки S возвращает ее индекс. Если строка S в наборе отсутствует, возвращает -1.

procedure TStrings.Insert(Index: Integer; const S: String);

Вставляет строку S в набор и присваивает ей индекс Index.

procedure TStrings.LoadFromFile(const FileName: String);

Загружает набор строк из файла.

procedure TStrings.SaveToFile(const FileName: String);

Сохраняет набор строк в файл.

property TStrings.Count: Integer; read;

Возвращает количество строк в наборе.

property TStrings.Text: String; read write;

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

property TStrings.CommaText: String; read write;

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

property TStrings.Strings[Index: Integer]: String; read write;

Позволяет обратиться к строке с индексом Index.

property TStrings.Objects[Index: Integer]: TObject; read write;

Позволяет обратиться к объекту, связанному со строкой с индексом Index.

TStringList

TDuplicates = (dupIgnore, dupAccept, dupError);
TNotifyEvent = procedure(Sender: TObject);

TStringList = class(TStrings)
  function Find(const S: String; var Index: Integer): Boolean;
  procedure Sort;
  property Duplicates: TDuplicates; read write;
  property Sorted: Boolean; read write;
  property OnChange: TNotifyEvent; read write;
  property OnChanging: TNotifyEvent; read write;
end;
function TStringList.Find(const S: String; var Index: Integer): Boolean;

Ищет в наборе строку S и в случае успеха в параметре index возвращает ее индекс. Выходное значение функции является индикатором успешности поиска.

procedure TStringList.Sort;

Сортирует строки набора по установленным правилам.

property TStringList.Duplicates: TDuplicates; read write;

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

property TStringList.Sorted: Boolean; read write;

Признак необходимости сортировки строк в алфавитном порядке.

property TStringList.OnChange: TNotifyEvent; read write;

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

property TStringList.OnChanging: TNotifyEvent; read write;

Определяет реакцию на изменение набора строк. Событие возникает до очередного изменения.

TStream

Базовый класс, обеспечивающий выполнение основных операций потока безотносительно к реальному носителю информации.

TStream = class(TObject)
  function Read(Buffer: AnsiString; Count: Longint): Longint;
  function Write(Buffer: AnsiString; Count: Longint): Longint;
  function Seek(Offset: Longint; Origin: Word): Longint;
  procedure ReadBuffer(Buffer: AnsiString; Count: Longint);
  procedure WriteBuffer(Buffer: AnsiString; Count: Longint);
  function CopyFrom(Source: TStream; Count: Longint): Longint;
  property Position: Longint; read write;
  property Size: Longint; read write;
end;
function TStream.Read(Buffer: AnsiString; Count: Longint): Longint;

Считывает из потока Count байты в буфер Buffer. Возвращает число скопированных байтов.

function TStream.Write(Buffer: AnsiString; Count: Longint): Longint;

Записывает в поток Count байты из буфера Buffer. Возвращает число скопированных байтов.

function TStream.Seek(Offset: Longint; Origin: Word): Longint;

Абстрактный класс, перекрываемый в наследниках. Смещает текущую позицию в реальном носителе данных на Offset байтов в зависимости от условия Origin:

  • soFromBeginning - смещение должно быть положительным и отсчитывается от начата потока;
  • soFromCurrent - смещение относительно текущей позиции в потоке;
  • soFromEnd - смещение должно быть отрицательным и отсчитывается от конца потока.
procedure TStream.ReadBuffer(Buffer: AnsiString; Count: Longint);

Считывает из потока Count байты в буфер Buffer. Возвращает число скопированных байтов.

procedure TStream.WriteBuffer(Buffer: AnsiString; Count: Longint);

Записывает в поток Count байты из буфера Buffer. Возвращает число скопированных байтов.

function TStream.CopyFrom(Source: TStream; Count: Longint): Longint;

Копирует из потока Source Count-байт, начиная с текущей позиции. Возвращает число скопированных байтов.

property TStream.Position: Longint; read write;

Определяет текущую позицию в потоке.

property TStream.Size: Longint; read write;

Определяет размер потока в байтах.

THandleStream

Класс THandleStream инкапсулирует поток, связанный с физическим носителем данных через дескриптор.

THandleStream = class(TStream)
  constructor Create(AHandle: Integer);
  property Handle: Integer; read;
end;
constructor THandleStream.Create(AHandle: Integer);

Конструктор Create используется для создания экземпляра класса. Значение дескриптора передается в параметре конструктора.

property THandleStream.Handle: Integer; read;

Определяет значение дескриптора.

TFileStream

Класс предназначен для работы с файлом через потоки.

TFileStream = class(THandleStream)
  constructor Create(const FileName: String; Mode: Word);
end;
constructor TFileStream.Create(const FileName: String; Mode: Word);

Параметр FileName задает имя файла. Параметр Mode задает типы операций и права доступа, применимые к данному объекту:

  • fmCreate - создать новый файл. Если файл существует, то открывается в режиме чтения;
  • fmOpenRead - открыть для чтения;
  • fmOpenWrite - открыть для записи;
  • fmOpenReadWrite - открыть для чтения/записи;
  • fmShareCompat - многопользовательский режим доступа;
  • fmShareExclusive - эксклюзивный режим. Ни одно приложение не сможет считать или изменить открытый файл;
  • fmShareDenyWrite - запрет записи для других пользователей и приложений;
  • fmShareDenyRead - запрет чтения для других пользователей и приложений;
  • fmShareDenyNone - запрет разделения ресурса.

TIniFile

Класс предназначен для работы с ini-файлами.

TIniFile = class(TObject)
  constructor Create(const FileName: String);
  function SectionExists(const Section: String): Boolean;
  function ReadString(const Section, Ident, Default: String): String;
  procedure WriteString(const Section, Ident, Value: String);
  function ReadInteger(const Section, Ident: String; Default: Longint): Longint;
  procedure WriteInteger(const Section, Ident: String; Value: Longint);
  function ReadBool(const Section, Ident: String; Default: Boolean): Boolean;
  procedure WriteBool(const Section, Ident: String; Value: Boolean);
  function ReadDate(const Section, Name: String; Default: TDateTime): TDateTime;
  function ReadDateTime(const Section, Name: String; Default: TDateTime): TDateTime;
  function ReadFloat(const Section, Name: String; Default: Double): Double;
  function ReadTime(const Section, Name: String; Default: TDateTime): TDateTime;
  procedure WriteDate(const Section, Name: String; Value: TDateTime);
  procedure WriteDateTime(const Section, Name: String; Value: TDateTime);
  procedure WriteFloat(const Section, Name: String; Value: Double);
  procedure WriteTime(const Section, Name: String; Value: TDateTime);
  procedure ReadSection(const Section: String; Strings: TStrings);
  procedure ReadSections(Strings: TStrings);
  procedure ReadSectionValues(const Section: String; Strings: TStrings);
  procedure EraseSection(const Section: String);
  procedure DeleteKey(const Section, Ident: String);
  procedure UpdateFile;
  function ValueExists(const Section, Ident: String): Boolean;
  property FileName: String read;
end;
constructor TIniFile.Create(const FileName: String);

Создает экземпляр класса TIniFile, ассоциированный с файлом FileName.

function TIniFile.SectionExists(const Section: String): Boolean;

Проверяет существование секции ini-файла с именем Section и возвращает значение True если секция существует.

function TIniFile.ReadString(const Section, Ident, Default: String): String;

Считывает из ini-файла строковой параметр Ident секции Section. Если заданный параметр отсутствует, возвращает Default.

procedure TIniFile.WriteString(const Section, Ident, Value: String);

Присваивает строковому параметру Ident секции Section ini-файла значение Value.

function TIniFile.ReadInteger(const Section, Ident: String; Default: Longint): Longint;

Считывает из ini-файла целочисленный параметр Ident секции Section. Если заданный параметр отсутствует, возвращает Default.

procedure TIniFile.WriteInteger(const Section, Ident: String; Value: Longint);

Присваивает целочисленному параметру Ident секции Section ini-файла значение Value.

function TIniFile.ReadBool(const Section, Ident: String; Default: Boolean): Boolean;

Считывает из ini-файла логический параметр Ident секции Section. Если заданный параметр отсутствует, возвращает Default.

procedure TIniFile.WriteBool(const Section, Ident: String; Value: Boolean);

Присваивает логическому параметру Ident секции Section ini-файла значение Value.

function TIniFile.ReadDate(const Section, Name: String; Default: TDateTime): TDateTime;

Считывает дату из параметра Name, находящегося в секции Section ini-файла. Если заданный параметр отсутствует, возвращает Default.

function TIniFile.ReadDateTime(const Section, Name: String; Default: TDateTime): TDateTime;

Считывает дату и время из параметра Name, находящегося в секции Section ini-файла. Если заданный параметр отсутствует, возвращает Default.

function TIniFile.ReadFloat(const Section, Name: String; Default: Double): Double;

Считывает из ini-файла вещественный параметр Name секции Section. Если заданный параметр отсутствует, возвращает Default.

function TIniFile.ReadTime(const Section, Name: String; Default: TDateTime): TDateTime;

Считывает время из параметра Name, находящегося в секции Section ini-файла. Если заданный параметр отсутствует, возвращает Default.

procedure TIniFile.WriteDate(const Section, Name: String; Value: TDateTime);

Записывает дату в параметр Name, находящийся в секции Section ini-файла.

procedure TIniFile.WriteDateTime(const Section, Name: String; Value: TDateTime);

Записывает дату и время в параметр Name, находящийся в секции Section ini-файла.

procedure TIniFile.WriteFloat(const Section, Name: String; Value: Double);

Присваивает вещественному параметру Name секции Section ini-файла значение Value.

procedure TIniFile.WriteTime(const Section, Name: String; Value: TDateTime);

Записывает время в параметр Name, находящийся в секции Section ini-файла. Если заданный параметр отсутствует, возвращает Default.

procedure TIniFile.ReadSection(const Section: String; Strings: TStrings);

Считывает секцию Section из ini-файла в переменную Strings.

procedure TIniFile.ReadSections(Strings: TStrings);

Считывает все секции из ini-файла в переменную Strings

procedure TIniFile.ReadSectionValues(const Section: String; Strings: TStrings);

Считывает значения параметров секции Section из ini-файла в переменную Strings.

procedure TIniFile.EraseSection(const Section: String);

Удаляет секцию Section из ini-файла.

procedure TIniFile.DeleteKey(const Section, Ident: String);

Удаляет параметр Ident секции Section ini-файла.

procedure TIniFile.UpdateFile;

Сохраняет на диск находящиеся в буфере обмена значения параметров ini-файла. Функция актуальна только для Windows 9х, т.к. системы на ядре Windows NT не используют кеширование в работе с ini-файлами.

function TIniFile.ValueExists(const Section, Ident: String): Boolean;

Проверяет существование параметра Ident секции Section в ini-файле и возвращает значение True, если параметр существует.

property TIniFile.FileName: String read;

Возвращает имя файла на диске, ассоциированного с экземпляром класса TIniFile.

TVMObject

Базовый класс, являющийся предком всех специализированных классов VMProtect.

TVMObject = class(TObject)
  constructor Create(AOwner: TVMObject);
  procedure Assign(SourceObject: TVMObject);
  property Owner: TVMObject; read;
end;
constructor TVMObject.Create(AOwner: TVMObject);

Создает экземпляр класса TVMObject с указанием AOwner, который будет являться владельцем созданного объекта.

procedure TVMObject.Assign(SourceObject: TVMObject);

Метод Assign позволяет продублировать объект, присвоив одному объекту значения всех свойств другого.

property TVMObject.Owner: TVMObject; read;

Обеспечивает доступ к владельцу объекта.

TVMObjectList

Абстрактный класс предназначен для работы со списком объектов.

TVMObjectList = class(TVMObject)
  procedure Clear;
  procedure Delete(Index: Integer);
  property Count: Longint; read;
end;
procedure TVMObjectList.Clear;

Очищает список объектов и освобождает связанную с ним память.

procedure TVMObjectList.Delete(Index: Integer);

Уничтожает элемент списка с индексом Index и освобождает связанную с ним память.

property TVMObjectList.Count: Longint; read;

Возвращает количество объектов в списке.

TPESection

Класс предназначен для работы с секцией PE файла.

TPESection = class(TVMObject)
  property Name: String; read write;
  property PhysicalOffset: Longint; read;
  property PhysicalSize: Longint; read;
  property RVA: Longint; read;
  property VirtualSize: Longint; read;
  property Flags: Longint; read;
  property IncludedInPacking: Boolean; read write;
end;
property TPESection.Name: String; read write;

Определяет имя секции.

property TPESection.PhysicalOffset: Longint; read;

Определяет физическое смещение секции в файле.

property TPESection.PhysicalSize: Longint; read;

Определяет физический размер секции в файле.

property TPESection.RVA: Longint; read;

Определяет относительный адрес секции.

property TPESection.VirtualSize: Longint; read;

Определяет виртуальный размер секции.

property TPESection.Flags: Longint; read;

Определяет свойства секции через набор битов:

  • IMAGE_SCN_CNT_CODE - секция содержит исполняемый код;
  • IMAGE_SCN_CNT_INITIALIZED_DATA - секция содержит проинициализированные данные;
  • IMAGE_SCN_CNT_UNINITIALIZED_DATA - секция содержит непроинициализированные данные;
  • IMAGE_SCN_MEM_DISCARDABLE - секция может быть выгружена из памяти после старта приложения;
  • IMAGE_SCN_MEM_NOT_CACHED - секция не кешируется;
  • IMAGE_SCN_MEM_NOT_PAGED - секция не подвергается страничному преобразованию;
  • IMAGE_SCN_MEM_SHARED - секция является общей для разных процессов;
  • IMAGE_SCN_MEM_EXECUTE - код в секции может быть исполнен;
  • IMAGE_SCN_MEM_READ - данные в секции можно читать;
  • IMAGE_SCN_MEM_WRITE - данные в секции можно изменять;
property TPESection.IncludedInPacking: Boolean; read write;

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

TPESections

Класс предназначен для работы cо списком секций PE файла.

TPESections = class(TVMObjectList)
  function IndexOfName(const Name: String): Integer;
  property Items[Index: Integer]: TPESection; read; default;
end;
function TPESections.IndexOfName(const Name: String): Integer;

Возвращает индекс секции по её имени. Если секция c именем Name отсутствует, возвращает -1.

property TPESections.Items[Index: Integer]: TPESection; read; default;

Позволяет обратиться к секции с индексом Index.

TMapRecord

Класс предназначен для работы с объектом программы.

TObjectType = (otCode, otData, otExport, otMarker, otVBMarker,
  otAPIMarker, otImport, otString, otUnknown);

TMapRecord = class(TVMObject)
  property Address: Int64; read;
  property Name: String; read;
  property CodeType: TObjectType; read;
  property SegmentName: String; read;
end;
property TMapRecord.Address: Int64; read;

Определяет адрес объекта.

property TMapRecord.Name: String; read;

Определяет имя объекта.

property TMapRecord.CodeType: TObjectType; read;

Определяет тип объекта:

  • otCode - объект является функцией/процедурой;
  • otData - объект является данными;
  • otExport - объект является экспортируемой функцией;
  • otMarker, otVBMarker, otAPIMarker - объект является маркером;
  • otImport - объект является импортируемой функцией;
  • otString - объект является строковой константой.
property TMapRecord.SegmentName: String; read;

Определяет имя сегмента, к которому принадлежит адрес объекта.

TMapRecords

Класс предназначен для работы со списком объектов программы. Список объектов из списка имен, доступных в MAP файле, списка импортирумых/экспортируемых функций, списка найденных маркеров.

TMapRecords = class(TVMObjectList)
  function IndexOfAddress(Address: Int64): Integer;
  function IndexOfName(const Name: String): Integer;
  property Items[Index: Integer]: TMapRecord; read; default;
end;
function TMapRecords.IndexOfAddress(Address: Int64): Integer;

Возвращает индекс объекта с адресом Address. Если объект с адресом Address не найден, возвращает -1.

function TMapRecords.IndexOfName(const Name: String): Integer;

Возвращает индекс объекта с именем Name. Если объект с именем Name не найден, возвращает -1.

property TMapRecords.Items[Index: Integer]: TMapRecord; read; default;

Позволяет обратиться к объекту программы c индексом Index.

TPEDirectory

Класс предназначен для работы с директорией PE файла.

TPEDirectory = class(TVMObject)
  function IsEmpty: Boolean;
  procedure Clear;
  property Name: String; read;
  property RVA: Longint; read;
  property Size: Longint; read;
end;
function TPEDirectory.IsEmpty: Boolean

Если RVA или Size равны 0, возвращает True. Иначе - False.

procedure TPEDirectory.Clear;

Обнуляет RVA и Size.

property TPEDirectory.Name: String; read;

Определяет имя директории.

property TPEDirectory.RVA: Longint; read;

Определяет относительный адрес директории.

property TPEDirectory.Size: Longint; read;

Определяет размер директории.

TPEDirectories

Класс предназначен для работы с директориями PE файла.

TPEDirectoryType = (dtExportTable, dtImportTable, dtResourceTable,
  dtExceptionTable, dtSecurityTable, dtFixupTable, dtDebugTable,
  dtImageDescription, dtMachineSpecific, dtThreadLocalStorage,
  dtLoadConfig, dtBoundImport, dtIAT, dtDelayImportDescription,
  dtCOMRuntimeHeader);

TPEDirectories = class(TVMObject)
  property Items[Index: TPEDirectoryType]: TPEDirectory; read; default;
end;
property Items[Index: TPEDirectoryType]: TPEDirectory; read; default;

Позволяет обратиться к директории c индексом Index.

TPEExportRecord

Класс предназначен для работы с описанием экспортируемой функции.

TPEExportRecord = class(TVMObject)
  property Address: Int64; read;
  property Ordinal: Word; read;
  property Name: String; read;
  property ForwardName: String; read;
end;
property TPEExportRecord.Address: Int64; read;

Определяет адрес экспортируемой функции.

property TPEExportRecord.Ordinal: Word; read;

Определяет ординал экспортируемой функции.

property TPEExportRecord.Name: String; read;

Определяет имя экспортируемой функции.

property TPEExportRecord.ForwardName: String; read;

Определяет имя экспортируемой функции, расположенной в другой библиотеке.

TPEExportRecords

Класс предназначен для работы со списком экспортируемых функций.

TPEExportRecords = class(TVMObjectList)
  procedure Add(Address: Int64; Ordinal: Word; const Name: String);
  function IndexOfName(const Name: String): Integer;
  function IndexOfOrdinal(const Ordinal: Word): Integer;
  property DLLName: String; read write;
  property Items[Index: Integer]: TPEExportRecord; read; default;
end;
procedure TPEExportRecords.Add(Address: Int64; Ordinal: Word; const Name: String);

Добавляет новую экспортируемую функцию с адресом Address, ординалом Ordinal и именем Name.

function TPEExportRecords.IndexOfName(const Name: String): Integer;

Возвращает индекс экспортируемой функции по её имени. Если экспортируемая функция c именем Name отсутствует, возвращает -1.

function TPEExportRecords.IndexOfOrdinal(const Ordinal: Word): Integer;

Возвращает индекс экспортируемой функции по её ординалу. Если экспортируемая функция c ординалом Ordinal отсутствует, возвращает -1.

property TPEExportRecords.DLLName: String; read write;

Позволяет обратиться к имени DLL.

property TPEExportRecords.Items[Index: Integer]: TPEExportRecord; read; default;

Позволяет обратиться к экспортируемой функции c индексом Index.

TPEResourceRecord

TResourceType = (rtUnknown, rtCursor, rtBitmap, rtIcon, rtMenu,
rtDialog, rtStringTable, rtFontDir, rtFont, rtAccelerators,
  rtRCData, rtMessageTable, rtGroupCursor, rtGroupIcon, rtVersion,
  rtDlgInclude, rtPlugPlay, rtVXD, rtAniCursor, rtAniIcon,
  rtManifest);

TPEResourceRecord = class(TVMObjectList)
  function IndexOfName(const Name: String): Integer;
  property Name: String; read;
  property ResType: TResourceType; read;
  property Items[Index: Integer]: TPEResourceRecord; read; default;
end;

TPEResourceRecords

TPEResourceRecords = class(TPEResourceRecord)
  function IndexOfResType(ResType: TResourceType): Integer;
end;

TPEFile

Класс предназначен для работы с PE файлом.

TPEFile = class(TVMObject)
  function Read(var Buffer: AnsiString; Count: Longint): Longint;
  function Write(const Buffer: AnsiString; Count: Longint): Longint;
  property ImageBase: Int64; read;
  property MapRecords: TMapRecords; read;
  property Directories: TPEDirectories; read;
  property Sections: TPESections; read;
  property ExportRecords: TPEExportRecords; read;
  property ResourceRecords: TPEResourceRecords; read;
  property Address: Int64; read write;
  property Position: Longint; read write;
  property Size: Longint; read;
  property Handle: Integer; read;
  property EntryPoint: Int64; read write;
end;
function TPEFile.Read(var Buffer: AnsiString; Count: Longint): Longint;

Считывает из потока Count байты в буфер Buffer. Возвращает число скопированных байтов.

function TPEFile.Write(const Buffer: AnsiString; Count: Longint): Longint;

Записывает в поток Count байты из буфера Buffer. Возвращает число скопированных байтов.

property TPEFile.ImageBase: Int64; read;

Определяет начальный адрес загрузки файла.

property TPEFile.MapRecords: TMapRecords; read;

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

property TPEFile.Directories: TPEDirectories; read;

Позволяет обратиться к директориям файла.

property TPEFile.Sections: TPESections; read;

Позволяет обратиться к секциям файла.

property TPEFile.ExportRecords: TPEExportRecords; read;

Позволяет обратиться к списку экспортируемых функций.

property TPEFile.ResourceRecords: TPEResourceRecords; read;

Позволяет обратиться к ресурсам файла.

property TPEFile.Address: Int64; read write;

Определяет текущий адрес в файле.

property TPEFile.Position: Longint; read write;

Определяет текущую позицию в файле.

property TPEFile.Size: Longint; read;

Определяет физический размер файла.

property TPEFile.Handle: Integer; read;

Определяет физический размер файла.

property TPEFile.EntryPoint: Int64; read write;

Определяет адрес точки входа в файле.

TExtAddresses

Класс предназначен для работы со списком адресов.

TExtAddresses = class(TVMObjectList)
  procedure Add(Address: Int64);
  function IndexOf(Address: Int64): Integer;
  property Items[Index: Integer]: Int64; read; default;
end;
procedure TExtAddresses.Add(Address: Int64);

Добавляет новый адрес в список.

function TExtAddresses.IndexOf(Address: Int64): Integer;

Возвращает индекс адреса по его значению. Если адрес со значением Address не найден, возвращает -1.

property TExtAddresses.Items[Index: Integer]: Int64; read; default;

Позволяет обратиться к адресу с индексом Index.

TIntelRecord

Класс предназначен для работы c командой процессора.

TCommandType = (cmUnknown, cmPush, cmPop, cmMov, cmAdd, cmXor,
  cmTest, cmLea, cmRetF, cmRet, cmSsh, cmCall, cmJmp, cmFstsw,
  cmFsqrt, cmFchs, cmFstcw, cmFldcw, cmFild, cmFist, cmFistp,
  cmFld, cmFstp, cmFst, cmFadd, cmFsub, cmFsubr, cmFisub,
  cmFisubr, cmFdiv, cmFcomp, cmFmul, cmRepe, cmRepne, cmRep, cmDB,
  cmDD, cmDQ, cmMovs, cmCmps, cmScas, cmMovzx, cmMovsx, cmInc,
  cmDec, cmLes, cmLds, cmLfs, cmLgs, cmLss, cmXadd, cmBswap,
  cmJmpWithFlag, cmAnd, cmSub, cmStos, cmLods, cmNop, cmXchg,
  cmPushf, cmPopf, cmSahf, cmLahf, cmShl, cmShr, cmSal, cmSar,
  cmRcl, cmRcr, cmRol, cmRor, cmShld, cmShrd, cmLoope, cmLoopne,
  cmLoop, cmJCXZ, cmIn, cmIns, cmOut, cmOuts, cmWait, cmCbw,
  cmCwde, cmCdqe, cmCwd, cmCdq, cmCqo, cmClc, cmStc, cmCli,
  cmSti, cmCld, cmStd, cmNot, cmNeg, cmDiv, cmImul, cmIdiv,
  cmMul, cmOr, cmAdc, cmCmp, cmSbb, cmPusha, cmPopa, cmClflush,
  cmPause, cmBound, cmArpl, cmDaa, cmDas, cmAaa, cmAam, cmAad,
  cmAas, cmEnter, cmLeave, cmInt, cmInto, cmIret, cmSetCC,
  cmCmov, cmAddpd, cmAddps, cmAddsd, cmAddss, cmAndpd, cmAndps,
  cmAndnpd, cmAndnps, cmCmppd, cmCmpps, cmCmpsd, cmCmpss,
  cmComisd, cmComiss, cmCvtdq2ps, cmCvtpd2dq, cmCvtdq2pd,
  cmCvtpd2pi, cmCvtps2pi, cmCvtpd2ps, cmCvtps2pd, cmCvtpi2pd,
  cmCvtpi2ps, cmCvtps2dq, cmCvtsd2si, cmCvtss2si, cmCvtsd2ss,
  cmCvtss2sd, cmCvttpd2pi, cmCvttps2pi, cmCvttpd2dq, cmCvttps2dq,
  cmCvttsd2si, cmCvttss2si, cmDivpd, cmDivps, cmDivsd, cmDivss,
  cmMaxpd, cmMaxps, cmMaxsd, cmMaxss, cmMinpd, cmMinps, cmMinsd,
  cmMinss, cmMulpd, cmMulps, cmMulsd, cmMulss, cmOrpd, cmOrps,
  cmMovd, cmMovq, cmMovntq, cmMovapd, cmMovaps, cmMovdqa,
  cmMovdqu, cmMovdq2q, cmMovq2dq, cmMovhlps, cmMovhpd, cmMovhps,
  cmMovlhps, cmMovlpd, cmMovlps, cmMovmskpd, cmMovmskps, cmMovnti,
  cmMovntpd, cmMovntps, cmMovsd, cmMovss, cmMovupd, cmMovups,
  cmPmovmskb, cmPsadbw, cmPshufw, cmPshufd, cmPshuflw, cmPshufhw,
  cmPsubb, cmPsubw, cmPsubd, cmPsubq, cmPsubsb, cmPsubsw,
  cmPsubusb, cmPsubusw, cmPaddb, cmPaddw, cmPaddd, cmPaddq,
  cmPaddsb, cmPaddsw, cmPaddusb, cmPaddusw, cmPavgb, cmPavgw,
  cmPinstrw, cmPextrw, cmPmaxsw, cmPmaxub, cmPminsw, cmPminub,
  cmPmulhuw, cmPmulhw, cmPmullw, cmPmuludq, cmPsllw, cmPslld,
  cmPsllq, cmPslldq, cmPsraw, cmPsrad, cmPsrlw, cmPsrld, cmPsrlq,
  cmPsrldq, cmPunpcklbw, cmPunpcklwd, cmPunpckldq, cmPunpcklqdq,
  cmPunpckhqdq, cmPackusdw, cmPcmpgtb, cmPcmpgtw, cmPcmpgtd,
  cmPcmpeqb, cmPcmpeqw, cmPcmpeqd, cmEmms, cmPacksswb, cmPackuswb,
  cmPunpckhbw, cmPunpckhwd, cmPunpckhdq, cmPackssdw, cmPand,
  cmPandn, cmPor, cmPxor, cmPmaddwd, cmRcpps, cmRcpss, cmRqrtps,
  cmRqrtss, cmShufps, cmShufpd, cmSqrtpd, cmSqrtps, cmSqrtsd,
  cmSqrtss, cmSubpd, cmSubps, cmSubsd, cmSubss, cmUcomisd,
  cmUcomiss, cmUnpckhpd, cmUnpckhps, cmUnpcklpd, cmUnpcklps,
  cmXorpd, cmXorps, cmBt, cmBts, cmBtr, cmBtc, cmXlat, cmCpuid,
  cmRsm, cmBsf, cmBsr, cmCmpxchg, cmCmpxchg8b,
  cmHlt, cmCmc, cmLgdt, cmSgdt, cmLidt, cmSidt, cmSmsw, cmLmsw,
  cmInvlpg, cmLar, cmLsl, cmCtls, cmInvd, cmWbinvd, cmUd2,
  cmWrmsr, cmRdtsc, cmRdmsr, cmRdpmc, cmFcom, cmFdivr, cmFiadd,
  cmFimul, cmFicom, cmFicomp, cmFidiv, cmFidivr, cmFaddp, cmFmulp,
  cmFsubp, cmFsubrp, cmFdivp, cmFdivrp, cmFbld, cmFbstp, cmFfree,
  cmFrstor, cmFsave, cmFucom, cmFucomp, cmFldenv, cmFstenvm,
  cmFxch, cmFabs, cmFxam, cmFld1, cmFldl2t, cmFldl2e, cmFldpi,
  cmFldlg2, cmFldln2, cmFldz, cmFyl2x, cmFptan, cmFpatan,
  cmFxtract, cmFprem1, cmFdecstp, cmFincstp, cmFprem, cmFyl2xp1,
  cmFsincos, cmFrndint, cmFscale, cmFsin, cmFcos, cmFtst,
  cmFstenv, cmF2xm1, cmFnop, cmFinit, cmFclex, cmFcompp,
  cmSysenter, cmSysexit, cmSldt, cmStr, cmLldt, cmLtr, cmVerr,
  cmVerw, cmSfence, cmLfence, cmMfence, cmPrefetchnta, cmPrefetcht0,
  cmPrefetcht1, cmPrefetcht2, cmPrefetch, cmPrefetchw, cmFxrstor,
  cmFxsave, cmStmxcsr, cmFcmov, cmFucomi, cmFcomi, cmFucomip,
  cmFcomip, cmFucompp);

TOperandType = (otNone, otValue, otRegistr, otMemory, otSegmentRegistr,
  otControlRegistr, otDebugRegistr, otFloatValue, otHiPartRegistr, otBaseRegistr,
  otMMXRegistr, otXMMRegistr);

TOperandTypes = set of TOperandType;

TOperandSize = (osByte, osWord, osDWord, osQWord, osTByte, osOWord);

TOperand = record
  OperandSize: TOperandSize;
  OperandType: TOperandTypes;
  Registr: Byte;
  BaseRegistr: Byte;
  Scale: Byte;
  Value: Int64;
  ValueSize: TOperandSize;
  AddressSize: TOperandSize;
  FixupIndex: Integer;
end;

TIntelRecord = class(TVMObject)
  procedure ReadFromFile(File: TPEFile; var Address: Int64);
  property Address: Int64; read;
  property CommandText: String; read;
  property CommandType: TCommandType; read;
  property Dump[Index: Integer]: Byte; read;
  property DumpLength: Longint; read;
  property Operands[Index: Integer]: TOperand; read;
end;
procedure TIntelRecord.ReadFromFile(File: TPEFile; var Address: Int64);

Дизассемблирует команду c адресом Address.

property TIntelRecord.Address: Int64; read;

Определяет адрес команды.

property TIntelRecord.CommandText: String; read;

Определяет мнемонику команды.

property TIntelRecord.CommandType: TCommandType; read;

Определяет тип команды.

property TIntelRecord.Dump[Index: Integer]: Byte; read;

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

property TIntelRecord.DumpLength: Longint; read;

Определяет размер массива байтов.

property TIntelRecord.Operands[Index: Integer]: TOperand; read;

Позволяет обратиться к операнду с индексом Index.

TVMProcedure

Класс предназначен для работы с блоком кода (списком команд процессора).

TCompilationType = (ctVirtualization, ctMutation, ctUltra);

TCompilationOption = (coUseProjectOptions,coCheckCRC,coEncryptRegs,coEncryptValues,coUseKey);
TCompilationOptions = set of TCompilationOption;

TVMProcedure = class(TVMObjectList)
  function IndexOfAddress(Address: Int64): Integer;
  property Address: Int64; read;
  property BreakAddress: Int64; read write;
  property Name: String; read;
  property CodeType: TObjectType; read;
  property CompilationType: TCompilationType; read write;
  property CompilationOptions: TCompilationOptions; read write;
  property ExtAddresses: TExtAddresses; read;
  property IncludedInCompilation: Boolean; read write;
  property Records[Index: Integer]: TIntelRecord; read;
  property SELicense: Byte; read write;
  property SEVersion: Byte; read write;
  property InputSize: Longint; read;
  property OutputSize: Longint; read;
end;
function TVMProcedure.IndexOfAddress(Address: Int64): Integer;

Возвращает индекс команды с адресом Address. Если команда с адресом Address не найдена, возвращает -1.

property TVMProcedure.Address: Int64; read;

Определяет начальный адрес блока кода.

property TVMProcedure.BreakAddress: Int64; read write;

Определяет конечный адрес блока кода.

property TVMProcedure.Name: String; read;

Определяет название блока кода.

property TVMProcedure.CodeType: TObjectType; read;

Определяет тип блока кода.

property TVMProcedure.CompilationType: TCompilationType; read write;

Определяет тип компиляции:

  • ctVirtualization - виртуализация;
  • ctMutation - мутация;
  • ctUltra - ультра (мутация + виртуализация).
property TVMProcedure.CompilationOptions: TCompilationOptions; read write;

Определяет опции компиляции:

  • coUseProjectOptions - использовать опции проекта;
  • coCheckCRC - контроль целостности ВМ объектов;
  • coEncryptRegs - шифрация регистров на выходе из ВМ;
  • coEncryptValues - скрывать константы;
  • coUseKey - привязать к ключу (доступно только в SenseLock Edition).
property TVMProcedure.ExtAddresses: TExtAddresses; read;

Позволяет обратиться к списку внешних адресов.

property TVMProcedure.IncludedInCompilation: Boolean; read write;

Позволяет установить признак, будет ли включен блок кода в компиляцию.

property TVMProcedure.Records[Index: Integer]: TIntelRecord; read;

Позволяет обратиться к команде с индексом Index.

property TVMProcedure.SELicense: Byte; read write;

Позволяет установить номер лицензии при привязке кода к ключу (доступно только в SenseLock Edition).

property TVMProcedure.SEVersion: Byte; read write;

Позволяет установить номер версии при привязке кода к ключу (доступно только в SenseLock Edition).

property TVMProcedure.InputSize: Longint; read;

Возвращает размер процедуры до компиляции в байтах.

property TVMProcedure.OutputSize: Longint; read;

Возвращает размер процедуры после компиляции в байтах.

TWatermark

Класс предназначен для работы с водяным знаком.

TWatermark = class(TVMObject)
  property Enabled: Boolean; read write;
  property Name: String; read write;
  property Value: String; read write;
  property UseCount: Longint; read write;
end;
property TWatermark.Enabled: Boolean; read write;

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

property TWatermark.Name: String; read write;

Определяет имя водяного знака.

property TWatermark.Value: String; read write;

Определяет значение водяного знака.

property TWatermark.UseCount: Longint; read write;

Определяет, сколько раз водяной знак был использован в защищенных файлах.

TWatermarks

Класс предназначен для работы со списком водяных знаков.

TWatermarks = class(TVMObjectList)
  function Add(const Name, Value: String): TWatermark;
  function IndexOfName(const Name: string): Integer;
  function IndexOfValue(const Value: string): Integer;
  function CreateWatermarkValue: String;
  property Items[Index: Integer]: TWatermark; read; default;
end;
function TWatermarks.Add(const Name, Value: String): TWatermark;

Добавляет новый водяной знак с именем Name и значением Value.

function TWatermarks.IndexOfName(const Name: string): Integer;

Возвращает индекс водяного знака по имени. Если водяной знак c именем Name отсутствует, возвращает -1.

function TWatermarks.IndexOfValue(const Value: string): Integer;

Возвращает индекс водяного знака по значению. Если водяной знак cо значением Value отсутствует, возвращает -1.

function TWatermarks.CreateWatermarkValue: String;

Возвращает уникальное значение водяного знака.

property TWatermarks.Items[Index: Integer]: TWatermark; read; default;

Позволяет обратиться к водяному знаку с индексом Index.

TInternalDll

Класс предназначен для работы с внутренней DLL.

TInternalDll = class(TVMObject)
  property Name: String; read write;
  property FileName: String; read write;
  property LoadAtStart: Booleam; read write;
end;
property TInternalDll.Name: String; read write;

Определяет имя внутренней DLL.

property TInternalDll.FileName: String; read write;

Определяет имя файла, из которого будет загружена DLL.

property TInternalDll.LoadAtStart: Boolean; read write;

Определяет, будет ли загружена DLL при запуске защищенной программы.

TInternalDlls

Класс предназначен для работы со списком внутренних DLL.

TInternalDlls = class(TVMObjectList)
  function Add(const Name, FileName: String; LoadAtStart: Boolean): TInternalDll;
  property Items[Index: Integer]: TInternalDll; read; default;
end;
function TInternalDlls.Add(const Name, FileName: String; LoadAtStart: Boolean): TInternalDll;

Добавляет новую внутренную DLL с именем Name.

property TInternalDlls.Items[Index: Integer]: TInternalDll; read; default;

Позволяет обратиться к внутренней DLL с индексом Index.

TLicense

Класс предназначен для работы с лицензией.

TLicense = class(TVMObject)
  property Date: TDate; read write;
  property CustomerName: String; read write;
  property CustomerEmail: String; read write;
  property OrderRef: String; read write;
  property Comments: String; read write;
  property SerialNumber: String; read;
  property Blocked: Boolean; read write;
end;
property TLicense.Date: TDate; read write;

Определяет дату выданной лицензии.

property TLicense.CustomerName: String; read write;

Определяет имя владельца лицензии.

property TLicense.CustomerEmail: String; read write;

Определяет адрес электронной почты владельца лицензии.

property TLicense.OrderRef: String; read write;

Определяет номер заказа, по которому приобретена лицензия.

property TLicense.Comments: String; read write;

Определяет комментарии к лицензии.

property TLicense.SerialNumber: String; read;

Определяет серийный номер лицензии.

property TLicense.Blocked: Boolean; read write;

Определяет признак блокировки лицензии.

TLicenseManager

Класс предназначен для работы со списком лицензий.

TLicenseManager = class(TVMObjectList)
  function ImportLicense(const SerialNumber: String): TLicense;
  procedure Save;
  property Items[Index: Integer]: TLicense; read; default;
end;
function TLicenseManager.ImportLicense(const SerialNumber: String): TLicense;

Импортирует лицензию из серийного номера. Если серийный номер невалиден, то возвращает nil.

property TLicenseManager.Save;

Сохраняет параметры лицензирования и лицензии в файл.

property TLicenseManager.Items[Index: Integer]: TLicense; read; default;

Позволяет обратиться к лицензии с индексом Index.

TVMProtector

Является основным классом ядра VMProtect.

TMessageType = (mtDebuggerFound, mtVMToolsFound, mtFileCorrupted, mtSerialNumberRequired);

TVMProtector = class(TVMObjectList)
  function AddByAddress(Address: Int64; CompilationType: TCompilationType;
    IncludedInCompilation: Boolean): TVMProcedure;
  function AddByName(const Name: String; CompilationType: TCompilationType;
    IncludedInCompilation: Boolean): TVMProcedure;
  function IndexOfAddress(Address: Int64): Integer;
  function IndexOfName(const Name: String): Integer;
  property InputFile: TPEFile; read;
  property InputFileName: String; read;
  property OutputFile: TPEFile; read;
  property OutputFileName: String; read write;
  property Procedures[Index: Integer]: TVMProcedure; read;
  property WatermarkValue: String; read write;
  property VMExecutorCount: Longint; read write;
  property VMSectionName: String; read write;
  property SEMasterPassword: String; read write;
  property SEUserPIN: String; read write;
  property SEKeyID: Int64; read write;
  property SELicense: Byte; read write;
  property SEVersion: Byte; read write;
  property InternalDlls: TInternalDlls; read;
  property Messages[Index: TMessageType]: String; read write;
  property LicenseManager:TLicenseManager; read;
end;
function TVMProtector.AddByAddress(Address: Int64; CompilationType: TCompilationType;
  IncludedInCompilation: Boolean): TVMProcedure;

Добавляет новый блок кода с адресом Address, типом компиляции CompilationType и признаком IncludedInCompilation.

function TVMProtector.AddByName(const Name: String; CompilationType: TCompilationType;
  IncludedInCompilation: Boolean): TVMProcedure;

Добавляет новый блок кода с именем Name, типом компиляции CompilationType и признаком IncludedInCompilation.

function TVMProtector.IndexOfAddress(Address: Int64): Integer;

Возвращает индекс блока кода по адресу. Если блок кода c адресом Address отсутствует, возвращает -1.

function TVMProtector.IndexOfName(const Name: String): Integer;

Возвращает индекс блока кода по имени. Если блок кода c именем Name отсутствует, возвращает -1.

property TVMProtector.InputFile: TPEFile; read;

Позволяет обратиться к защищаемому файлу.

property TVMProtector.InputFileName: String; read;

Позволяет обратиться к имени защищаемого файла.

property TVMProtector.OutputFile: TPEFile; read;

Позволяет обратиться к выходному файлу.

property TVMProtector.OutputFileName: String; read write;

Позволяет обратиться к имени выходного файла.

property TVMProtector.Procedures[Index: Integer]: TVMProcedure; read;

Позволяет обратиться к блоку кода с индексом Index.

property TVMProtector.WatermarkValue: String; read write;

Позволяет установить значение водяного знака.

property TVMProtector.VMExecutorCount: Longint; read write;

Позволяет установить количество виртуальных машин.

property TVMProtector.VMSectionName: String; read write;

Позволяет установить название секций ВМ.

property TVMProtector.SEMasterPassword: String; read write;

Позволяет установить пароль к мастер ключу (доступно только в SenseLock Edition).

property TVMProtector.SEUserPIN: String; read write;

Позволяет установить PIN код пользователя (доступно только в SenseLock Edition).

property TVMProtector.SEKeyID: Int64; read write;

Позволяет установить идентификатор ключа (доступно только в SenseLock Edition).

property TVMProtector.SELicense: Byte; read write;

Позволяет установить номер лицензии при использовании единых опций лицензирования для защищаемых блоков код (доступно только в SenseLock Edition).

property TVMProtector.SEVersion: Byte; read write;

Позволяет установить номер версии при использовании единых опций лицензирования для защищаемых блоков код (доступно только в SenseLock Edition).

property TVMProtector.InternalDlls:TInternalDlls; read;

Позволяет обратиться к списку внутренних DLL (доступно только в Ultimate).

property TVMProtector.Messages[Index: TMessageType]: String; read write;

Позволяет изменить стандартные сообщения, показываемые при:

  • mtDebuggerFound - обнаружении отладчика;
  • mtVMToolsFound - обнаружении средств виртуализации;
  • mtFileCorrupted - изменении защищенного файла;
  • mtSerialNumberRequired - отсутствии серийного номера.
property TVMProtector.LicenseManager:TLicenseManager; read;

Позволяет обратиться к менеджеру лицензий (доступно только в Ultimate).


Встроенные функции

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

  • VMProtector
  • Watermarks
  • Writeln
  • ParamCount
  • ParamStr
  • ChangeFileExt
  • Random
  • MsgBox
  • CompareText
  • AnsiCompareText
  • Format
  • Now
  • Time
  • DateTimeToStr
  • TimeToStr
function VMProtector: TVMProtector;

Возвращает экземпляр класса TVMProtector, представляющего собой основной объект ядра VMProtect.

function Watermarks: TWatermarks;

Возвращает экземпляр класса TWatermarks, представляющего собой объект для работы с водяными знаками.

procedure Writeln(const S: String);

Writeln выводит текстовое сообщение в окно сообщений скрипта.

function ParamCount: Integer;

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

function ParamStr(Index: Integer): String;

Возвращает параметр командной строки, соответствующий параметру с номером Index, или пустую строку, если Index больше чем ParamCount. Например: при Index со значением 2 результатом функции будет второй параметр командной строки. ParamStr(0) возвращает путь и имя файла запущенной программы (например: C:\TEST\MYPROG.EXE).

function ChangeFileExt(const FileName, Extension: String): String;

Используется для изменения расширения в имени файла, переданном через параметр FileName, на расширение, переданное в параметре Extension. Функция ChangeFileExt не переименовывает исходный файл, а лишь возвращает строку с новым именем файла.

function Random(Range: Integer): Integer;

Возвращает случайное число в диапазоне 0 <= X < Range.

function MsgBox(const Text, Caption: String; Flags: Integer): Integer;

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

  • Text - текст сообщения, ограничение длины в 255 символов отсутствует. При отображении длинные сообщения автоматически компонуются в диалоговом окне.
  • Caption - заголовок диалогового окна. Длина заголовка может быть больше 255 символов, но при этом он не компонуется. Длинный заголовок приводит к широкому окну сообщения.
  • Flags - набор битов, определяющий содержание и поведение диалогового окна. Данный параметр может быть комбинацией флагов из следующих групп:

    Флаг кнопки окна сообщения:
    MB_ABORTRETRYIGNORE | Окно сообщения содержит три кнопки: Abort, Retry, и Ignore.
    MB_OK | Окно сообщения содержит одну кнопку: OK. Этот флаг используется по умолчанию.
    MB_OKCANCEL | Окно сообщения содержит две кнопки: OK и Cancel.
    MB_RETRYCANCEL | Окно сообщения содержит две кнопки: Retry и Cancel.
    MB_YESNO | Окно сообщения содержит две кнопки: Yes и No.
    MB_YESNOCANCEL | Окно сообщения содержит три кнопки: Yes, No, и Cancel.

    Флаг иконки окна сообщения:
    MB_ICONEXCLAMATION | В окне сообщения показывается иконка с восклицательным знаком.
    MB_ICONINFORMATION | В окне сообщения показывается иконка в виде круга с маленькой буквой i.
    MB_ICONQUESTION | В окне сообщения показывается иконка с вопросительным знаком.
    MB_ICONHAND | В окне сообщения показывается иконка с изображением знака Stop.

    Флаг кнопки по умолчанию:
    MB_DEFBUTTON1 | Первая кнопка - кнопка по умолчанию. MB_DEFBUTTON1 - значение по умолчанию, в отличие от указанных MB_DEFBUTTON2, MB_DEFBUTTON3, или MB_DEFBUTTON4.
    MB_DEFBUTTON2 | Вторая кнопка - кнопка по умолчанию.
    MB_DEFBUTTON3 | Третья кнопка - кнопка по умолчанию.
    MB_DEFBUTTON4 | Четвертая кнопка - кнопка по умолчанию.

  • Возвращаемое значение
    0 | Недостаточно памяти для создания диалогового окна.
    IDOK | Пользователь нажал кнопку OK.
    IDCANCEL | Пользователь нажал кнопку Cancel.
    IDABORT | Пользователь нажал кнопку Abort.
    IDRETRY | Пользователь нажал кнопку Retry.
    IDIGNORE | Пользователь нажал кнопку Ignore.
    IDYES | Пользователь нажал кнопку Yes.
    IDNO | Пользователь нажал кнопку No.
function CompareText(const S1, S2: String): Integer;

Сравнивает S1 и S2 и возвращает 0, если они равны. Если S1 больше чем S2, то функция возвращает целое значение больше 0. Если S1 меньше чем S2, то CompareText возвращает целое число меньше 0. Функция CompareText не чувствительна к регистру, и результат ее работы не зависит от текущей локали Windows.

function AnsiCompareText(const S1, S2: String): Integer;

Сравнивает S1 с S2 без учета регистра. Результат операции сравнения зависит от текущей локали Windows. AnsiCompareText возвращает значение меньше 0, если S1 < S2, значение больше 0, если S1 > S2, и значение 0, если S1 = S2.

function Format(const Format: String; const Args: array of const): String;

Позволяет преобразовать несколько элементов открытого массива аргументов в соответствии с указаниями форматирующей строки. Элементами массива аргументов могут быть константы и/или переменные целого и вещественного типа, строки и указатели.

Format - форматирующая строка, представляющая собой произвольную строку, в любом месте которой можно вставить форматирующий спецификатор. Количество форматирующих спецификаторов должно быть не больше количества элементов массива аргументов Args. При работе функции каждый из элементов массива аргументов, в порядке их следования, подставляется в форматирующую строку, в порядке следования форматирующих спецификаторов: первому аргументу - первый спецификатор, второму - второй и т. д. Если количество спецификаторов меньше количества аргументов, "лишние" аргументы игнорируются.

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

"%" [index ":"] ["-"] [width] ["." prec] type
  • index ":" - индекс открытого массива, с помощью этого элемента можно явно указать аргумент, который будет обрабатывать спецификатор;
  • "-" указывает на необходимость прижать отформатированный спецификатором текст к левой границе отведенного для него пространства;
  • width - число, определяющее количество символов для обработанного спецификатором текста; если это число меньше требуемого, этот элемент спецификатора игнорируется, если больше - дополняется справа (если есть элемент "-") или слева (если он отсутствует) нужным количеством пробелов;
  • "." prec - задает точность представления целых и вещественных типов;
  • type - символ, определяющий тип форматирования:
    d | Целое десятичное число. Если задан параметр prec, то символьное представление должно содержать по меньшей мере prec десятичных цифр: если символьное представление содержит меньше цифр, оно дополняется слева символами 0, если больше - параметр prec игнорируется. Если аргумент не является целым числом, возникает исключительная ситуация.
    u | Беззнаковое целое число. Используется подобно типу d, но результат форматирования будет беззнаковым числом.
    e | Вещественное число в экспоненциальном представлении. Символьное представление имеет вид -d, dddddddddddE+ddd. Если число положительное, ведущий минус опускается, а если его модуль меньше 1, знак плюс меняется на минус. Всегда содержит одну цифру целой части, по меньшей мере одну цифру дробной части и не меньше трех цифр десятичного порядка. Если задан параметр точности, он определяет общее количество цифр до символа Е, но не меньше двух: если параметр точности содержит 1 или 0, он заменяется на 2. Если символьное представление содержит больше символов, чем prec, оно округляется по первой отбрасываемой цифре. Значение параметра точности по умолчанию 15.
    f | Вещественное число в виде -ddd, ddd (фиксированное представление). Параметр точности (по умолчанию 2) указывает количество цифр в дробной части. Если он 0, выводится только целая часть числа.
    g | Вещественное число в максимально коротком представлении (экспоненциальном или фиксированном). Параметр точности (по умолчанию 15) определяет максимальное количество значащих разрядов. Если число имеет меньшее количество цифр, оно не дополняется до prec, а если число не имеет дробной части, оно выводится как целое (без запятой).
    n | Соответствует фиксированному представлению, но использует символ-разделитель тысяч.
    m | Денежное представление вещественного числа. Подобно типу n, но справа ставится знак денежной единицы.
    p | Выводит содержимое указателя в виде 8 шестнадцатеричных цифр.
    s | Аргумент должен быть символом, строкой или строкой с терминальным нулем. Параметр точности, если указан, определяет максимальную длину строки: если строка больше, она усекается, если меньше, параметр точности игнорируется.
    x | Шестнадцатеричное представление целого числа. Параметр точности определяет минимальное количество шестнадцатеричных цифр (если число меньше, оно дополняется ведущими нулями).

Функция не чувствительна к регистру символов форматирования. Параметры index, prec и width задаются явно (числами в форматирующей строке) или неявно с помощью символа "*". В этом случае в качестве параметра берется значение очередного аргумента в списке аргументов (он должен быть целым числом).

function Now: TDateTime;

Возвращает текущую дату и время.

function Time: TDateTime;

Возвращает текущее время.

function DateTimeToStr(DateTime: TDateTime): String;

Конвертирует значение DateTime в отформатированную строку даты и времени.

function TimeToStr(Time: TDateTime ): String;

Конвертирует значение Time в отформатированную строку времени.


Вызов функций из внешних DLL

Используя средства встроенного скритового языка VMProptect, можно вызывать функции из внешних DLL-библиотек. Поддерживаются вызовы как стандартных Win32 API функций, находящихся внутри стандартных Windows DLL-библиотек, так и вызовы собственных функций из пользовательских библиотек. Перед вызовом функции находящейся в DLL-библиотеке необходимо описать прототип вызываемой функции в теле скрипта. Описать вызываемую функцию можно двумя способами, которые для наглядности рассмотрим на примере.

Пример: для функции, представленной в виде:
function A(B: Integer): Integer;
описание возможно двумя разными формами:

function A(B: Integer): Integer; external '<имя_dll_функции>@<имя_dll_файла>';
Эта форма указывает, что функция из DLL-библиотеки должна вызываться с типом вызова по умолчанию ('stdcall'). Все стандартные Win32 API функции используют 'stdcall'. Этот же тип вызова очень часто используется и многими функциями пользовательских DLL-библиотек.

function A(B: Integer): Integer; external '<имя_dll_функции>@<имя_dll_файла> <тип_вызова>';

Эта форма указывает, что библиотечная функция должна вызваться специальным типом вызова. Возможные типы вызова: 'stdcall' (по умолчанию), 'cdecl', 'pascal' и 'register'.


События

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

  • OnBeforeCompilation
  • OnBeforeSaveFile
  • OnBeforePackFile
  • OnAfterSaveFile
  • OnAfterCompilation
procedure OnBeforeCompilation;

Событие вызывается на этапе формирования списка защищаемых объектов. В обработчике OnBeforeCompilation можно добавить в проект новые процедуры, а также изменить или удалить уже существующие.

procedure OnBeforeSaveFile;

Событие вызывается перед записью всех объектов, созданных в результате компиляции, в выходной файл. В обработчике события OnBeforeSaveFile можно изменить как сам файл, так и некоторые его свойства (например, список ресурсов, список экспортируемых функций, имена секций и т.д.), которые впоследствии будут автоматически записаны в выходной файл.

procedure OnBeforePackFile;

Событие вызывается перед упаковкой защищенного файла приложения. Используя OnBeforePackFile можно внести изменения в файл, который будет упакован. Данное событие вызывается только при использовании опции "Упаковать выходной файл".

procedure OnAfterSaveFile;

Событие вызывается после записи всех объектов, созданных в результате компиляции, в выходной файл. В обработчике события можно добавить в выходной файл новые данные или изменить ранее записанные.

procedure OnAfterCompilation;

Событие вызывается после компиляции всех объектов, входящих в проект. На данном этапе пользователю доступен откомпилированный файл, над которым могут быть выполнены любые необходимые действия, например добавление электронной подписи (сертификата).

При написании обработчиков событий можно создавать собственные процедуры и функции. Имена событий отображаются в редакторе скрипта жирным шрифтом.


Примеры

  • Автоматическое добавление всех маркеров в проект
  • Изменение имен секций
  • Удаление служебных ресурсов (для программ на Delphi и Borland C Builder)
  • Удаление compid (для программ на Visual C)
  • Подсчет количества данных до и после компиляции по каждому объекту

Автоматическое добавление всех маркеров в проект

procedure OnBeforeCompilation;
var I:Integer;
begin
  with VMProtector do
   for I:=0 to InputFile.MapRecords.Count-1 do
    with InputFile.MapRecords.Items[I] do
     if (CodeType=otMarker) or (CodeType=otAPIMarker) or (CodeType=otVBMarker) then
      begin
       AddByAddress(Address,ctVirtualization,True);
      end;
end;

Изменение имен секций

function GetRandomSectionName: String;
var I:Integer;
    B:Byte;
begin
  Result:='';
  for I:=1 to 8 do
   begin
    B:=32+Random(Ord('z')-32);
    Result:=Result+Chr(B);
   end;
end;

procedure OnAfterSaveFile;
var I:Integer;
begin
  with VMProtector.OutputFile do
   for I:=0 to Sections.Count-1 do
    Sections.Items[I].Name:=GetRandomSectionName;
end;

Удаление служебных ресурсов (для программ на Delphi и Borland C Builder)

procedure OnBeforeSaveFile;
var I,J:Integer;
    RCDataRecord:TPEResourceRecord;
    ResourceNames:TStringList;
begin
  with VMProtector.OutputFile do
   begin
    I:=ResourceRecords.IndexOfResType(rtRCData);
    if I>-1 then
     begin
      RCDataRecord:=ResourceRecords[I];
      ResourceNames:=TStringList.Create;
      try
       ResourceNames.Add('PACKAGEINFO');
       ResourceNames.Add('DVCLAL');
       for J:=0 to ResourceNames.Count-1 do
        begin
         I:=RCDataRecord.IndexOfName(ResourceNames[J]);
         if I>-1 then
          begin
           RCDataRecord.Delete(I);
           Writeln(Format('Resource "%s" is deleted',[ResourceNames[J]]));
          end;
        end;
      finally
       ResourceNames.Free;
      end;
     end;
   end;
end;

Удаление compid (для программ на Visual C)

procedure OnBeforeSaveFile;
var I:Integer;
    S:AnsiString;
begin
  S:='';
  for I:=1 to 64 do
   S:=S+Chr(0);
  with VMProtector.OutputFile do
   begin
    Position:=$80;
    Write(S,Length(S));
   end;
end;

Подсчет количества данных до и после компиляции по каждому объекту

procedure OnAfterCompilation;
var M,N,I:Integer;
    P:Double;
begin
  with VMProtector do
   for I:=0 to Count-1 do
    with Procedures[I] do
     if IncludedInCompilation then
      begin
       M:=InputSize;
       N:=OutputSize;
       if M=0 then
        P:=1
       else
        P:=N/M;
       Writeln(Format('%s -> inputsize: %d bytes, outputsize: %d bytes (%d%%)',[Name,M,N,Round(P*100)]));
      end;
end;


Контактная информация

По всем вопросам, связанным с работой программы или её приобретением, обращайтесь к нам по адресу:
www: http://www.vmpsoft.com
e-mail: info@vmpsoft.com
icq uin: 65107137

Новости | Купить | Форум