Get Demo
  • Windows
  • MacOS
  • Linux

Recommendations on protecting your application

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.

Registration procedure

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.

Checking registration keys

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

When protecting the application with VMProtect, HashPJW and CheckRegistration function should be processed to complicate hacker’s life.

Saving check results

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.

What is VMProtect?

VMProtect is a new generation of software protection utilities. VMProtect supports Delphi, Borland C Builder, Visual C/C++, Visual Basic (native), Virtual Pascal and XCode compilers. At the same time VMProtect has a built-in disassembler that works with Windows and Mac OS X executables, and also can link a MAP-file created by the compiler to quickly select fragments of code for protection. For easy automation of application protection tasks, VMProtect implements a built-in script language. VMProtect fully supports 32/64-bit operating systems of the Windows family starting from Windows 2000, and macOS starting from version 10.6. Importantly, regardless of the target platform, VMProtect supports all range of executables, that is, the Windows version can work with files from the macOS version and vice versa.

The cornerstone principle of VMProtect is to provide efficient protection of the application code from examination by making the application code and logic very complex for further analysis and cracking. Main software code protection mechanisms VMProtect applies are: virtualization, mutation, and combined protection that involves mutation of the application code with subsequent virtualization.

The crucial advantage of the virtualization method used in VMProtect is the fact that the virtual machine executing virtualized fragments of code is embedded into the resulting code of the protected application. Therefore, the app protected with VMProtect needs no third-party libraries or modules to function. VMProtect allows using several different virtual machines to protect different fragments of code of the same application resulting in even more complicated cracking process, because a hacker now has to analyze architecture of multiple virtual machines.

The method of application code mutation applied in VMProtect is based on obfuscation — a process that adds to the application code various excessive, “garbage” commands, “dead” parts of the code, random conditional jumps. It also mutates original commands and transfers execution of certain operations to the stack.

The key difference of VMProtect from other software protectors is its ability to protect different parts of the code with different methods: part of the code can be virtualized, the other part is obfuscated and critical fragments are protected using the combined method.

Another unique feature of VMProtect is embedding of watermarks to the code of the application. Watermarks allow to definitely identify the official owner of the hacked copy of the program, and therefore to take certain measures to him or her.

VMProtect is available in 3 editions:

  • Lite;
  • Professional;
  • Ultimate;

The below table lists differences in functionality of certain VMProtect editions:

Capabilities
Protection methods
Mutation
Virtualization
Ultra (mutation-virtualization)
Console version
Protection options
Memory protection
Import protection
Resource protection
Packing
Debuger detection
Virtual box detection
Service functions
Watermarks
Script language
Licensing system
License manager
File protection

Analysis, cracking and protection of software

A software product can be analyzed by means of static or dynamic analysis. Static analysis means protection cracking algorithm is based on disassembly results analysis or on decompiling of the protected application. Dynamic analysis is required to crack encrypted or dynamically changing executables, because static analysis of such programs proved to be difficult.

For dynamic analysis, the program being cracked is executed in a debugger framework. This way, everything that happens during operation of the program can be controlled by the debugger. During dynamic analysis, a cracker uses the debug mode to bypass all protection algorithms of the program one by one, in particular registration key generation and check procedures. Another tool dynamic analysis often use is tracking of files, system services, ports and external devices the cracked program queries.

The main instruments to protect applications from cracking attempts are software protectors. Protection most of protectors provide is based on packing and/or encryption of the original executable with great focus put on protecting unpacking/decryption procedures.

Such an algorithm is often insufficient to provide reliable protection. If an application is protected by packing, a hacker can easily obtain the original unpacked file as soon as he makes the memory dump right after the unpacker finishes its work. Moreover, there are multiple automated tools to crack the most popular protectors. The same is true for encryption: after obtaining a proper license key (often purchased legally), a cracker can decrypt protected parts of the code.

Some software protectors use a number of anti-debug techniques. However, each one of them significantly influences the performance of the protected program. Also, anti-debug methods are only effective against dynamic analysis and are completely inefficient against static analysis. Even more, all anti-debug methods modern protectors use are well-known and studied, and crackers have programmed many utilities to avoid or bypass them. Activity monitors are not affected by the built-in anti-debug protection at all.

