Программа VMProtect представляет собой надежный инструмент защиты кода приложения от изучения и взлома. Однако его эффективное использование возможно лишь в том случае, если встроенные в приложение защитные механизмы спроектированы по определенным правилам и не содержат типичных ошибок. Рассмотрим некоторые моменты, связанные с созданием защитных механизмов приложения.
Типичной ошибкой программистов, разрабатывающих собственную схему регистрации экземпляра приложения, является вынос процедуры проверки корректности введенного регистрационного ключа в отдельную функцию c понятным возвращаемым значением:
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);
Выше приведены простейшие примеры реализации встроенных в приложение механизмов защиты. Варианты реальной реализации процедуры регистрации, функции проверки регистрационного ключа или организации хранения результата проверки регистрационного ключа ограничены только фантазией разработчика. Однако в любом случае при разработке механизмов защиты приложения следует знать о возможных ошибках и не повторять их.