VMProtect is a reliable tool to protect the application code from analysis and cracking, yet the most efficient use is only possible if the in-app protection mechanisms are built properly, without typical mistakes that could ruin the whole protection. Let's review crucial elements of developing a good protection of your program.
A typical mistake many developers make when they design their own application registration procedure is enveloping the entire registration key check to an individual function that also returns an easy-to-comprehend 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 such an approach, an intruder doesn't even need to understand the key check algorithm. He may simply modify the code in the beginning of the check procedure so that it always returned a correct registration key value:
function CheckRegistration(const RegNumber: String): Boolean; begin Result:=True; exit; ... end;
A much more effective way to check the key is to embed the check for correctness to the main operation logic of the program, so that the algorithm of registration key check could not be separated from the algorithm of the calling procedure. We also recommend to "blend" the operation logic with the registration key check procedure to make the program fail if the check was bypassed. For the above example this can be done 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 like that, an intruder will have to analyze the code of registration key check in all details in order to bypass it. If this application is protected by VMProtect, virtualization of both the CheckRegistration function and TForm1.Button1Click procedure is recommended. To make the hacking even more complex, you can turn on the "Ultra" protection mode to combine mutation of the code and subsequent virtualization.
Another critical mistake developers make is wrong implementation of registration key checks. Often the entered key is simply compared with the correct value. A cracker can easily match the correct value of the key by tracing arguments of 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 such situation we recommend comparing hashes of keys, instead of their actual values. The hash function is irreversible, so a cracker is unable to retrieve a real key value from the hash and has to spend a lot more time to study the program, because more code fragments need to be analyzed now, not just the registration key check procedure:
var HashOfValidRegNumber: Longint; ... // Peter Weinberger's PJW hashing algorithm example of use 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, HashPJW and CheckRegistration function should be processed to complicate hacker's life.
Usually, even developers who spent a lot of time on registration procedure do not give due attention to protecting the result of the registration procedure. The below example uses a global variable to store and control the registration state of the application before invoking the serial number check procedure. For an intruder, finding a global variable is a piece of cake - he simply compares data segments BEFORE and AFTER registration. By the way, 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 such a situation, we recommend to store results of all checks related to registration of the program in dynamic memory. In this case scanning data segments for modified memory blocks BEFORE and AFTER registration turns useless. Here is a very 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 the simplest ways to utilize the built-in protection mechanisms. Real-world implementations of registration procedures, registration key checks and storing the result are only limited to creativity of a developer. Anyway, you should know about these potential mistakes to avoid them while developing your own protection mechanism.