Protection recommendations
VMProtect is a reliable tool for protecting software code against analysis and cracking. However, it is only possible to effectively use it if the protection mechanisms built into the application are designed according to certain rules and have no typical mistakes. Let us consider some aspects relating to the creation of protection mechanisms in an application.
Registration procedure
Software developers often make a common mistake when developing their own registration scheme for an application copy. This mistake involves following a procedure of checking the validity of the entered registration key in a separate function that returns whether the entered key is valid or not:
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;
If this approach is used to check the registration code, the cracker does not have to go into the key check algorithm. This is because it will be enough just to change the code at the beginning of the procedure so that it always returns the value corresponding to the valid registration key:
function CheckRegistration(const RegNumber: String): Boolean; begin Result:=True; exit; ... end;
A much more effective way is to build the registration code validity check into the program logic. This will mean that it is impossible to separate the registration code check from the algorithm of the procedure calling. It is also recommended to "add" some logic of the program to the registration code check procedure. This will ensure that "bypassing" of the registration procedure will cause errors in the application. The following method can be used in the above example:
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;
If the CheckRegistration function is implemented this way, the cracker will have to thoroughly analyze how the whole key check function works in order to "bypass" checking of the registration key. If you use VMProtect to protect this application, it is recommended to virtualize not only the CheckRegistration function, but also the TForm1.Button1Click procedure. To make it even more difficult to crack the application, it is possible to use the "Ultra" protection mode combining the mutation of the application code with its virtualization.
Registration key check
Very often programmers make real blunders while implementing the registration key validity check when they compare the entered key to its correct value. In this case the cracker can easily find out the correct key value during single-stepping by viewing the arguments used to call the string comparison function:
var ValidRegNumber: String; ... function CheckRegistration(const RegNumber: String): Boolean; begin if RegNumber=ValidRegNumber then Result:=True else Result:=False; end;
To avoid this kind of situation during the comparison of the entered key value to its correct value, it is recommended to use their hashes instead of the real values. As a matter of fact, a hash function is a one-way function, which means it is impossible to determine the value of the valid key by checking hashes. Cracking the application will take much more time spent on analyzing the program because it will be necessary to examine far more parts of the code instead of only the procedure of checking whether the entered key is valid:
var
HashOfValidRegNumber: Longint;
...
// An example of using Peter Weinberger's (PJW) generic hashing algorithm
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.
If you use VMProtect to protect the program, it makes sense to protect the HashPJW and CheckRegistration functions to make the cracker’s work harder.
Registration check result storage
As a rule, programmers who spend a lot of time implementing the registration procedure do not pay enough attention to protecting the results of the program registration. In the example below, the state of the global variable that stores the result of the registration check is verified before calling the registration number check function. It is easy for the cracker to find the global variable – it is enough just to check the data segments BEFORE and AFTER the registration. Incidentally, the well-known program ArtMoney is based on a similar principle.
var IsRegistered: Boolean; ... procedure TForm1.Button1Click(Sender: TObject); begin ... if not IsRegistered then IsRegistered:=CheckRegistration(RegNumber); if not IsRegistered then exit; ... end;
To avoid this kind of situation, it is recommended to store all checks related to the program registration in dynamic memory. This is because scanning data segments for changes in memory cells BEFORE and AFTER the registration will be useless in this case. See below for the simplest example showing how to store the result in dynamically allocated memory.
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);
It is possible to see the simplest examples of protection mechanisms built into the application above. It is only up to the developer to decide how to actually implement the registration procedure, the registration key check function or how to store the registration key check result. It is important to know about mistakes possible during the implementation of application protection mechanisms in order to avoid them.