More efficient ways to protect an application are obfuscation and virtualization that complicate analysis of the protected application’s code. Generally, high efficiency of these protection method is based on the human factor: the more complex the code is and the more resources the application uses, the harder it is for a cracker to understand program logic and, consequently, to crack protection.

Obfuscation “entangles” the code of an application by adding excessive instructions to it. Virtualization transforms the source code to the bytecode executed by a special interpreter that imitates a virtual machine with a specific set of commands. Therefore, virtualization leads to high and irreducible level of complexity of the resulting code, and if applied properly, the code protected with such a method does not contain methods to restore the original code explicitly. So, the main advantage of virtualization is that a virtualized fragment of the code doesn’t transform to machine language commands during execution, and this in turn prevents obtaining of the original code of the application by a cracker.

Reverse engineering of virtualized fragments is reduced to analysis of the architecture of a virtual machine, building a disassembler for the corresponding architecture of a processor imitated by the virtual machine, and analysis of the disassembled code. A properly implemented virtual machine makes creating a disassembler for it quite a difficult task. The only disadvantage of virtualization is relatively low execution speed, so this method should only be applied to parts of the code that are non-critical to execution speed.

Most of today’s protectors do not put much attention to obfuscation and virtualization, or their implementation is poor. This allows crackers to remove such protection in automatic or semi-automatic mode. Another bottleneck of modern protectors is use of undocumented Windows functions, which leads to limited operation of the protected application in newer versions of the OS, or if DEP is enabled.

Glossary

You can’t use a tool effectively if you don’t know the terminology specific to the corresponding subject. The following glossary explains terminology used in VMProtect. The glossary is not intended to be exhaustive, so some terms may provide meanings that differ from classic ones.

Bytecode – the code received after transcoding commands of the real processor to commands of the virtual machine.

Virtualization – a process that transforms a part of the executable code of the application to commands of the virtual machine featuring command system, architecture and operational logic that are unknown to a potential hacker. Virtualized fragments of code are executed by the interpreter of the virtual machine without transforming them to machine language code of the physical processor. Generally, reverse engineering of virtualized fragments comes down to building a disassembler with the same architecture as the processor the virtual machine imitates and analyzing of the resulting disassembled code.

Virtual Machine – a program code directly executing bytecode in the protected application.

Watermarks – a unique for each user array of bytes that allows to definitely identify a legal owner of the hacked copy of the program.

Mutation – replacing an original command with an analogue or with a certain set of commands producing the same result

Obfuscation – a group of methods and techniques intended to complicate analysis of a program code. Depending on the programming language a protected program is written on, different obfuscation types are used. Obfuscation of applications written on interpreting languages (Perl, PHP and others) is made through modifying the source code: comments are removed, variables are given senseless names, string constants are encrypted and so on. Obfuscation of Java / .NET applications is performed through transforming the bytecode processed by the virtual machine. Obfuscation of compiled programs relies on modifying machine language codes: the obfuscator adds various “garbage” commands, “dead code”, random jumps. Also, original commands mutate, a part of operations is moved to the stack, and a number of structural (or less frequently mathematical) transformations is made. Reverse engineering of obfuscated fragments of code attempts to bring the fragments back to their original state, and that is a time-consuming task as long as obfuscation is done properly.

Protector – software intended to protect other programs from being hacked. The majority of today’s protectors do not modify the source code of an application, packing or encrypting the app instead. The main focus is put to protecting the unpacking/decrypting program or procedure.

Entry Point – the initial address execution of the application loaded into the memory starts from.

Packing – a way to protect the program code by compressing the executable file of the program and/or libraries using non-typical algorithms. The protected fragments of code are compressed by the packer, and unpacked completely or partially at user’s side when the application is executed.

Encryption protects a part of the application’s code with strong cryptographic algorithms. Software protected by encryption requires an end-user to enter the activation code to remove limitations set by the developer for the unregistered version of the program.

Introduction

There is no an ideal way to protect software from unauthorized use and distribution. No existing systems can provide absolute security and prevent a potential hacker from neutralizing it. However, using quality and efficient protection can make cracking of software extremely difficult up to complete inadvisability in terms of time and efforts put into breaking the protection. While software protection can pursue different goals, the basis of any protection system is securing the application from analysis, because it is resistance to reverse engineering that shapes overall efficiency of the protection system.