Home » Support » User Manual » Introduction » Protection recommendations

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<&gt0 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.