Get Demo
  • Windows
  • MacOS
  • Linux

Recommendations on protecting your application

VMProtect is a reliable tool for protecting application code from analysis and cracking, but maximum effectiveness can only be achieved if the built-in protection mechanisms are implemented correctly, without common mistakes that could compromise the entire protection system. Let’s review the key elements of building strong protection for your application.

Registration procedure

A common mistake many developers make when designing their application registration procedure is placing the entire registration key check into a separate function that returns an easily understandable value:

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;

With this approach, an intruder does not even need to understand the key verification algorithm. They can simply modify the code at the beginning of the check procedure so that it always returns a valid registration result:

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

A much more effective approach is to integrate the registration key validation directly into the main application logic, so that the key verification algorithm cannot be separated from the calling procedure. We also recommend “blending” the application logic with the registration check procedure so that the program fails if the verification is bypassed. For the example above, this can be implemented as follows:

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 in this way, an intruder will have to analyze the registration key verification code in detail in order to bypass it. If the application is protected with VMProtect, we recommend virtualizing both the CheckRegistration function and the TForm1.Button1Click procedure. To make cracking even more difficult, you can enable the “Ultra” protection mode to combine code mutation with subsequent virtualization.

Checking registration keys

Another critical mistake developers make is implementing registration key validation incorrectly. Often, the entered key is simply compared with the correct value. A cracker can easily identify the correct key value by tracing the arguments passed to 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 situation, we recommend comparing key hashes instead of the actual key values. A hash function is irreversible, so a cracker cannot retrieve the original key value from the hash. As a result, they must spend significantly more time analyzing the program because multiple code fragments need to be examined, not just the registration check procedure:

var
  HashOfValidRegNumber: Longint;
...
// Example of using Peter Weinberger's PJW 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.

When protecting the application with VMProtect, both the HashPJW and CheckRegistration functions should also be protected to make reverse engineering more difficult.

Saving check results

Even developers who spend a significant amount of time implementing registration procedures often fail to properly protect the result of the registration process itself. The example below uses a global variable to store and control the application’s registration state before invoking the serial number verification procedure. For an intruder, locating a global variable is extremely easy — they simply compare the data segments BEFORE and AFTER registration. Incidentally, the popular ArtMoney program uses the same 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 issue, we recommend storing all registration-related verification results in dynamically allocated memory. In this case, scanning data segments for modified memory blocks BEFORE and AFTER registration becomes ineffective. Here is a simple example demonstrating 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);

These are some of the simplest ways to utilize built-in protection mechanisms. Real-world implementations of registration procedures, registration key verification, and result storage are limited only by the developer’s creativity. However, understanding these common mistakes is essential in order to avoid them while designing your own protection system.

Last updated 11 days ago