Get Demo
  • Windows
  • MacOS
  • Linux

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.

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.

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.

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

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.

Working with VMProtect

Before you start working with VMProtect, please take a look at the following sections of the manual:

Preparing a project

Let us take a look to a very simple application consisting of only one form (Form1), a text element (Edit1) and a button (Button1). The application works as follows: when the Button1 is clicked, the app checks if the password entered is correct and displays a corresponding message.

A password is checked using a very simple algorithm: on the first step we transform it to a numeric form, then we calculate the remainder on dividing it by 17. The password is correct if the remainder on dividing the numeric representation of the entered password by 17 is equal to 13. The password check procedure implementation on Delphi looks as follows:

function TForm1.CheckPassword: Boolean;
begin
  Result:=(StrToIntDef(Edit1.Text, 0) mod 17=13);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
  if CheckPassword then
   MessageDlg('Correct password', mtInformation, [mbOK], 0)
  else
   begin
    MessageDlg('Incorrect password', mtError, [mbOK], 0);
    Edit1.SetFocus;
   end;
end;

Selection of procedures and functions to protect can be done in three ways:

  • Using a PDB/MAP file created by the compiler along with the executable of the program. The MAP-file contains all necessary information about names and addresses of all procedures and functions of the app. If the MAP-file is used, you can select procedures and functions to protect by their names. With the MAP-file, every time the project is recompiled, VMProtect automatically determines new addresses of procedures and functions.
  • Using markers inserted to the source code of the application. Markers are special marks VMProtect uses to determine the boundaries of the protected fragment. Also, VMProtect supports markers with a predefined compilation type. Using markers makes sense when you only want to protect a part of a function or procedure. Using markers allows you to specify parts of the code where string constants to protect will be further placed.
  • By address of protected procedures in the executable file. In comparison with the above two ways, this one is less convenient for use. Every time the application is modified and recompiled, you have to specify all addresses again. This type of protection is recommended for applications without the source code available.

Using a MAP-file to define boundaries of the protected code has one more significant advantage. It is worth reviewing it a bit more. Almost any procedure or function that has local variables or that uses the stack to save registers and/or intermediate calculation results has the so called prologue and epilogue located correspondingly in the beginning and in the end of the compiled procedure or function:

push ebp      \
mov ebp, esp   \ prologue
push 00        / 
push ebx      /
...
pop ebx       \
pop ecx        \ epilogue
pop ebp        /
ret           /

Due to the way modern compilers work, code markers never incorporate the prologue and the epilogue of a function. Even if the entire code of the CheckPassword function between begin and end is enclosed to markers. It would be enough for a hacker to modify the prologue of the function so that the virtualized code was never executed. For the CheckPassword function this can be done as follows:

mov eax, 1
ret

Important!

If a PDB/MAP file is used to choose code fragments for virtualization, the prologue and the epilogue are also virtualized significantly boosting hack-proof capability of the protected program. Moreover, is one virtualized function is called from another virtualized function, control transfers between them without actually jumping to the address of the called function (a call in this case is a simple jump to another address within the bytecode of the virtual machine interpreter). This also strengthens protection of the program as all modification of the entry point a hacker makes are rendered useless. When working with virtualized functions, transfer of control to the entry point of a virtualized function only happens when the protected function is called from an unprotected or mutated fragment of code.

Using a PDB/MAP file

To create a MAP-file you should enable the corresponding option in the compiler settings.

Visual Studio

If you develop your application in Visual Studio, this can be done as follows: in the main menu of the IDE, open the project properties (Project – Properties), then on the “Linker – Debugging” tab set the “Generate MAP File” option to “Yes (/MAP)”:

Borland Delphi

If you develop the application using Borland Delphi, the same can be done as follows: in the main menu of the Delphi IDE open the project options(Project – Options) and on the “Linker” tab set the “MAP file” section option to “Detailed”:

After you enabled MAP-file generation, the project must be rebuilt.

Upon loading of a MAP-file VMProtect compares modification date and time of the MAP-file and those of the protected file. If they are different, the MAP-file is not loaded.

Using markers

To protect individual fragments of the code and to protect string constants, you can insert special markers to the source code of your application. Markers are calls to functions imported from the external library (32-bit applications use VMProtectSDK32.dll, and 64-bit applications use VMProtectSDK64.dll; drivers use VMProtectDDK32.sys and VMProtectDDK64.sys respectively) – further referred to as VMProtectSDK. Procedures and functions in VMProtectSDK do not perform any actions and are merely labels VMProtect uses to determine boundaries of the protected code. The beginning and the end of the protected block are marked as follows:

C/C++

#include "VMProtectSDK.h"
VMProtectBegin(MARKER_TITLE);
...
VMProtectEnd();

C#

using System.Reflection;

class Foo
{
	[Obfuscation(Feature = "virtualization", Exclude = false)]
	// Feature = "virtualization", "ultra", "mutation"
	public Foo()
	{
...

Pascal

uses VMProtectSDK;
VMProtectBegin(MARKER_TITLE);
...
VMProtectEnd;

MASM

include VMProtectSDK.inc
invoke VMProtectBegin,SADD(MARKER_TITLE)
...
invoke VMProtectEnd

Visual Basic

VMProtectBegin (StrPtr(MARKER_TITLE))
...
VMProtectEnd

Also, instead of VMProtectBegin you can use markers with the predefined compilation types:

  • VMProtectBeginVirtualization – the marker uses the “Virtualization” compilation type.
  • VMProtectBeginMutation – the marker uses the “Mutation” compilation type.
  • VMProtectBeginUltra – the marker uses the “Ultra” compilation type.

Markers are processed as follows: when VMProtect analyzes the code of the protected application, it locates all calls to VMProtectSDK procedures and functions. Boundaries of blocks to protect are defined by marker pairs VMProtectBegin / VMProtectBeginVirtualization / VMProtectBeginMutation / VMProtectBeginUltra and VMProtectEnd. Then, when VMProtect processes the code of the protected application, it removes both the markers and any mentions of the VMProtectSDK, so there is no need to include these libraries to your setup package. Markers are removed regardless of whether they are included to compilation or not. When named markers are used, their names are also removed.

If a title for the marker is specified, it is assigned with the name like “VMProtectMarker MARKER_TITLE”. If a title of the marker is not specified, it is assigned with a unique name: “VMProtectMarker”+marker serial number. However, using non-named markers has a significant disadvantage: if a new marker will be inserted to the code of the program, numeration of all non-named markers will change. So we recommend to always use named markers.

A particularly important thing to consider when working with markers is that you shouldn’t allow jumps from non-protected areas inside the marker. This can happen, for example, if you enclose a part of a cycle in markers. If the application that uses markers becomes non-functional after protection, you can detect jumps from non-protected areas and addresses by enabling the “Debug mode” option. In this mode, when the protected application works under the debugger, the latter will interrupt execution of the program if a jump from a non-protected area into the protected debugger is detected. When all such jumps are found, you should either change placement of the markers, or if this is impossible, mark those addresses as external using the GUI version of VMProtect.

SDK functions

SDK functions can be integrated to the source code of the protected application to set boundaries of the protected areas, to detect debuggers or virtualization tools.

Code markers

Service functions

Licensing functions

VMProtectBegin

void VMProtectBegin(const char *MarkerName);

The marker identifying the beginning of the protected area of the code. A call to VMProtectBegin must be placed before the first command (or procedure or function call) of the protected code block. MarkerName defines the name of the marker that looks like “VMProtectMarker”+MarkerNamе in VMProtect. For example, a marker VMProtectBegin(‘CheckRegistration’) will look as VMProtectMarker “CheckRegistration”. If the name of the marker is not set, it is given a unique name in the form of “VMProtectMarker”+marker_serial_number. You can set the compilation type of the given protected block in VMProtect.

VMProtectBeginVirtualization

void VMProtectBeginVirtualization(const char *MarkerName);

The marker identifying the beginning of the protected area of the code with the predefined “virtualization” compilation type. MarkerName defines the name of the marker. The compilation type of this marker cannot be changed during further work with VMProtect.

VMProtectBeginMutation

void VMProtectBeginMutation(const char *MarkerName);

The marker identifying the beginning of the protected area of the code with the predefined “mutation” compilation type. MarkerName defines the name of the marker. The compilation type of this marker cannot be changed during further work with VMProtect.

VMProtectBeginUltra

void VMProtectBeginUltra(const char *MarkerName);

The marker identifying the beginning of the protected area of the code with the predefined “ultra (virtualization+mutation)” compilation type. MarkerName defines the name of the marker. The compilation type of this marker cannot be changed during further work with VMProtect.

VMProtectBeginVirtualizationByKey

void VMProtectBeginVirtualizationLockByKey(const char *MarkerName);

The marker identifying the beginning of the protected area of the code with the predefined “virtualization” compilation type and the enabled “Lock to key” option. MarkerName defines the name of the marker. The compilation type of this marker cannot be changed during further work with VMProtect.

VMProtectBeginUltraLockByKey

void VMProtectBeginUltraLockByKey(const char *MarkerName);

The marker identifying the beginning of the protected area of the code with the predefined “ultra (virtualization+mutation)” compilation type and the enabled “Lock to key” option. MarkerName defines the name of the marker. The compilation type of this marker cannot be changed during further work with VMProtect.

VMProtectEnd

void VMProtectEnd(void);

The marker identifying the end of the protected area of the code. The call to VMProtectEnd must be placed after the last command (procedure or function call) of the protected code block.

VMProtectIsProtected

bool VMProtectIsProtected(void);

The MProtectIsProtected function returns True if the file is processed by VMProtect.

VMProtectIsDebuggerPresent

bool VMProtectIsDebuggerPresent(bool CheckKernelMode);

The VMProtectIsDebuggerPresent function allows to detect the launch of the application under a debugger. The result (True/False) can be processed with in-app protection mechanisms. If CheckKernelMode=False the function checks for User-mode debuggers (OllyDBG, WinDBG etc.). If CheckKernelMode=True, both User-mode and Kernel-mode debuggers (SoftICE, Syser etc.). When protecting drivers, the value of CheckKernelMode does not make sense, because drivers always work in the kernel mode, so presence of kernel-mode debugger is always checked.

VMProtectIsVirtualMachinePresent

bool VMProtectIsVirtualMachinePresent(void);

The VMProtectIsVirtualMachinePresent function allows to detect the launch of the application under a virtual machine tool: VMware, Virtual PC, VirtualBox, Sandboxie. The result (True/False) can be processed with in-app protection mechanisms.

VMProtectIsValidImageCRC

bool VMProtectIsValidImageCRC(void);

The VMProtectIsValidImageCRC function detects the fact that the executable module has been changed in the memory of the process (only unchangeable segments of code and data are checked). The result (True/False) can be processed with in-app protection mechanisms.

VMProtectDecryptStringA

const char * VMProtectDecryptStringA(const char *Value);

The VMProtectDecryptStringA function decrypts the ANSI string constant – Value. To decrypt the constant, you must include it to the list of protected objects.

VMProtectDecryptStringW

const wchar_t * VMProtectDecryptStringW(const wchar_t *Value);

The VMProtectDecryptStringW function decrypts the Unicode string constant – Value. To decrypt the constant, you must include it to the list of protected objects.

VMProtectFreeString

bool VMProtectFreeString(const void *Value);

The VMProtectFreeString function frees dynamic memory allocated for the decrypted string. It is not necessary to free up memory, but if you do this – you must use this function. If VMProtectDecryptStringA / VMProtectDecryptStringW are used with the same parameters for the second time without destroying previously decrypted string, additional memory is not allocated.

Main window

Main window consists of the following elements:

Main menu

The main menu consists of the following items:

File menu

  • Open – choose an executable you want to protect, or a project file (*.vmp). You can also select a file to open from the list of previously protected applications shown in the File menu. You can also bring up the open dialog with the corresponding button on the toolbar . Finally, you can drag-n-drop the file you need to the VMProtect window;
  • Save Project – save application protection settings to a “*.vmp” file. Project settings file is saved to the same folder where the executable of the protected application is located. Saving is also available with the toolbar button ;
  • Save Project As… – save the project file to a file with a new name;
  • Close – finish working with the current project;
  • Exit – close VMProtect.

Edit menu

Image

Project menu

  • Add function – add a new function to the list of protected objects. You can specify the exact address of a function, or find that function by name:

  • Add folder – add a folder to the project:

    Usage of folders do not impacts protection of objects inside. Folders are only used to arrange protected objects in the project tree. You can move objects into a folder with simple drag-n-drop;

  • Add license – add a license to the project:

    This command adds a new user license and generate a license key based on entered user dаta: name, e-mail, date, order id and others. You can write this information inside the key, as well as to limit functionality of the license. For example, allow certain number of executions or specify an expiry date for the key.
    To add a license you should first generate a pair of keys:

  • Export Key Pair – exports project keys to the specified format of the key generator:

  • Import – imports a license from a serial number or from another project and adds it to the current project.
  • Compile – compile the protected application with the current protection settings. To compile the project, you can also click the toolbar button: .
  • Execute – executes the protected application. Original/protected executable of the application can be started with the button on the toolbar: . The down arrow allows you to specify command line parameters for execution.

Tools menu

  • Watermarks – brings up the Watermarks dialog window:

  • Preferences – brings up the Preferences window of the program:

Help menu

  • Home Page – open VMProtect homepage;
  • Contents – open VMProtect help file;
  • About… – see general information about VMProtect.

Toolbar

The toolbar consists of the following elements:

  • Open a project or a protected file: 
  • Save the project: 
  • Name of the protected file:
  • Compile the project: 
  • Execute the original/protected file (pressing the down arrow allows to specify command line parameters): 
  • Default action (depends on the selected section):
  • Quick search box:

Project section

The “Project” section contains the following subsections:

  • Functions for Protection – allows selecting objects for protection
  • Licenses – allows managing licenses and serial numbers
  • Files – allows including additional data files or DLL to the protected EXE file
  • Script – allows enhancing protection capabilities by means of the built-in LUA script language
  • Options – allows configuring application protection parameters

Functions for Protection section

This section is for choosing which functions must be protected.

Adding a function

To add a new object to the project, click the “Add Function” button on the toolbar, or select the corresponding item in the context menu:

A new function dialog appears:

Specify the address of the function, or start typing function’s name to the quick search box to select the function directly from the list on the “Functions” tab. Use multi-selection if you want to add several functions at once. All selected functions are added with the chosen protection options.

Options

  • Compilation type – select the way the object is compiled. Selecting a proper way to protect each object (procedure, function, code fragment) from examination and hacking results in optimal balance between performance and security of the code. VMProtect allows the developer to set the following compilation types for each protected object:
    • Mutation. The executable code is modified at the level of CPU commands – existing commands are mutated, various “garbage” commands are added and so on. This type of compilation is relatively weak in terms of protecting the code from hack or analysis. Its main purpose is to prevent detection of processed functions with automated signature analysers (PEiD+KANAL, IDA+FLIRT etc.). This type of compilation is worthwhile to protect library functions as they usually do not require strong protection from cracking and analysis. So it is enough to modify signatures so that a potential cracker couldn’t automatically detect particular libraries used in the application. Mutation provides low level of protection and analysis, but high performance of code execution.
    • Virtualization. The executable code is translated to the bytecode executed on a virtual machine. This compilation type should be applied to all critical parts of code that require both serious hacking and analysis countermeasures and high performance. Virtualization provides average protection from hacking and analysis and average execution speed.
    • Ultra (mutation + virtualization). The executable code is mutated on the processor instruction level and then is translated to the bytecode executed on a virtual machine. This type of compilation should be applied to all fragments of code when the speed of execution is not critical. Ultra protection provides high protection and is executed slowly.
  • Lock to Serial Number – if this option is enable, the protected function become unavailable without a valid serial number entered. In such a way you can limit access to certain functions of the application in the unregistered version.

Search

You can locate objects you need by their name using the quick search box:

You can use the following wildcard symbols in the search box:

  • * – replaces any number of symbols;
  • ? – replaces one symbol.

Adding a folder

You can add a folder to the project using the context menu:

Folders group protected objects and allow changing protection parameters (exclusion from compilation, type of compilation) of all objects in a folder at once.

Enter the name of a new folder:

Editing objects

The right side of the window displays contents of the selected object. If a protected object is selected in the project tree, the right part displays a disassembled representation of this object:

Code

If a folder is selected in the project tree, the right side displays the list of functions in that folder:

You can move functions to and from folders with simple drag-n-drop.

Managing licenses

Initialization

By default, licensing functions are off. To enable them, you should create a pair of keys in the “Licenses” subsection of the “Project” section. If the project links to the license manager database (in older versions of VMProtect the license manager was a standalone program), VMProtect will suggest you to import the corresponding licenses to the project. When initialization finishes, the “Lock to Serial Number“ option will be available, and you will be able to create and process serial numbers.

Licenses

The “Licenses” section displays a full list of licenses in the left panel and parameters of the selected element in the main panel.

The right panel displays detailed information about the selected license. It also allows blocking a serial number, copying it to the clipboard or seeing hardware id information (by clicking it).

Creating a license

To add a new license, click the corresponding button on the toolbar. The Add License dialog appears:

The upper part of the dialog allows to specify main parameters of the new license. The lower section configures which parameters will be finally put into a serial number and allows adding additional dаta: for example, a hardware identifier or custom user data. When you finish filling the data, click OK, and VMProtect creates a new license.

Removing and blocking licenses

To delete a license, right-click it in the list of licenses and choose “Delete” in the context menu. Or simply select the license and press Del. To block a license, turn the “Blocked” parameter in the main panel to “Yes”.

There is a difference between deleting a license and clocking it. Removed serial numbers are not blocked by the licensing system. They simply do not exist any more in the database. If a license was created by mistake and the serial number never was sent to anyone, you can delete such a license. However, if the serial number was compromised, you should not just delete the license, you should block it instead. In this case the information about this serial number is passed to the licensing system, and it will not further accept this serial number.

Importing serial numbers

Serial numbers can be created not only in VMProtect, but also in third-party programs – key generators. Key generators allow you to automatically provide e-commerce agents with serial numbers whenever a license is purchased. To add such serial numbers to the database, you can import them. To import serial numbers, press Ctrl+I or select the “Import” command in the “Project” menu.

The first step to import a serial number is the import dialog:

If the entered license already presents in the database, the edit license window will be opened. Otherwise, the new license dialog appears where you can adjust the license information and confirm license creation by clicking the OK button. The license is added to the database.

Exporting license parameters

External key generators require secret key of the product to work properly. You can obtain this information in the most suitable form by using the “Export key pair” command from the “Project” menu. The following export dialog appears:

The “Export format” field allows you to select one of the supported formats, while the “Results of export” field contains the key data in the most appropriate format for the selected type of a key generator. To copy the contents to the clipboard, click the “Copy” button.

Files section

The “Files” section allows the developer to include to the protected EXE file additional data it requires for operation such as: images, data files, text resources and dynamically linked libraries. During execution of the protected EXE file, all types of data including DLL are loaded from memory of the process directly, bypassing writing of these data to the disk.

The Add File toolbar button allows adding a new file to the project. You can also add a new file with the context menu:

To adjust properties of the selected file, select it in the left list and edit its title, file name or properties.

To remove a file from the list of protected objects, right-click it with the mouse and choose “Delete”, or simply select it and press Del.

By right-clicking a section name, you can exclude the file from compiling. Compilation exclusions are saved to the project file.

Script section

The “Script” subsection of the “Project” section is for writing scripts using the built-in script language:

You can edit the code of the script on the main panel of the section. The right-click menu allows manipulating with fragments of the code.

Certain code blocks (cycles, functions) can be collapsed or expanded using a hierarchy sign (plus or minus) near the corresponding line number.

Right-click the section name to exclude the script from compilation. The excluded script is not executed and is not processed by the program. This parameter is saved in the project settings.

Options section

The “Options” subsection of the “Project” section allows you to configure various protection parameters:

File

  • Memory Protection – this option allows you to secure the image of the file in memory from any changes (data integrity is checked for all sections that do not have the WRITABLE attribute). Image integrity check is performed before passing the control to the original entry point of the program. If integrity is violated, a corresponding message is shown and the program stops execution.
  • Import Protection – this option allows hiding the list of API the protected program uses from a cracker. We recommend using this option along with packing of the output file.
  • Resource Protection – this option encrypts resources of the program (except icons, manifests and other service resources).
  • Pack the Output File – this option allows you to pack the protected file to reduce its size. The application is unpacked automatically when the protected file is executed. The entire unpacking goes without any disk writing, completely in RAM. When using this option, we also recommend to include EntryPoint to the list of protected objects.

    Important!

    When the program starts, after the code is unpacked the control is passed to EntryPoint. If the code of EntryPoint is virtualized, this code will be executed on the same VM interpreter as the code of the unpacker itself. Virtualization of EntryPoint combined with packing of the protected file prevents manual unpacking of the protected file, as in this case an intruder has to restore EntryPoint code to get a working file image.

Additional

  • Watermarks – allows adding watermarks to the project.
  • VM Segments – When the file is compiled, new segments will be added to it to the place where various system data are stored (virtualized and mutated code, VM interpreters, watermarks etc.). This option allows you to specify names for these new segments. We recommend changing the standard “.vmp” name of segments to something else (for example “.UPX”).
  • Strip Debug Information – removing of debug information impedes analysis of the code by a cracker.
  • Strip Relocations – some compilers (i.e. Delphi) create a relocation table for EXE files that are not used by the operating system to load EXE files. If the option is enabled, the space occupied by relocation table is be used for VM needs.

Detection

  • Debugger – this option prevents debugging of the protected file. There are 2 types of debuggers: User-mode debuggers (OllyDBG, WinDBG etc.) and Kernel-mode debuggers(SoftICE, Syser and others). Debugger detection is performed before passing control to the entry point of the program. If a debugger is detected, a corresponding message is shown and the program stops execution.
  • Virtualiztion Tools – this option prohibits executing the protected file in various virtual environments: VMware, Virtual PC, VirtualBox, Sandboxie. Detection of virtualization is performed before passing control to the entry point of the program. If a virtual environment is detected, a corresponding message is shown and the program stops execution.

Messages

Here you can customize messages the program displays when it detects a debugger, a virtualization tool, if the file is corrupted or when there’s an attempt to execute the code protected by a serial number.

Licensing parameters

  • File Name - choose a project file created in the license manager as a licensing parameter file. By default, the current project file is used.
  • Activation Server - this option is required for activation system.

Functions section

The “Functions” section lists all functions available for protection:

When a function is selected, you can see its properties and protection options on the main panel. For each function you can specify a compilation type and enable lock to a serial number.

Details section

The “Details” section displays various information about the protected application. It also allows you to exclude certain segments of data or resources from packing. Changes you make in this section are saved to the project file.

“Details” section contains the following subsections:

Directories section

Displays information about file directories:

Segments section

Displays information about segments:

Options

  • Excluded from memory protection – a segment can be excluded from memory protection.
  • Excluded from packing – a segment can be excluded from packing.

Imports section

Displays information about imported functions and libraries:

Exports section

Displays information about exports the file has:

Resources section

Displays information about resources:

Options

  • Excluded from packing – a resource can be excluded from packing.

Dump section

Displays the memory dump of the protected application as machine language codes and assembler instructions:

The “Go To Address” button on the toolbar allows you to go to a specified address of the protected application:

To quickly go to a certain function, start typing its name in the quick search box. You can also enter its exact address.

Console version

After you created a project in the GUI mode you can use the console version (VMProtect_Con.exe). You can execute it as follows:

VMProtect_Con File [Output File] [-pf Project File] [-sf Script File] [-lf Licensing Parameters File] [-bd Build Date (yyyy-mm-dd)] [-wm Watermark Name] [-we]
  • File – the file name of the executable you want to protect (*.exe, *.dll and so on), or the file name of a (*.vmp) project. If a project file name is specified, the file name of the executable is taken from the project file.
  • Output File – the file name and path to the protected file that should be created after processing the original file. If this parameter is not set, the value is taken from the project file.
  • Project File – the file name and path to the project file created in the GUI mode. If the parameter is not set, the program searches for a *.vmp file in the folder of the executable.
  • Script file – the file name of the script the protected file is processed with. If the parameter is not set, the script from the current project file is used.
  • Licensing Parameters File – the name of a file containing licensing parameters. If this parameter is not set, licensing parameters are taken from the current project file.
  • Build Date – Application build date in the following format: “yyyy-mm-dd”. If this parameter is not set, the current date is used. The build date is inscribed into the protected application and is used by the licensing system to check serial numbers against the “Maximum build date” field.
  • Watermark Name – the name of a watermark inserted into the protected file. If the name of a watermark is not set, the watermark specified in the project settings is used.
  • we – when this parameter is set, all warnings are displayed as errors.

Licensing system

The licensing system creates and checks for validity serial numbers. It supports limiting the protected software by date and time, locking it to specific hardware, encrypting the code with a serial number, limiting the period of free updates and many more. The system is based on asymmetric cryptographic algorithms to minimize chances that an unauthorized serial number generator will be built. To protect the licensing system itself, VMProtect uses virtualization therefore minimizing chances that the application will be cracked or patched on the code level.

Licensing system features

Secure serial numbers

The license system uses an asymmetric algorithm to encrypt serial numbers. The number is encrypted with a private key that only the developer has. The protected product uses the corresponding public key to decrypt the serial number and checks it. Due to the length of keys the system uses (1024 bit or higher for RSA) it is virtually impossible to compute the private key and make a key generator for the application.

Locking the code to a serial number

VMProtect allows execution of a part of the program code on a virtual machine. The set of commands of the virtual machine changes on every build of the protected program. The licensing system allows to encrypt a part of virtual machine commands with the key stored in a serial number. Therefore, even if a hacker modifies a conditional jump in the program, the code still will not work without the correct serial number. And since code decryption is managed by the virtual machine, the decryption algorithm is hard to analyze even if the serial number is available.

Limiting the period of free upgrades

The licensing system can write a date into the key so that all application versions after that date will not work with this key. This mechanism allows you to limit the period of free upgrades. For example, upon purchase the current date plus one year is written to the key, so a user will be able to download new versions from the website within one year. The key will work in these versions only. When the one-year period ends, a user has a choice: either use the last working version of the program or purchase an update for one more year.

Key expiration date

The licensing system allows you to write a date into the key, after which that key stops working. This is a convenient option for products that require systematic updates. For example, upon purchase the current date plus one year is put to the key, and the program works for the given user for one year. Unlike the period of free upgrades, the user has no choice here – he has to purchase a new license if he wants to continue using the program.

Limiting the program operation time

The licensing system allows you to limit the maximum operation time of a copy of the program. This proves to be useful in many demo applications. For example, a user wants to test a full-featured copy of the program. In this case you can send to him or her a serial number that limits the maximum session time of the program with say ten minutes. After that, the program stops functioning. This option is also convenient for various server applications, where a user can’t easily restart the program.

Hardware locking

The licensing system allows the developer to receive a hardware identifier of user’s PC based on information about CPU, network card and OS. The licensing system can produce a serial number that will be only valid on that hardware only. This option allows you to limit usage of the application to several computers.

Black list

If a serial number is compromised, the licensing system allows adding such a number into the black list. The blocked serial number will not work in all further versions of the application.

Data storage

The licensing system stores in a serial number and provides to the program the following dаta: a user name, an e-mail and up to 255 bytes of arbitrary information (the so called custom user data). You can use this capability to show additional information in the “About” window of the program, to implement additional security checks if the entered serial number, to store constants available in the registered version of the product only and so on.

Time-limited demo versions

With the activation system, a software developer can automatically build time-limited serial numbers locked to user’s hardware. This allows you to setup a secure trial period (demo) for an application, because VMProtect does not try to hide trial marks on user’s computer, and instead generates a working, but time-limited serial number. Activation is carried out over the Internet, but the activation API also provides the offline activation mode.

What can’t the licensing system do and why?

100% secure hardware lock

Besides the licensing system allows locking a serial number to a hardware identifier, you should understand that most of hardware data is received using operating system means, so a hacker can intercept them and modify this information. The licensing system uses certain ways to minimize this risk, but if you need 100% secure hardware lock, we recommend to use a solution based on USB keys also supported by VMProtect.

How the licensing system works

Application protection

To protect an application, VMProtect embeds the special code into it. This code checks serial numbers using information specified in the “Licensing” subsection of the “Project” section. A public key is embedded to the application and is used then to decrypt serial numbers. Also, the protection date and some additional information the licensing requires to work is put into the application.

Creating serial numbers

Serial numbers can be created in the Licenses section of the “Project” section or using third-party applications – key generators. A serial number is a set of data about a customer encrypted using the asymmetric algorithm. The serial number then is passed to the customer, he or she enters it to the program, and the licensing system checks it.

Checking a serial number in the program

The licensing system has special functions a program can use to work with serial numbers. The program sends a serial number to the licensing system and queries information about it. The licensing system returns the state of a serial number (valid/invalid and why) and also can provide detailed information about the serial numbers including a user name, an e-mail, the expiration date of this serial number and so on. The protected program analyzes serial number information and decides whether or not to continue operation and to limit the functionality.

Integrating to application

In the several steps described below we will create a test application that queries the licensing system: provides serial numbers to it, receives the status of a serial number and its contents. On the first stage, we use the license system in the test mode; one the second stage we use it as it would be used in actual practice.

Work modes of the licensing system

Building protection always goes through two main steps: development and release. As for licensing, at first you create an application, integrate protection into it, then add checks and functional limitations. And only after thorough testing you can make the product available to users and begin the second stage. Testing of a protected application is a complex process, because you need to make sure all checks and conditional jumps operate correctly. Making “real” serial numbers for all possible test cases is inconvenient. That is why the licensing system offers the “developer mode” (AKA “test mode”) as well. In this work mode, no protection is applied to the app, and reaction of the system to supplied serial numbers is adjusted in the configuration file. When the application is free from bugs and it correctly works with the licensing system, VMProtect replaces the “test” licensing module with the real one that do perform real serial number checking. This is done when the application is protected, so you can’t avoid this step by mistake.

Stage 1: Test mode

In the test mode, all reactions of the licensing system (statuses and data it returns) to supplied serial numbers is described in the configuration file. The file is called VMPLicense.ini and should be located in the working folder of the application. In 10 steps provided below we will go from creating the simplest application to full-featured use of the licensing system in the test mode with hardware locking and limiting the period of free upgrades.

Stage 2: Real mode

In the real mode VMProtect licensing system puts a special licensing module to the protected application. This module carries out the same functions as the test one in the SDK, but works with contents of a serial number instead of the configuration ini-file. The next five steps illustrate the process of protecting a simple application with a full-featured protection based on VMProtect and the licensing system.

Additional information

Values of all bit flags, structure formats and function call parameters can be found in the Licensing system API section of this help file. Use this section as a reference, while Steps provided above help to easily implement a typical ready-to-use protection.

Step 1.1: Creating a protected application

The first step is to create an application. This would be a simple app without any user interface and with no serious capabilities. Our goal is to pass a serial number to the licensing system and receive its answer.

#include <windows.h>
#include <stdio.h>
bool is_registered(const char *serial)
{
        return serial && serial[0] == 'X';
}
int main(int argc, char **argv)
{
        char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity
        if (!is_registered(serial))
        {
                printf("please register!\n");
                return 0;
        }
        printf("We are registered.\n");
        return 0;
}

The program uses a very simple way to check the serial number. The is_registered() function compares the first symbol of the serial number with ‘X’ and thinks the number is correct if this they match. For an incorrect serial number, a registration message is displayed, while if a user enter the correct key, “We are registered.” is shown instead.

Step 1.2: Adding the license checking code

Include VMProtect SDK

If you haven’t do this before, it is time to include VMProtect SDK to your project. The SDK is three files: the header file (VMProtectSDK.h), the library file (VMProtectSDK32.lib) and the dll-file with implementation (VMProtectSDK32.dll). There are individual implementations of the library and the dll-file for 64-bit systems.

Put the dll-file, the header file and the library file to the working folder of our application, where the source files are, and include the header file to the main file:

#include <windows.h>
#include <stdio.h>
#include "VMProtectSDK.h"

Build the project and make sure it compiles and runs as before. The licensing system is inactive yet.

Sending a serial number to the licensing system

Now, right below the line with the serial number, we add a call to the SDK function of the licensing system:

char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity
int res = VMProtectSetSerialNumber(serial);
printf("res = 0x%08X\n", res);

If after you did this the program stops with an error saying the required dll-file is missing, make sure you put the corresponding DLL-file to the working folder of our application. In case of a successful execution, you should see the following message:

res = 0x00000002

2 corresponds to the SERIAL_STATE_FLAG_INVALID flag described in the API. This means the licensing system thinks our key is incorrect, which is pretty true, as we didn’t “explain” to the system which keys are correct, and which ones are not.

Specifying the “correct” serial number

In the test mode, the licensing system analyzes the VMProtectLicense.ini file and reacts to function calls in accordance with the specified settings. We will thoroughly review the file on later steps, and now we simply create such a file and add the following text there:

[TestLicense]
AcceptedSerialNumber=Xserialnumber 

Now, run our program again. If you still receive the “2″ error code, make sure the ini-file is located in the working folder of the app. This time we should receive “0″. That’s the sign that the licensing system accepted and approved the serial number. Now we can remove the is_registered() function from the code – the licensing system is now in charge for checking serial numbers:

#include <windows.h>
#include <stdio.h>
#include "VMProtectSDK.h"
int main(int argc, char **argv)
{
        char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity
        int res = VMProtectSetSerialNumber(serial);
        printf("res = 0x%08X\n", res);
        if (res)
        {
                printf("please register!\n");
                return 0;
        }
        printf("We are registered.\n");
        return 0;
}

Step 1.3: Retrieving serial number status flags

A handy function to print flags

First of all, we need a handy function to transform numeric values of flags to comprehensible statuses of a serial number. Here is the code of this function:

#define PRINT_HELPER(state, flag) if (state & flag) printf("%s ", #flag)
void print_state(INT state)
{
        if (state == 0)
        {
                printf("state = 0\n");
                return;
        }
        printf("state = ");
        PRINT_HELPER(state, SERIAL_STATE_FLAG_CORRUPTED);
        PRINT_HELPER(state, SERIAL_STATE_FLAG_INVALID);
        PRINT_HELPER(state, SERIAL_STATE_FLAG_BLACKLISTED);
        PRINT_HELPER(state, SERIAL_STATE_FLAG_DATE_EXPIRED);
        PRINT_HELPER(state, SERIAL_STATE_FLAG_RUNNING_TIME_OVER);
        PRINT_HELPER(state, SERIAL_STATE_FLAG_BAD_HWID);
        PRINT_HELPER(state, SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED);
        printf("\n");
}

Despite the size, function is really simple – checks all bit flags one by one and print all that are present in the status variable. Replace printf in the code after checking the serial number to the call to print_state, and make changes to the serial number we pass to the licensing system:

char *serial = "Xserialnumber1"; // we set the serial number directly in the code, for simplicity
int res = VMProtectSetSerialNumber(serial);
print_state(res);

Now, if we run this program, the following message will be printed to the console:

state = SERIAL_STATE_FLAG_INVALID
please register!

Now, we put the old key back by removing “1″ and run the program again:

state = 0
We are registered.

Now, as we can see status flags of a serial number, let’s move to retrieving flags and data from a serial number.

Retrieving serial number status

You can get the status of a serial number in three ways: by calling VMProtectSetSerialNumber(), by calling VMProtectGetSerialNumberState() or by calling VMProtectGetSerialNumberData() – status flags are put into one of fields of the structure. Each method is intended to use in specific time. The first check of the serial number is performed during installation. At this moment you should decline incorrect numbers, expired numbers, numbers in the black list and so on. Some limitations, for example, the maximum operation time of the program or serial number expiration date should also be checked in run-time. And the VMProtectGetSerialNumberState() method is the fastest and the most convenient way here. And if you need to receive complete information about the serial number, you can use the more powerful VMProtectGetSerialNumberData() function.

Step 1.4: Retrieving the name and the e-mail of a user

Let’s start with simple things. We want to get the name and the e-mail of a user from a serial number to show them in the About window (or anywhere else). To do this, we have to add two more lines to our ini-file:

[TestLicense]
AcceptedSerialNumber=Xserialnumber
UserName=John Doe
EMail=john@doe.com

And in the program, if the registration is successful, we obtain these data and output them to the screen:

VMProtectSerialNumberData sd = {0};
VMProtectGetSerialNumberData(&sd, sizeof(sd));
printf("name = %ls,\ne-mail = %ls\n", sd.wUserName, sd.wEMail);

The structure contains UNICODE data, so printf() uses %ls specifiers instead of %s. The program should print the following text on the screen:

state = 0
We are registered.
name = John Doe,
e-mail = john@doe.com

Step 1.5: Checking the expiration date of the serial number

Now put a new line to the ini-file in the following format: ExpDate=YYYYMMDD. For example:

ExpDate=20000101

The date specified in this line must be already passed, that is, the maximum date is yesterday. When we run the program, we should see the following:

state = SERIAL_STATE_FLAG_DATE_EXPIRED
please register!

Now let’s get some more information before the “please register” message is shown and the program exists:

if (res)
{
        VMProtectSerialNumberData sd = {0};
        VMProtectGetSerialNumberData(&sd, sizeof(sd));
        printf("exp. date: y = %d, m = %d, d = %d\n", sd.dtExpire.wYear, sd.dtExpire.bMonth, sd.dtExpire.bDay);
        printf("please register!\n");
        return 0;
}

The second run of the app now provides more details to us:

state = SERIAL_STATE_FLAG_DATE_EXPIRED
exp. date: y = 2000, m = 1, d = 1
please register!

Ok, now remove the ExpDate=… line from the ini-file, so it will not influence everything else we are to do.

Step 1.6: Limiting the operation time of the program

You can limit the program operates since the moment it is started. This can be useful for demonstration purposes: you provide a real serial number to a user, but the program works no longer than 5 minutes. The licensing system doesn’t force such a program to shut down, but merely sets the status flag. So, let’s set a maximum working time of one minute, by adding the following line to the ini-file:

TimeLimit=1

And modify the program as follows:

int main(int argc, char **argv)
{
        char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity
        int res = VMProtectSetSerialNumber(serial);
        print_state(res);
        if (res) return 0;
        VMProtectSerialNumberData sd = {0};
        VMProtectGetSerialNumberData(&sd, sizeof(sd));
        printf("I will run for %d minute(s)\n", sd.bRunningTime);
        print_state(VMProtectGetSerialNumberState());
        Sleep(60 * 1000 * sd.bRunningTime);
        printf("After %d minute(s):\n", sd.bRunningTime);
        print_state(VMProtectGetSerialNumberState());
        return 0;
}

The program prints the status of the serial number upon start up, then calculates the maximum operating time and waits it to expire. Then the serial number status is printed again. With the maximum operation time set to one minute we should receive the following result:

state = 0
I will run for 1 minute(s)
state = 0
After 1 minute(s):
state = SERIAL_STATE_FLAG_RUNNING_TIME_OVER

The protected program should analyze the status of a serial number periodically and shut down if the flag is set. The licensing system does not do this automatically, because the program may need to free memory, save data to a file and so on. Also, you may want the program to not stop after the operation time has expired, but instead switch to a more restricted mode. The licensing system leaves this up to the developer.

Step 1.7: Limiting the free upgrades period

How it works

When VMProtect protects an application it records the date. The licensing system treats this date as a build date of the application. And you can put into a serial number the maximum build date this serial number can work with. Therefore, if you put the current date plus one year to the serial number, it will work with all versions of your programs you will be releasing in a year. A version you release one year and a day after will not work with this serial number, and a user will have a choice: use older version of your program or purchase a new key to work with the latest version of the program for one more year.

Let’s try it

Put the line formatted as MaxBuildDate=YYYYMMDD into the ini-file:

MaxBuildDate=20000101

In the test mode, the licensing system treats today as the build date, so it is important that the date specified in this line already passed. That is, the maximum date is yesterday. Modify the code of the main() function so that it looked like this:

int main(int argc, char **argv)
{
        char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity
        int res = VMProtectSetSerialNumber(serial);
        print_state(res);
        if (res)
        {
                VMProtectSerialNumberData sd = {0};
                VMProtectGetSerialNumberData(&sd, sizeof(sd));
                printf("max. build date: y = %d, m = %d, d = %d\n", sd.dtMaxBuild.wYear, sd.dtMaxBuild.bMonth, sd.dtMaxBuild.bDay);
                printf("please register!\n");
                return 0;
        }
        printf("I'm registered\n");
        return 0;
}

Then, upon program run you should see the following:

state = SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED
max. build date: y = 2000, m = 1, d = 1
please register!

By replacing the date in the ini-file to today or tomorrow, we end up with the “working” program:

state = 0
I'm registered

Remove the MaxBuildDate=… line from the ini-file so that it would not influence our further steps.

Step 1.8: Serial numbers in the black list

A serial number marked in VMProtect as “blocked” should not be accepted by the licensing system. When you will rebuild your application next time, VMProtect will add the hash of blacklisted serial numbers to the protected application. As a result, the licensing system of the application will decline these serial numbers in the future.

Firstly, lets minimize the contents of the main() function:

int main(int argc, char **argv)
{
        char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity
        int res = VMProtectSetSerialNumber(serial);
        print_state(res);
        return 0;
}

Now, run the program and make sure the licensing system do accept our serial number:

state = 0

Now, add this serial number to the black list of the licensing system. Add the following line to the ini-file:

BlackListedSerialNumber=Xserialnumber

And run the program again:

state = SERIAL_STATE_FLAG_BLACKLISTED

Should we inform a user that the serial number he or she enters is blacklisted? It is up to you. You can simply tell the serial number is incorrect or inform the user that the key is compromised. The licensing system simply informs the program about the fact of using the blacklisted serial number.

Step 1.9: Hardware lock

Receiving a hardware identifier

Before we lock to hardware, we must receive a hardware identifier. The identifier is put into a serial number, and when the number is passed to the licensing system, it checks if identifiers match. So, first we need to receive the identifier of our hardware. Let’s reduce the main() function to the bare minimum:

int main(int argc, char **argv)
{
        int nSize = VMProtectGetCurrentHWID(NULL, 0);
        char *buf = new char[nSize];
        VMProtectGetCurrentHWID(buf, nSize);
        printf("HWID: %s\n", buf);
        delete [] buf;
        return 0;
}

By running the program, we receive a default test hardware identifier:

HWID: myhwid

To change the identifier, add the following line to the ini-file:

MyHWID=test

If we run the program afterwards, we can see the system thinks “test” is a hardware identifier of our PC:

HWID: test

Important!

The program will display the real hardware identifier only after it is processed with VMProtect.

Hardware-locked serial number

To lock our test serial number to hardware, we should add one more line to the ini-file. This time we define the identifier that is “put into” the serial number:

KeyHWID=test

Then we complicate main() back a bit. Now it will pass a serial number and analyze the result it gets:

int main(int argc, char **argv)
{
        int nSize = VMProtectGetCurrentHWID(NULL, 0);
        char *buf = new char[nSize];
        VMProtectGetCurrentHWID(buf, nSize);
        printf("HWID: %s\n", buf);
        delete [] buf;
        char *serial = "Xserialnumber";
        int res = VMProtectSetSerialNumber(serial);
        print_state(res);
        return 0;
}

After running the code we will see the following result:

HWID: test
state = 0

The licensing system has compared the current hardware identifier with the one written in the serial number. Identifiers are equal, so the VMProtectSetSerialNumber() function returned 0 – the serial number matches.

Now let’s try to “run” our program on another hardware. We simply change the value of the MyHWID parameter in the ini-file from “test” to “new test”. Run the program again:

HWID: new test
state = SERIAL_STATE_FLAG_BAD_HWID

This time the licensing system returned the SERIAL_STATE_FLAG_BAD_HWID flag, which means the real hardware identifier and the one stored in the serial number are not matching. The current identifier we see on the screen is “new test”, while the serial number holds “test”. If we change the KeyHWID parameter in the ini-file to “new test” we can make our serial number work on this “hardware” too.

Step 1.10: User data

A serial number can hold up to 255 bytes of arbitrary data that the licensing system passes to the program as they are. The data can hold any additional information about the sale, data required for operation of the full version or something else. Let’s modify our main() function so that it would read data from a serial number and display them on the screen:

int main(int argc, char **argv)
{
        char *serial = "Xserialnumber";
        int res = VMProtectSetSerialNumber(serial);
        print_state(res);
        if (res) return 0;
        VMProtectSerialNumberData sd = {0};
        VMProtectGetSerialNumberData(&sd, sizeof(sd));
        printf("Serial number has %d byte(s) of data\n", sd.nUserDataLength);
        for (int i = 0; i < sd.nUserDataLength; i++)
                printf("%02X ", sd.bUserData[i]);
        printf("\n");
        return 0;
}

We also reduce the Ini-file to this:

[TestLicense]
AcceptedSerialNumber=Xserialnumber 

Now, we run the program and make sure our serial number works well, but doesn’t contain any dаta:

state = 0
Serial number has 0 byte(s) of data

To add new user data into the serial number, we need to create the UserData variable in the ini-file and assign data to it in the HEX format. Symbols must go in pairs, that is the length of a line must be a multiple of 2. Like this:

UserData=010203A0B0C0D0E0

In this case, if we runthe program we will receive the following result:

state = 0
Serial number has 8 byte(s) of data
01 02 03 A0 B0 C0 D0 E0

Step 2.1: Creating a new protected application

At the first stage we made several simple apps to test the API of the licensing system. Now, on the second stage we’ll create just one application. It will also be a console app with the foo() function working only in the registered version. Here is the code of our test application:

#include <windows.h>
#include <stdio.h>
#include "VMProtectSDK.h"
#define PRINT_HELPER(state, flag) if (state & flag) printf("%s ", #flag)
void print_state(INT state)
{
        if (state == 0)
        {
                printf("state = 0\n");
                return;
        }
        printf("state = ");
        PRINT_HELPER(state, SERIAL_STATE_FLAG_CORRUPTED);
        PRINT_HELPER(state, SERIAL_STATE_FLAG_INVALID);
        PRINT_HELPER(state, SERIAL_STATE_FLAG_BLACKLISTED);
        PRINT_HELPER(state, SERIAL_STATE_FLAG_DATE_EXPIRED);
        PRINT_HELPER(state, SERIAL_STATE_FLAG_RUNNING_TIME_OVER);
        PRINT_HELPER(state, SERIAL_STATE_FLAG_BAD_HWID);
        PRINT_HELPER(state, SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED);
        printf("\n");
}
char *read_serial(const char *fname)
{
        FILE *f;
        if (0 != fopen_s(&f, fname, "rb")) return NULL;
        fseek(f, 0, SEEK_END);
        int s = ftell(f);
        fseek(f, 0, SEEK_SET);
        char *buf = new char[s + 1];
        fread(buf, s, 1, f);
        buf[s] = 0;
        fclose(f);
        return buf;
}
// The foo() method is very short, but we need it to be an individual function
// so we asked the compiler to not compile it inline
__declspec(noinline) void foo()
{
        printf("I'm foo!\n");
}
int main(int argc, char **argv)
{
        char *serial = read_serial("serial.txt");
        int res = VMProtectSetSerialNumber(serial);
        delete [] serial;
        if (res)
        {
                printf("serial number is bad\n");
                print_state(res);
                return 0;
        }
        printf("serial number is correct, calling foo()\n");
        foo();
        printf("done\n");
        return 0;
}

Compile the program without the debug information, but in the linker settings we enable creating of the MAP-file – we will need it to work with VMProtect. After we run the program, we should see the following text:

serial number is bad
state = SERIAL_STATE_FLAG_INVALID

Currently, the licensing system still works in the test mode, because the file wasn’t processed by VMProtect and doesn’t contain a licensing module in it.

Step 2.2: Creating a VMProtect protection project

Now, as our test app is ready, compiled and has an assigned MAP-file in the same folder, we can run VMProtect Ultimate and open the executable file. We need to add two functions to the project: _main (this is how Visual Studio renamed our main()) and foo(). Both functions can be seen in the list of functions in the “Functions” section in VMProtect.

Then, we need to initialize the licensing system. Open the "Licenses" section and create a pair of keys with the length of 2048 bit.

Step 2.3: First start of the protected product

The licensing system is initialized, so let’s try to compile the VMProtect project and run the protected file. After running it from the command line we will receive the following message:

C:\test>dummy_app.vmp.exe
serial number is bad
state = SERIAL_STATE_FLAG_INVALID

If you run depends.exe and can see that our protected executable file doesn’t use the VMProtectSDK.dll any more. This means the licensing module is already built into the program. You can also review the list of used DLL from VMProtect, in the “Details | Imports” section.

Our protected program reads a serial number from the serial.txt file. Since there is no such file yet, the licensing module receives an empty serial number that is interpreted as incorrect. Now we switch to the “Licenses” section and generate a serial number. This procedure is described here in all details, and now we merely create a simple serial number without any limitations.

Then, we copy the serial number (select the “Serial number” field in the license properties and precc Ctrl+C), create a file named serial.txt in the same folder as the protected application, and paste the copied number there. Now, if we run our application we will see this:

C:\test>dummy_app.vmp.exe
serial number is correct, calling foo()
I'm foo
done

The licensing system checked the serial number and found it correct.

Step 2.4: Testing the results

Serial number expiration date

Let’s create another serial number with a certain expiration date. For example, 2005. This date has already passed and therefore our serial number must be incorrect. Switch to the “Licenses” section and click the “Add license” button on the toolbar. In the “Add license” dialog window enable the “Expiration date” option and specify September 30, 2005. Create the serial number, copy it and paste to serial.txt, then run the program:

C:\test>dummy_app.vmp.exe
serial number is bad
state = SERIAL_STATE_FLAG_DATE_EXPIRED

the licensing module returned the “serial number is expired” flag. Now, put the working serial number back to the serial.txt file and make sure the licensing module accepts it perfectly.

C:\test>dummy_app.vmp.exe
serial number is correct, calling foo()
I'm foo
done

Adding a serial number to the black list

Let’s imagine our “good” serial number has leaked to the Internet and is compromised now. We need to block it so that it will not work in future versions of the program. To do this, select the serial number in the list and set the “Blocked” property in the main panel to “Yes”. For now, the serial number is not yet blocked, but when you protect the file again, the application will not accept this number any more. Let’s make sure this is really so. If we run our program now, it should accept the blocked serial number without any problems, because this is the old version that knows nothing about the blocked number:

C:\test>dummy_app.vmp.exe
serial number is correct, calling foo()
I'm foo
done

Now we make a copy of our program and name it as “dummy_app1.vmp.exe”, then open VMProtect and protect the application again. Then run this new version:

C:\test>dummy_app.vmp.exe
serial number is bad
state = SERIAL_STATE_FLAG_BLACKLISTED

And the old version again, for comparison:

C:\test>dummy_app1.vmp.exe
serial number is correct, calling foo()
I'm foo
done

The older version doesn’t know about the blocked serial number and works as before.

On the next step we will try to lock the code to a serial number. But before we proceed, unblock the serial number and reapply protection in VMProtect to the application to make it accept this serial number again. Or simply create a new license.

Step 2.5: Locking the code to a serial number

One of the most common ways to crack programs is to locate the place where the serial number is checked and the nearby conditional jump that follows it. If the serial number is correct, the execution of the program goes one way, if not – the other way. A hacker locates this jump and replaces it with a jump to the “correct” way. Let’s “crack” our test program using this technique. Directly in the source code, of course. Let’s “switch off” our conditional jump:

char *serial = read_serial("serial.txt");
int res = VMProtectSetSerialNumber(serial);
delete [] serial;
if (false && res)
{

Now, our program accepts any serial number and works normally. Of course, if the file is protected with VMProtect, even an experienced hacker would spend months to locate and modify the conditional jump as we did it. And taking into account the program check the serial number multiple times and under different conditions, even such a simple check is quite secure. But let’s go further.

Locking the code to a serial number

The licensing system of VMProtect allows you to lock the code of one or more functions to a serial number so, that they will not work without the correct serial number provided. the body of the function is virtualized, then encrypted and can only be decrypted with the correct serial number. This means, even if a hacker finds and fixes the conditional jump in the serial number check, functions locked to the serial number still will not work. Let’s try this. In the “Functions” section choose the foo() function and at the right panel change the “Lock to Serial Number” option to “Yes”.

Then, protect the application. Since, we already “hacked” it, put an arbitrary text into the serial.txt file and run the application. The following text appears in the console:

C:\test>dummy_app.vmp.exe
serial number is correct, calling foo()

This means, the hacker “fixed” the conditional jump, and the program runs on the “correct” way. But when the foo() is invoked, the program displays a message:

Since we locked the foo() function to the serial number, and the hacker does not have it, an attempt to decrypt the code of the function resulted in malfunction and inability to continue execution of the program. When “OK” is pressed, the program shuts down and the “done” message is never displayed in the console.

What should be locked to a serial number?

It makes sense to lock to a serial number a function that should only run in the registered version of the program. Since locking requires virtualization, you should take into account some loss of performance. For instance, if a text editor does not allow saving result in a demo-version, you can lock the save document function to a serial number. If during its operation this function calls other functions, it is not necessary to lock them too, as they won’t be of any use without the main function.

You should also remember that invoking the locked function without the serial number leads to program shut down, without a chance to save result of the work. that is why you should thoroughly test the application to make sure it doesn’t calls such functions in the trial mode. In the above example, the text editor must disable the “Save” command in the demo mode and do not react on Ctrl+S shortcut as well. Of course, it also shouldn’t ask to save the document on exit too. If you don’t pay attention to this, a user may be disappointed with your “buggy” demo-version.

Locking to a serial number and invalid serial numbers

When the VMProtectSetSerialNumber() function is invoked, the licensing module checks the serial number passed to this function. Encrypted fragments of the code are only executed if the serial number was absolutely correct at the moment of check – not blacklisted, with the correct hardware identifier, not expired and so on. In this case all encrypted procedures are executed until the application is closed, or VMProtectSetSerialNumber() is invoked again.

Some limitations can “trigger” during execution of the program: for example, the operating time of the program may expire or the serial number expiration date comes. In this case the licensing module still encrypts and executes functions locked to the serial number. This is so, because it is hard for the protected application to detect the moment those limitations trigger and change the behaviour accordingly (block corresponding menu items and so on). If the licensing module suddenly stops execution of the code fragments that are locked to the serial number, this will very likely lead to malfunction of the application. That is why the decision is made when a serial number is set, and the corresponding execution mode is selected.

Licensing API functions

The licensing system API is an integral part of VMProtect API and its SDK. API allows you to specify a serial number and retrieve all information about it: whether it suits the program or not, is the serial number expired, the name this product is registered to and so on. Also, the API provides a hardware identifier of the computer the program runs on.

VMProtectSetSerialNumber

This function loads a serial number to the licensing system. Call syntax:

int VMProtectSetSerialNumber(const char *SerialNumber);

The input SerialNumber parameter must contain a pointer to a null-terminated string (‘\0′) containing a base-64 encoded serial number. The function returns a bit mask of serial number status flags, the same as the one VMProtectGetSerialNumberState() returns. You can read more about flags below. The serial number is “good” if the function returned 0.

VMProtectGetSerialNumberState

This function returns status flags for the serial number specified by a call to VMProtectSetSerialNumber().

int VMProtectGetSerialNumberState();

If at least one flag is set, there is a problem with the serial number. The program shouldn’t work if at least one bit is set. The detailed description of flags and their values is listed in the table below:

Flag Value Description
SERIAL_STATE_FLAG_CORRUPTED 0x01 The licensing system is corrupted. Possible reasons are: incorrect setup of the protection project, cracking attempt.
SERIAL_STATE_FLAG_INVALID 0x02 The serial number is incorrect. The flag is set if the licensing system cannot decrypt the serial number.
SERIAL_STATE_FLAG_BLACKLISTED 0x04 The serial number matches the product, but is black listed in VMProtect.
SERIAL_STATE_FLAG_DATE_EXPIRED 0x08 The serial number is expired. You can obtain the detailed information about the expiration date by calling VMProtectGetSerialNumberData()
SERIAL_STATE_FLAG_RUNNING_TIME_OVER 0x10 Operating time of the program is depleted. You can obtain the detailed information about the operating time of the program by calling VMProtectGetSerialNumberData()
SERIAL_STATE_FLAG_BAD_HWID 0x20 Hardware identifier does not match the hardware identifier prescribed in the key.
SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED 0x40 The serial number does not match the current version of the protected program. You can obtain the maximum build date of the program this serial number matches by calling VMProtectGetSerialNumberData().

VMProtectGetSerialNumberData

This function obtains information about contents of the serial number acquired with a call to VMProtectSetSerialNumber(). Call syntax:

bool VMProtectGetSerialNumberData(VMProtectSerialNumberData *Data, int Size);

The first parameter is a pointer to the VMProtectSerialNumberData structure, where all necessary information will be written to. The second parameter is the size of the structure passed in the first parameter. It is required to control the structure’s format. The function returns FALSE if the licensing system is corrupted (see the SERIAL_STATE_FLAG_CORRUPTED flag), if a zero address of the structure is provided or if the passed size of the structure is incorrect. In all other cases, the function returns TRUE and records all information about the serial number to the provided address. Below are the elements of the structure:

Element Type Description
nState int A bit flag mask indicating the status of a key. Similar to the one returned by VMProtectGetSerialNumberState().
wUserName wchar_t[256] The name of a customer in UNICODE, null-terminated.
wEMail wchar_t[256] The e-Mail of a customer in UNICODE, null-terminated.
dtExpire VMProtectDate The key expiration date. The format of the VMProtectDate structure is described below.
dtMaxBuild VMProtectDate The maximum product build date the given key can work with. The format of the VMProtectDate structure is described below.
bRunningTime int The amount of minutes the program will work (maximum duration of a session). The value in minutes counts from the moment the program starts.
nUserDataLength unsigned char The length of user data in the bUserData field.
bUserData unsigned char[255] User data put into the key. The actual number of bytes is specified in nUserDataLength.

VMProtectDate

The structure is a compact representation of date. Its fields are listed in the table below:

Element Type Description
wYear unsigned short Year.
bMonth unsigned char Month, starts from 1.
bDay unsigned char Day, starts from 1.

VMProtectGetCurrentHWID

This function obtains a hardware identifier of the PC the program is working on. Call syntax:

int VMProtectGetCurrentHWID(char * HWID, int Size);

The first parameter is a pointer to a memory area where the identifier is written to. The second parameter is the size of this area. The function returns the number of bytes written inclusive of the trailing zero byte (‘\0′). If NULL is provided in the first parameter, the function returns the number of bytes required to store the hardware identifier. Here is the correct way to use the function:

int nSize = VMProtectGetCurrentHWID(NULL, 0); // get the required buffer size
char *pBuf = new char[nSize]; // allocate memory for the buffer
VMProtectGetCurrentHWID(pBuf, nSize); // obtain the identifier
// use the identifier
delete [] pBuf; // release memory

Serial number generators

What are they for?

Apart from VMProtect, other software can generate serial numbers too. This is necessary to automate sending serial numbers. A customer purchases the product, an e-commerce agent sends an HTTP query to the website of the vendor, the generator runs on the server and produces a serial number based on customer’s data. The serial number is sent to the customer and to the vendor. The vendor then adds the serial to VMProtect manually using the import license dialog.

How it works

The licensing system of VMProtect is based on asymmetric algorithms, that is why a secret product key is required to generate a serial number. You can export this key in the project properties window and pass it to the generator in any suitable way.

The generator is called by the e-commerce agent using an HTTP query. A PHP generator can be called directly, a DLL-based generator – indirectly, but the principle is the same:

  • Receive user data from the e-commerce agent
  • Add all required information specified by the vendor
  • Generate a serial number
  • Encrypt it with one of algorithms
  • Send the result to the e-commerce agent

Are there any existing generators?

The licensing system comes with three ready to use serial number generators as DLL, for the .Net platform and on PHP.

Can I make my own generator?

Yes, you can. The format of the serial number is here, serial number encryption algorithms are described here.

Is it safe?

Generally, yes, this is safe. However, you should follow these recommendations:

  • Use HTTPS – if your e-commerce provider can send HTTPS queries, and your web hosting provider can answer such requests – you should prefer this variant over the typical HTTP, because in this case all data are transmitted in the encrypted form and the generated serial number cannot be intercepted.
  • “Hide” your generator – make sure no one can open the generator occasionally. The www.site.com/keygen.php address is a bad idea. While www.site.com/abc123.php is much better. Make sure you do not put any external links to the key generator, it isn’t listed in website directories, and don’t put it into any service file like robot.txt. The less is known about the location of the generator, the better. Optionally, you can even place the generator on another website.
  • Make sure it is the e-commerce agent who calls the generator – the program processing queries from the agent should check the IP address of the caller. E-commerce providers usually publish the IP-range the use to query serial number generators. Find that list at your agent and add a check to the program. If IP address sending a query lies beyond the specified IP range, do not produce comprehensible error messages. Either do not return anything or produce a simple 404. Do not give any clues to why the query has failed.
  • Check input parameters – product settings in the e-commerce agent’s control panel usually allow you to specify a query string the agent should make to receive a license. For instance, you want to receive user name, e-mail address, date of purchase and the order id. So make sure all these parameters are passed and all of them have the correct format. Do not produce any response to erroneous queries. Send a message to your own e-mail whenever an erroneous query to the generator is made. This should help to investigate the issue.
  • Add a “password” specify an additional parameter in the query sent by the e-commerce agent, a password. It should have an non-obvious name and value. Check this parameter from the receiving side. If the value is wrong, or the parameter is not specified – do not generate a serial number.

Windows-version

Description

Windows key generators are DLL-files for x86 and x64 platforms, a C language header file and an MSVC-compatible lib-file. Therefore, the library can be both linked statically and loaded dynamically.

All files of the generator are located in the %Examples%\Keygen\DLL folder. A test application generating serial numbers is also there.

Generator API

The generator exports just two functions: the first one generates a serial number, while the second one frees up memory allocated by the first one. Let’s start with the first and the main one:

VMProtectErrors __stdcall VMProtectGenerateSerialNumber(
                                VMProtectProductInfo * pProductInfo, 
                                VMProtectSerialNumberInfo * pSerialInfo, 
                                char ** pSerialNumber
                        );

The first parameter is a pointer to the VMProtectProductInfo structure, which contents are uploaded to VMProtect (see Exporting product parameters). The structure contains product private key, the algorithm used and the identifier of the product. More details on filling this structure follow below.

The second parameter is a pointer to the VMProtectSerialNumberInfo structure, which contents are moved to the generated serial number. The structure holds all fields of a serial number and a bit mask that defines which fields should be written to the serial number.

struct VMProtectSerialNumberInfo
{
        INT              flags;
        wchar_t *        pUserName;
        wchar_t *        pEMail;
        DWORD            dwExpDate;
        DWORD            dwMaxBuildDate;
        BYTE             nRunningTimeLimit;
        char *           pHardwareID;
        size_t           nUserDataLength;
        BYTE *           pUserData;
}; 

The flags field contains bit flags from the VMProtectSerialNumberFlags set described before the structure:

  • HAS_USER_NAME – put the user name from the pUserName variable into the serial number.
  • HAS_EMAIL – put the e-mail from the pEMail variable into the serial number.
  • HAS_EXP_DATE – the serial number will expire after the date specified in the dwExpDate variable.
  • HAS_MAX_BUILD_DATE – the serial number will only work with version of the product built up to the date specified in the dwMaxBuildDate variable.
  • HAS_TIME_LIMIT – the program stops working after the time specified in the nRunningTimeLimit variable expires (the time is specified in minutes and shouldn’t exceed 255).
  • HAS_HARDWARE_ID – the program works only on hardware with the id specified in the pHardwareID variable.
  • HAS_USER_DATA – put custom user data of nUserDataLength length at the address of pUserData to the serial number.

The third parameter is a pointer to a pointer. The address of the generated serial number is written there. After generating the serial number, it should be copied, and the address must be passed to the second API function of the generator that will free memory taken by the serial number.

void __stdcall VMProtectFreeSerialNumberMemory(char * pSerialNumber);

The VMProtectGenerateSerialNumber function returns a VMProtectErrors value that either contains 0 if the serial number is successfully generated, or an error code. Possible error codes are:

  • ALL_RIGHT – no errors, the serial number is generated.
  • UNSUPPORTED_ALGORITHM – an incorrect key encryption algorithm is passed in the first parameter of the function.
  • UNSUPPORTED_NUMBER_OF_BITS – an incorrect number of bits is passed in the first parameter of the function.
  • USER_NAME_IS_TOO_LONG – the length of the UTF-8 encoded user name exceeded 255 byte.
  • EMAIL_IS_TOO_LONG – the length of the UTF-8 encoded user e-mail exceeded 255 byte.
  • USER_DATA_IS_TOO_LONG – the length of the user data exceeded 255 byte.
  • HWID_HAS_BAD_SIZE – the hardware identifier has incorrect size.
  • PRODUCT_CODE_HAS_BAD_SIZE – the identifier of the product passed in the first parameter of the function has incorrect size.
  • SERIAL_NUMBER_TOO_LONG – the serial number is too long and can’t fit the number of bits specified in the algorithm.
  • BAD_PRODUCT_INFO – the first parameter of the function is incorrect or NULL.
  • BAD_SERIAL_NUMBER_INFO – the second parameter of the function is incorrect or NULL.
  • BAD_SERIAL_NUMBER_CONTAINER – the third parameter of the function doesn’t point to memory to write the serial number address to.
  • NOT_EMPTY_SERIAL_NUMBER_CONTAINER – the third parameter of the function doesn’t point to an empty memory cell, the cell must be NULL.
  • BAD_PRIVATE_EXPONENT – the first parameter of the function contains an incorrect value of the private exponent.
  • BAD_MODULUS – the first parameter of the function contains an incorrect value of modulus.

Errors can be of two categories: those caused by incorrect parameters or incorrect value of the first parameter, and all the rest. First category errors are rare and they indicate incorrect configuration of the structure. You should upload the product information again and check if the structure is filled correctly. An example of a proper filling of the structure can be found below.

Second category errors are caused by an attempt to put more data to the key than it can hold with its size. In this case we recommend to send a message to the e-commerce provider containing a text like “The key will be sent in 24 hours” instead of the actual serial number, and send all the required information to your own e-mail. In this case, the key is generated manually in VMProtect, some data are truncated to fit all crucial information to the maximum key size.

Example of usage

Below is a code example that calls the above functions and generates a serial number. Notice the block of code in the very beginning. The example will not work until you replace it with the one exported from VMProtect for your product:

//////////////////////////////////////////////////////////////////////////
// !!! this block should be generated by VMProtect !!!                 ///
//////////////////////////////////////////////////////////////////////////
VMProtectAlgorithms g_Algorithm = ALGORITHM_RSA;
size_t g_nBits = 0;
byte g_vModulus[1];
byte g_vPrivate[1];
byte g_vProductCode[1];
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
int _tmain(int argc, _TCHAR* argv[])
{
        VMProtectProductInfo     pi;
        pi.algorithm = g_Algorithm;
        pi.nBits = g_nBits;
        pi.nModulusSize = sizeof(g_vModulus);
        pi.pModulus = g_vModulus;
        pi.nPrivateSize = sizeof(g_vPrivate);
        pi.pPrivate = g_vPrivate;
        pi.nProductCodeSize = sizeof(g_vProductCode);
        pi.pProductCode = g_vProductCode;
        VMProtectSerialNumberInfo si = {0};
        si.flags = HAS_USER_NAME | HAS_EMAIL;
        si.pUserName = L"John Doe";
        si.pEMail = L"john@doe.com";
        char * pBuf = NULL;
        VMProtectErrors res = VMProtectGenerateSerialNumber(&pi, &si, &pBuf);
        if (res == ALL_RIGHT)
        {
                printf("Serial number:\n%s\n", pBuf);
                VMProtectFreeSerialNumberMemory(pBuf);
        }
        else
        {
                printf("Error: %d\n", res);
        }
        return 0;
} 

This is an example project for Microsoft Visual Studio from %Examples%\Keygen\DLL\Example. Below are the most interesting parts of the code with our comments.

First lines of the main function fill the VMProtectProductInfo structure with data, exported from VMProtect. This code is typical and it shouldn’t be changed to avoid errors. Then we create the VMProtectSerialNumberInfo structure and insert a bit combination of user name and e-mail to the flag field. In the next line, we put the user name and the password to the appropriate fields in the structure. Note, values are accepted in the UNICODE encoding. The key generator will transform them to UTF-8.

Then, we initialize a pointer variable where the address of the generated key will be stored, and call VMProtectGenerateSerialNumber, then analyze the return code. If there are no errors, the generated key goes out to console, and a call to free serial number memory function is made.

VMProtectSerialNumberInfo Structure

Some fields of the structure may needs some additional explanations. For example, dwExpDate and dwMaxBuildDate fields contain dates in the specific format: 0xYYYYMMDD, that is, the year is stored in the high wordб while the month and the day are in the respectively high and low bytes of the low word. To produce such a number, the following macros is used:MAKEDATE(y, m, d). You can call it like this: MAKEDATE(2010, 05, 12).

The pHardwareID field should contain a pointer to a string the VMProtectGetCurrentHWID method from the licensing SDK has returned.

.Net-version

Description

The .Net-version of the key generator is a build containing all that is required to generate serial numbers. Source codes are in %Examples%\Keygen\Net as two projects: KeyGen (the key generator itself) and Usage (an example of use of the key generator).

The key generator is supplied in source codes for quick building under a given version of the .Net Framework, however we strongly do not recommend to apply any changes to the code. In future versions of VMProtect some new possibilities may be added to the generator, and that may lead to repeated modification of the code. Also, this may lead to errors that are very difficult to locate. If you found errors in the original code of the generator or would like to suggest improvements, please contact the support team.

Using the generator

Take the code from the Usage project as a base, then add to your project a link to the VMProtect.KeyGen.dll build. After that you will be able to generate serial numbers in your application. To function properly, the generator must “know” which product you generate serial numbers for. To achieve this, in VMProtect open the “Project | Export key pair” dialog and select the “Parameters for KeyGen.Net” option. The text area below will contain text information you should copy and paste to your application as a string constant.

Here is an example code to call the generator:

try
{
        string data = @""; // put the exported data here
        Generator g = new Generator(data);
        g.UserName = "John Doe";
        g.EMail = "john@doe.com";
        g.ExpirationDate = DateTime.Now.AddMonths(1);
        g.MaxBuildDate = DateTime.Now.AddYears(1);
        g.RunningTimeLimit = 15;
        g.HardwareID = "AQIDBAgHBgU=";
        g.UserData = new byte[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
        string serial = g.Generate();
        Console.WriteLine("Serial number:\n{0}\n", serial);
}
catch (Exception ex)
{
        Console.WriteLine("Error: {0}", ex);
}    

The string you copied from VMProtect should be placed to the data variable passed as a parameter to the serial number class constructor. In case of any problems occur while parsing the product data, the constructor throws an exception containing a description of the problem. If the constructor successfully finishes its work, the generator is ready to produce serial numbers.

A serial number can contain various information specified using the generator properties. The above example displays how to fill all and every field of a serial number. Certain fields have limitations. For example, the User Name and E-Mail cannot accept strings exceeding 255 symbols in the UTF-8 encoding. If incorrect data are supplied, properties throw exceptions containing a description of the problem.

When the generator setup is done, the Generate() method is invoked. This method generates a serial number. At this step, all data of the serial number are combined, the checksum is calculated and the data are encrypted. If the volume of data exceeds the allowed length, the method throws an exception.

If you need to generate multiple serial numbers, you can use the generator class several times in a row, without the need to create it from scratch. To clear any given property of the generator, simply assign a null value to it.

UNIX-version

Description

The UNIX-version of the key generator is a PHP file that contains all necessary information for serial number generation. The file is located in %Examples%\Keygen\PHP. Below we describe the key points of using such a generator.

Configuring the generator

In the beginning of the PHP file, the setup code is located:

//////////////////////////////////////////////////////////////////////////////////////////////
// The following lines should be generated by VMProtect License Manager
$exported_algorithm = "RSA";
$exported_bits = 2048;
$exported_private = "PJvj4kEpoQMIpYK+9wEt......xKeiSZgzdiln8Q==";
$exported_modulus = "rOlny/3QgZb/VmGr3CmY......I6ESAUmtQ+RBqQ==";
$exported_product_code = "oLQdGUn8kVk=";
//////////////////////////////////////////////////////////////////////////////////////////////

This code is automatically generated by VMProtect (see Exporting product parameters) and is unique for each particular product. It is crucial to copy it accurately, as otherwise the generator will work incorrectly.

Contents of a key

Then, the generator specifies the contents of a serial number The contents are specified in an array, with all possible parameters of the key listed below. However, in real applications, some of them may be omitted:

$params = array(
        user_name => "John Doe", // UTF-8!
        email => "john@doe.com",
        hwid => "vHGMdMRvGCPjWcCQ", // Exactly as returned by VMProtectGetCurrentHWID
        expire_date => array(year => 2009, month => 10, day => 1),
        maxbuild_date => array(year => 2009, month => 10, day => 1),
        time_limit => 10,
        user_data => base64_decode("CGCvRvMWcPHGdMjQ"), // string of bytes
        );

Successful key generation handler function

Below you can see a simplest function called upon successful generation of a serial number. The only parameter sent to it is a serial number string. The function must pass the serial number to the caller (an e-commerce agent), usually with the echo command. The string is preliminarily split to sub-strings 75 symbols each, for convenience. Also, this function could send the generated serial number by e-mail to the developer or add it to a database.

function OnSerialGenerated($serial)
{
        $serial = wordwrap($serial, 75, "\n", true);
        echo $serial;
}

Key generator error handler function

The final part of the code that needs our attention is a function called when something goes wrong. This function receives a string with the error message, and when it finishes, the die() function is called. The handler function must do two things: instead of a key, return to an e-commerce agent a message saying the key will be sent manually. And send to the developer an exhaustive information about the error to fix it and generate the key manually.

function OnSerialGenerationFailed($details)
{
        echo "You will receive serial number in the next 24 hours"; // message to the customer
//    mail("support@vendor.com", "Houston, we have a problem", $details); // message to vendor
}

There are several possible causes of mistakes: incorrect parameters of the algorithms, incorrect parameters of the key, a too long user name or e-mail, or a too long serial number that doesn’t fit to the number of bits specified in the algorithm. That is why the OnSerialGenerationFailed function must send a detailed information about the issue to the developer, so he could generate a serial number and send it to the customer.

Other things to consider

Examples hold a simplified version of the key generator. It doesn’t take into account recommendations to develop web generators. It does not check the IP address of the caller and does not analyze input parameters. Please pay attention to this when developing your own generator based on this one.

A user name and an e-mail must be passed as UTF-8 strings. Make sure your e-commerce agent sends these data in the UTF-8 encoding, or transcode the information if this is not so. Wrong encoding won’t lead to an incorrect serial number generated, but the registration name displayed for such a serial number can be different from the real user’s name, so he or she may be surprised to see it in the “About” window of the application

Asymmetric encryption is a complex mathematical process. If it is implemented using pure PHP without any third-party libraries, serial number generation can take dozens of seconds. The generator uses gmp_powm, bi_powmod, bcpowod functions whenever they are available. If serial number generation take too long on your hosting, we recommend to ask the hosting provider to enable these functions. For example, the gmp_powm function works ten of times faster than bcpowmod.

Serial number format

Serial number structure

The serial number consists of blocks. Each block starts from an identifier byte that indicates contents of the block and possibly its length. The last block always has the 255 identifier that contains a checksum of the serial number except the last block.

Depending on the type of the block, it can have constant or variable length. In the latter case, the length is specified in the byte following the block identifier. The exact format of each block is described below.

Block format

ID Name Size (byte) Description Example
0x01 Version 1 The block contains a version of the serial number, 1-byte sized. The version must be 1. 01 01
0x02 User name 1 + N The block contains a UTF-8 encoded user name. Before the user name, there is 1 byte holding the length of the name. Therefore, the total length of a user name must not exceed 255 bytes. No trailing 0 is required. 02 04 4A 5F 48 4E
0x03 E-Mail 1 + N The block contains a UTF-8 encoded e-mail of the user. Before the e-mail there is 1 byte holding the length. Therefore, the total length of an e-mail must not exceed 255 bytes. No trailing 0 is required. 03 07 61 40 62 2E 63 6F 6D
0x04 Hardware identifier 1 + N The block contains hardware identifier as returned by the VMProtectGetCurrentHWID() function. The function returns a base-64 string. A decoded version of the string is put to the serial number. The length of the data block must be a multiple of 4. There is a length byte before the data block. The maximum block length is 32 bytes. 04 08 E1 E2 E3 E4 A1 A2 A3 A4
0x05 License expiration date 4 The block contains serial number expiration date Date format is described below. 05 01 0A 07 DA
0x06 Maximum operation time 1 the block contains 1 byte holding the time in minutes the program can operate. Therefore, the maximum time can be 255 minutes. 06 05
0x07 Product code 8 The block contains a product code – 8 bytes created by VMProtect and exported with product parameters. These data are exported in the base-64 encoding and must be decoded to a byte array before putting to a serial number. The size of the array must be exactly 8 bytes. This block is obligatory! Without it, the protected program will not work correctly. 07 01 02 03 04 05 06 07 08
0x08 User data 1 + N The block contains up to 255 bytes of custom user data. The license system doesn’t analyze these data and will return them when the VMProtectGetSerialNumberData() function is called. Before the data block, there is a byte holding the size of the user data. 08 05 01 02 03 04 05
0x09 Maximum build date 4 The block contains the maximum build date of the application. The format is described below. 09 01 0A 07 DA
0xFF Checksum 4 The block contains the serial number checksum. The block is located before the last one, and the checksum is calculated for all previous blocks. The checksum calculation mechanism is described below. FF 01 02 03 04

Date storage format

Dates are stored in a serial number as a double word – 0xYYYYMMDD. The high order word contains the year, and the lower order word holds the day and the month. Bytes follow the Little Endian representation – from lower to higher. If there is a pointer to the first byte of the record, the date can be read or written with the following code:

byte *pDate = 0xNNNNNN; // date address
*(DWORD *)pDate = (2010 << 16) | (10 << 8) | 1; // October 1, 2010
DWORD dwExp = *(DWORD *)pDate;

Checksum calculation

The checksum of the serial number is calculated using the SHA-1 hashing algorithm. The result is five 32-bit words. The first word is used as a checksum for the serial number. Please note: the word is Little Endian (from lower to higher). The string representation of SHA-1 hash the Big Endian is used – numbers go from the higher byte to the lower one, so if the SHA-1 hash is generated by a string function (like in PHP), the first four bytes of the hash must be reversed.

Additional information

Block with numbers other than those specified above are ignored by the licensing system. New blocks may be added in newer versions. We do not recommend creating your own blocks using non-occupied identifiers! Firstly, this could render the key non-functional with newer version of the licensing system. Secondly, the protected program cannot read the values of theses blocks anyway. To store additional information in a key, use the User Data field instead. It was meant exactly for this purpose.

A serial number doesn’t have SALT, a random information intended to provide variability of keys based on the same input data. This task is imposed to the encryption algorithm. If you need differences on the serial number level, for example, when selling a series of keys to an organization, you can add individual indices to the user name field (“Company” LLC, key 1 of 10) or insert this information to the User Data field in any appropriate format.

Serial number encryption algorithms

The security of a key in the licensing system is based on asymmetric cryptography algorithms. The current version implements the RSA algorithm with the key length from 1024 to 16384 bits. Future versions are planned to implement other algorithms based on ECC as well as symmetric+asymmetric combined cryptography.

The algorithm used is unique for each product. Keys made with one algorithm cannot be used with another one, this means changing algorithms after at least one license is created is not allowed. The protection module in the protected program “knows” which algorithm the serial number is encrypted with and will not accept keys made with other algorithms or with the same algorithm but different parameters (say, different key length).

RSA Algorithm

A serial number is encrypted with the RSA algorithm as follows:

  • Adding random data to the beginning of the serial number – the method is based on RFC2313, but the implementation is slightly different. The following bytes are added to the beginning of the key: 00 02 NN…NN 00, where NN..NN are from 8 to 16 random non-zero bytes. The number of bytes is random, but the system takes into account the length of the key and its maximum capacity.
  • Adding random data to the end of the serial number – the total number of bytes in a serial number must be equal to the number of bits in keys of the algorithm divided by 8. The serial number is appended with the corresponding number of bytes holding random data. As a result, the following serial number format is produced: 00 02 NN..NN 00 DD..DD MM..MM, where NN is a set of random non-zero bytes, DD is the original serial number, MM is a set of random bytes (including zeros). The summary length of the sequence should be equal to the number of bits in keys of the algorithm divided by 8
  • Encryption goes using a typical procedure implemented in many libraries to process big numbers. The PHP generator contains all the required information.
  • Packing – the set of bytes obtained after encryption is encoded to base-64 — this is a serial number that goes to a customer.

Activation system

What is it?

Activation is a two-stage process of registering an application. On the first step (usually, directly after the purchase) a user receives an activation code that usually looks like XXXX-YYYY-ZZZZ. On the second stage, the customer enters the code to the application, the application connects to the server of the developer via the Internet, the server checks the activation code and returns a serial number to the application tied to the given activation code and usually locked to user’s hardware.

Such approach allows the developer to control license usage by customers, prevent licenses from leaking and offers additional possibilities, for example, making demo licenses or offering reliable trial periods.

Why do I need it?

The activation system gives a software developer a lot of possibilities, for instance:

  • Short serial numbers – long serial numbers are great, except they are long. User may make a typo while entering them, e-mail client break those keys or replace some symbols. At the same time, long serial numbers can hold a sufficient volume of additional information. Activation codes can resolve all issues of long serial numbers while taking the most benefit from their advantages.
  • Installation control – all activations are available to the developer online in real-time. Each activation can be monitored, analyzed, blocked. The developer can provide support to users and so on.
  • Trial periods – you can create time-limited serial numbers by using product “modes”.
  • Subscriptions – subscriptions are similar to trial periods, but are offered for a fee. You can sell the right to use the program for a specified time to a user, just like antiviruses do. Simply sell activation codes for each service year.

What do I need?

VMProtect Ultimate has several functions for relatively easy implementation of both online and offline activation. You will also need Web License Manager installed on your server. Finally, you should configure the protection project in VMProtect, to make the activation module know the URL of the WebLM server.

Configuring activation in VMProtect

For the Activation API to work, the WebLM URL is required, so you should specify it in the project settings in VMProtect. To do this, open VMProtect and switch to the options section:

Configuring the activation server

Enter the address to the “Activation Server” field. The address should look as: http://yourserver/weblm path/activate.php. This is the first place to check if you face problems with online activation.

Activation in Web License Manager

You can find the detailed Web License Manager description on our website, and here we merely provide minimum steps required to create an activation code for use with the Activation API.

First, enter Web License Manager (you can use the demo on our website) and create a product. Then export the product as a VMProtect project to be able to configure licensing and activation. After the project is set up, all check are added to the code of your app and the executable is protected, click the “Add New Code” link on the left panel in WebLM:

Select the product you need from the upper drop-down list and fill in the rest of form with the data you want to put into the serial number. Click the “Save” button. You should see the activation code you can use to debug Activation API.

You can study rich capabilities of Web License Manager (such as integration to e-commerce providers and automatic activation code generation) in the online user manual.

Activation API

Activation API contains only 4 functions. Two for online activation and other two for offline activation when the computer has no access to the Internet. Activation API is intended to work in cooperation with Web License Manager, so a developer still should invoke licensing system API to use serial numbers obtained from WebLM.

VMProtectActivateLicense

The function passes the activation code to the server and returns a serial number for this specific computer. Otherwise, an error code is returned.

int VMProtectActivateLicense(const char *code, char *serial, int size);

The code parameter holds the activation code obtained from Web License Manager during the license purchase process. The serial parameter specifies a memory block of the given size, where the serial number generated by WebLM is put to.

VMProtectDeactivateLicense

This function passes a serial number to the server for deactivation. Possible return codes are listed below.

int VMProtectDeactivateLicense(const char *serial);

The serial parameters contains the serial number (not the activation code) obtained from WebLM earlier during activation.

VMProtectGetOfflineActivationString

VMProtectGetOfflineDeactivationString

These two functions work similar to the previous two, except they do not try to connect to the WebLM server. Instead, they return a text block that a user should copy to a computer connected to the Internet, open the WebLM offline activation form and paste the text there.

int VMProtectGetOfflineActivationString(const char *code, char *buf, int size);
int VMProtectGetOfflineDeactivationString(const char *serial, char *buf, int size);

The code and serial parameters are similar to those of the online version of these functions. The buf parameter should point to a buffer of 1000 bytes or more where the text block for the offline activation form will be copied to. Possible error codes are listed below.

Possible error codes

Code Value Description
ACTIVATION_OK 0 Activation is successful. The serial number is put to the serial variable.
ACTIVATION_SMALL_BUFFER 1 The buffer is too small to hold the serial number. The minimum buffer size is calculated as: bits / 8 * 3 / 2 + N, where bits is the length of the RSA key in bits, and N is a “security constant” – additional bytes for possible line breaks and other special symbols. We recommend using at least 10.
ACTIVATION_NO_CONNECTION 2 The activation module couldn’t connect to Web License Manager.
ACTIVATION_BAD_REPLY 3 The activation server returned an unexpected result. This means some configuration issues on the server, wrong server URL or a hack attempt.
ACTIVATION_BANNED 4 This activation code is banned on the server by the software vendor via the WebLM interface (for example, if the key has leaked or pirated). Not to be confused with ACTIVATION_ALREADY_USED.
ACTIVATION_CORRUPTED 5 Something has gone completely wrong. The error is produced by the activation module self-check system and usually means a hacking attempt. If you’ve got this error, all further operations with serial numbers and activation are not safe.
ACTIVATION_BAD_CODE 6 The specified code is not found in the database of the activation server. Perhaps, a user made a mistake while entering the code, so we can ask him or her to check if everything all right.
ACTIVATION_ALREADY_USED 7 The activation counter for this code is depleted and all further activations are impossible. This doesn’t mean the code is bad or banned. The code is good, but it just can’t be activated any more. The user should contact the software vendor and purchase additional licenses or uninstall the software on other computer to increase the value of the activation counter on the server.
ACTIVATION_SERIAL_UNKNOWN 8 Activation error. The given serial number is not found in the database on the server. Therefore, deactivation is impossible.
ACTIVATION_EXPIRED 9 Activation error. Means the activation period of the code has expired.
ACTIVATION_NOT_AVAILABLE 10 This error means activation/deactivation is unavailable.

Tips and tricks

Activation API is pretty simple, so you shouldn’t experience difficulties with it. Do not forget to offer a way to activate the program offline for users who have problems with the Internet. Also, don’t forget, the Activation API does not save the serial number it receives and does not pass it to the licensing module – this should be done by the developer. You don’t have to call Activation API on every launch of the application. You just need to call it once, get a serial number from WebLM, save it where appropriate and then use this saved copy.

Using scripts

VMProtect has a built-in powerful script language LUA greatly enhancing the default protection capabilities of VMProtect at every stage of protection.

LUA syntax is very similar to that of jаvascript, but unlike it LUA doesn’t contain explicit classes. Nevertheless, the script language allows easily implementing such object-oriented programming mechanisms as classes, inheritance and events. Script usage examples can be found in the “VMProtect/Examples/Scripts” folder.

Classes

The script language LUA built into VMProtect is object-oriented: it is very similar to jаvascript in its syntax, ideology and implementation. The script language includes standard classes providing basic functionality and specialized classes giving access to application protection functionality.

Class hierarchy

Core

Project options:

enum ProjectOption {
	None,
	Pack,
	ImportProtection,
	MemoryProtection,
	ResourceProtection,
	CheckDebugger,
	CheckKernelDebugger,
	CheckVirtualMachine,
	StripFixups,
	StripDebugInfo,
	DebugMode
}

A class to work with the VMProtect core:

class Core {
public:
	string projectFileName(); // returns the name of the project
	void saveProject(); // saves the project
	string inputFileName(); // returns the name of the source file for the current project
	string outputFileName(); // returns the name of the output file for the current project
	void setOutputFileName(string name); // sets the name of the output file for the current project
	string watermarkName(); // returns the name of the watermark of the current project
	void setWatermarkName(string name); // sets the name of the watermark for the current project
	int options(); // returns options of the current project                                                            
	void setOptions(int options); // sets options of the current project
	string vmSectionName(); // returns VM segment name for the current project
	void setVMSectionName(); // sets VM segment name for the current project
	Licenses licenses(); // returns the list of licenses for the current project
	Files files(); // returns the list of files for the current project
	Watermarks watermarks(); // returns the list of watermarks
	PEFile/MacFile inputFile(); // returns source file
	PEFile/MacFile outputFile(); // returns output file
	PEArchitecture/MacArchitecture inputArchitecture(); // returns source architecture
	PEArchitecture/MacArchitecture outputArchitecture(); // returns output architecture
};

Watermarks

A class to work with the list of watermarks:

class Watermarks {
public:
	Watermark item(int index); // returns a watermark with the given index
	int count(); // returns a number of watermarks in the list
	Watermark itemByName(string name); // returns a watermark with the given name
}

A class to work with a watermark:

class Watermark {
public:
	string name(); // returns the name of the watermark
	string value(); // returns the value of the watermarks
	bool blocked(); // returns the "Blocked" property
	void setBlocked(bool value); // sets the "Blocked" property
}

Licenses

A class to work with the list of licenses:

class Licenses {
public:
	int keyLength(); // returns the length of the key
	string publicExp(); // returns the public exponent
	string privateExp(); // returns the private exponent
	string modulus(); // returns modulus
	License item(int index); // returns a license with the given index
	int count(); // returns the number of licenses in the list
}

A class to work with a license:

class License {
public:
	string date(string format = "%c"); // returns the date of the license
	string customerName(); // returns the name of the license owner
	string customerEmail(); // returns an e-mail of the license owner
	string orderRef(); // returns the order id the license was purchased
	string comments(); // returns comments to the license
	string serialNumber(); // returns the serial number of the license
	bool blocked(); // returns the "Blocked" property
	void setBlocked(bool value); // sets the "Blocked" property
}

Files

A class to work with the list of files:

class Files {
public:
	File item(int index); // returns a file with the given index
	int count(); // returns the number of files in the list
}

A class to work with a file:

class File {
public:
	string name(); // returns the name of the file
	string fileName(); // returns the filename
	int options(); // returns options
	void setName(string name); // sets the name of the file
	void setFileName(string name); // sets the filename of the file
	void setOptions(); // sets options
}

Folders

A class to work with custom folders:

class Folders {
public:
	int count(); // returns the number of folders in the list
	Folder item(int index); // returns a folder with the given index
	Folder add(string name); // adds a new folder
	void clear(); // clears the list
};

A class to work with a custom folder:

class Folder {
public:
	int count(); // returns the number of subfolders
	Folder item(int index); // returns a subfolder with the given index
	Folder add(string name); // adds a new subfolder
	string name(); // returns the name of the folder
	void clear(); // clears the list of subfolders
	void destroy(); // destroys the folder an all child subfolders
};

PE files

Constants to work with the PE format:

enum PEFormat {
	// Directory Entries
	IMAGE_DIRECTORY_ENTRY_EXPORT,
	IMAGE_DIRECTORY_ENTRY_IMPORT,
	IMAGE_DIRECTORY_ENTRY_RESOURCE,
	IMAGE_DIRECTORY_ENTRY_EXCEPTION,
	IMAGE_DIRECTORY_ENTRY_SECURITY,
	IMAGE_DIRECTORY_ENTRY_BASERELOC,
	IMAGE_DIRECTORY_ENTRY_DEBUG,
	IMAGE_DIRECTORY_ENTRY_ARCHITECTURE,
	IMAGE_DIRECTORY_ENTRY_TLS,
	IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG,
	IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT,
	IMAGE_DIRECTORY_ENTRY_IAT,
	IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT,
	IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR,
	// Section characteristics
	IMAGE_SCN_CNT_CODE,
	IMAGE_SCN_CNT_INITIALIZED_DATA,
	IMAGE_SCN_CNT_UNINITIALIZED_DATA,
	IMAGE_SCN_MEM_DISCARDABLE,
	IMAGE_SCN_MEM_NOT_CACHED,
	IMAGE_SCN_MEM_NOT_PAGED,
	IMAGE_SCN_MEM_SHARED,
	IMAGE_SCN_MEM_EXECUTE,
	IMAGE_SCN_MEM_READ,
	IMAGE_SCN_MEM_WRITE,
	// Resource types
	RT_CURSOR,
	RT_BITMAP,
	RT_ICON,
	RT_MENU,
	RT_DIALOG,
	RT_STRING,
	RT_FONTDIR,
	RT_FONT,
	RT_ACCELERATOR,
	RT_RCDATA,
	RT_MESSAGETABLE,
	RT_GROUP_CURSOR,
	RT_GROUP_ICON,
	RT_VERSION,
	RT_DLGINCLUDE,
	RT_PLUGPLAY,
	RT_VXD,
	RT_ANICURSOR,
	RT_ANIICON,
	RT_HTML,
	RT_MANIFEST,
	RT_DLGINIT,
	RT_TOOLBAR
};

A class to work with a PE file:

class PEFile {
public:
	string name(); // returns the filename
	string format(); // returns the "PE" format name
	uint64 size(); // returns the size of the file
	int count(); // returns the number of architectures in the list
	PEArchitecture item(int index); // returns an architecture with the given index
	uint64 seek(uint64 offset); // sets a file position
	uint64 tell(); // returns a file position
	int write(string buffer); // records a buffer to the file
};

A class to work with the PE architecture:

class PEArchitecture {
public:
	string name(); // returns the name of the architecture
	PEFile file(); // returns the parent file
	uint64 entryPoint(); // returns the starting address
	uint64 imageBase(); // returns the base offset
	OperandSize cpuAddressSize(); // returns bit count of the architecture
	uint64 size(); // returns the size of the architecture
	PESegments segments(); // returns the list of segments
	PESections sections(); // returns the list of sections
	PEDirectories directories(); // returns the list of directories
	PEImports imports(); // returns the list of imported libraries
	PEExports exports(); // returns the list of exported functions
	PEResources resources(); // returns the list of resources
	PERelocs relocs(); // returns the list of relocations (fixups);
	MapFunctions mapFunctions(); // returns the list of functions available for protection
	IntelFunctions functions(); // returns the list of protected functions
	bool addressSeek(uint64 address); // sets a file position
	uint64 seek(uint64 offset); // sets a file position
	uint64 tell(); // returns a file position
	int write(string buffer); // writes a buffer to a file
};

A class to work with the list of segments for the PE architecture:

class PESegments {
public:
	PESegment item(int index); // returns a segment with the given index
	int count(); // returns the number of segments in the list
	PESegment itemByAddress(uint64 address); // returns the segment at the given address
};

A class to work with a PE architecture segment:

class PESegment {
public:
	uint64 address(); // returns the address of the segment
	string name(); // returns the name of the segment
	uint64 size(); // returns the size of the segment
	int physicalOffset(); // returns the file position (offset) of the segment
	int physicalSize(); // returns the file size of the segment
	int flags(); // returns flags of the segment
	bool excludedFromPacking(); // returns the "Excluded from packing" property
	void setName(string name); // sets the name of the segment
};

A class to work with the list of PE architecture sections:

class PESections {
public:
	PESection item(int index); // returns a section with the given index
	int count(); // returns the number of sections in the list
	PESection itemByAddress(uint64 address); // returns a section at the given address
};

A class to work with a PE architecture section:

class PESection {
public:
	uint64 address(); // returns the address of the section
	string name(); // returns the name of the section
	uint64 size(); // returns the size of the section
	int offset(); // returns the file positions of the section
	PESegment segment(); // returns the parent segment
};

A class to work with PE architecture directories:

class PEDirectories {
public:
	PEDirectory item(int index); // returns a directory with the given index
	int count(); // returns the number of directories in the list
	PEDirectory itemByType(int type); // returns a directory of the given type
};

A class to work with a PE architecture directory:

class PEDirectory {
public:
	uint64 address(); // returns the address of the directory
	string name(); // returns the name of the directory
	uint64 size(); // returns the size of the directory
	int type(); // returns the type of the directory
	void setAddress(uint64 address); // sets the address of the directory
	void setSize(int size); // sets the size of the directory
	void clear(); // clears the address and the size of the directory
};

A class to work with the list of imported libraries for the PE architecture:

class PEImports {
public:
	PEImport item(int index); // returns a library with the given index
	int count(); // returns the number of libraries in the list
	PEImport itemByName(string name); // returns a library with the given name
};

A class to work with an imported library for the PE architecture :

class PEImport {
public:
	string name(); // returns the name of the library
	PEImportFunction item(int index); // returns an imported function with the given index
	int count(); // returns the number of imported functions
	void setName(string name); // sets the name of the library
};

A class to work with a PE architecture imported function:

class PEImportFunction {
public:
	uint64 address(); // returns a memory address where the imported function address is stored
	string name();  // returns the name of the imported function
};

A class to work with the list of exported functions for the PE architecture :

class PEExports {
public:
	string name(); // returns the name of the library
	PEExport item(int index); // returns an exported function with the given index
	int count(); // returns the number of exported functions in the list
	void clear(); // clears the list
	PEExport itemByAddress(uint64 address); // returns an exported function at the given address
	PEExport itemByName(string name); // returns an exported function with the given name
};

A class to wotk with a PE architecture exported function:

class PEExport {
public:
	uint64 address(); // returns the address of the exported function
	string name(); // returns the name of the exported function
	int ordinal();  // returns the ordinal of the exported function
	string forwardedName(); // returns the name of the function the exported function forwards to
	void destroy(); // destroys the exported function
};

A class to work with the list of PE architecture resources:

class PEResources {
public:
	PEResource item(int index); // returns a resources with the given index
	int count(); // returns the number of resources in the list
	void clear(); // clears the list
	PEResource itemByType(int type); // returns a resource of the given type
	PEResource itemByName(string name); // returns a resource with the given name
};

A class to work with a PE architecture resource:

class PEResource {
public:
	PEResource item(int index); // returns a resource with the given index
	int count(); // returns the number of resources in the list
	void clear(); // clears the list
	uint64 address(); // returns the address of the resource
	int size(); // returns the size of the resource
	string name(); // returns the name of the resource
	int type(); // returns the type of the resource
	bool isDirectory(); // returns the "Directory" property
	void destroy(); // destroys the resource
	PEResource itemByName(string name); // returns a resource with the given name
	bool excludedFromPacking(); // returns the "Excluded from packing" property
};

A class to work with the list of PE architecture fixups (relocations):

class PERelocs {
public:
	PEReloc item(int index); // returns an element with the given index
	int count(); // returns the number of elements in the list
	PEReloc itemByAddress(uint64 address); // returns an element at the given address
};

A class to work with a PE architecture fixup (relocation):

class PEReloc {
public:
	uint64 address(); // returns the address of the element
};

Mach-O files

Constants to work with the Mach-O format:

enum MacFormat {
	// Load Command Types
	LC_SEGMENT,
	LC_SYMTAB,
	LC_SYMSEG,
	LC_THREAD,
	LC_UNIXTHREAD,
	LC_LOADFVMLIB,
	LC_IDFVMLIB,
	LC_IDENT,
	LC_FVMFILE,
	LC_PREPAGE,
	LC_DYSYMTAB,
	LC_LOAD_DYLIB,
	LC_ID_DYLIB,
	LC_LOAD_DYLINKER,
	LC_PREBOUND_DYLIB,
	LC_ROUTINES,
	LC_SUB_FRAMEWORK,
	LC_SUB_UMBRELLA,
	LC_SUB_CLIENT,
	LC_SUB_LIBRARY,
	LC_TWOLEVEL_HINTS,
	LC_PREBIND_CKSUM,
	LC_LOAD_WEAK_DYLIB,
	LC_SEGMENT_64,
	LC_ROUTINES_64,
	LC_UUID,
	LC_RPATH,
	LC_CODE_SIGNATURE,
	LC_SEGMENT_SPLIT_INFO,
	LC_REEXPORT_DYLIB,
	LC_LAZY_LOAD_DYLIB,
	LC_ENCRYPTION_INFO,
	LC_DYLD_INFO,
	LC_DYLD_INFO_ONLY,
	LC_LOAD_UPWARD_DYLIB,
	LC_VERSION_MIN_MACOSX,
	// Section Types	
	SECTION_TYPE,
	SECTION_ATTRIBUTES,
	S_REGULAR,
	S_ZEROFILL,
	S_CSTRING_LITERALS,
	S_4BYTE_LITERALS,
	S_8BYTE_LITERALS,
	S_LITERAL_POINTERS,
	S_NON_LAZY_SYMBOL_POINTERS,
	S_LAZY_SYMBOL_POINTERS,
	S_SYMBOL_STUBS,
	S_MOD_INIT_FUNC_POINTERS,
	S_MOD_TERM_FUNC_POINTERS,
	S_COALESCED,
	S_GB_ZEROFILL,
	S_INTERPOSING,
	S_16BYTE_LITERALS,
	S_DTRACE_DOF,
	S_LAZY_DYLIB_SYMBOL_POINTERS,
	SECTION_ATTRIBUTES_USR,
	S_ATTR_PURE_INSTRUCTIONS,
	S_ATTR_NO_TOC,
	S_ATTR_STRIP_STATIC_SYMS,
	S_ATTR_NO_DEAD_STRIP,
	S_ATTR_LIVE_SUPPORT,
	S_ATTR_SELF_MODIFYING_CODE,
	S_ATTR_DEBUG,
	SECTION_ATTRIBUTES_SYS,
	S_ATTR_SOME_INSTRUCTIONS,
	S_ATTR_EXT_RELOC,
	S_ATTR_LOC_RELOC
};

A class to work with a Mach-O file:

class MacFile {
public:
	string name(); // returns the name of the file
	string format(); // returns the name of the "Mach-O" format
	uint64 size(); // returns the size of the file
	int count(); // returns the number of architectures in the list
	MacArchitecture item(int index); // returns an architecture with the given index
	uint64 seek(uint64 offset); // sets the file position
	uint64 tell(); // returns the file position
	int write(string buffer); // writes a buffer to the file
};

A class to work with the Mach-O architecture:

class MacArchitecture {
public:
	string name(); // returns the name of the architecture
	MacFile file(); // returns the parent file
	uint64 entryPoint(); // returns the starting address
	OperandSize cpuAddressSize(); // returns bit count of the architecture
	uint64 size(); // returns the size of the architecture
	MacSegments segments(); // returns the list of segments
	MacSections sections(); // returns the list of sections
	MacCommands commands(); // returns the list of load commands
	MacSymbols symbols(); // returns the list of symbols
	MacImports imports(); // returns the list of imported libraries
	MacExports exports(); // returns the list of exported functions
	MacRelocs relocs(); // returns the list of fixups (relocations)
	MapFunctions mapFunctions(); // returns the list of functions available for protection
	IntelFunctions functions(); // returns the list of protected functions
	bool addressSeek(uint64 address); // sets the file position
	uint64 seek(uint64 offset); // sets the file position
	uint64 tell(); // returns the file position
	int write(string buffer); // writes a buffer to the file
};

A class to work with the list of Mach-O architecture segments:

class MacSegments {
public:
	MacSegment item(int index); // returns a segment with the given index
	int count(); // returns the number of segments in the list
	MacSegment itemByAddress(); // returns a segment at the given address
};

A class to work with a Mach-O architecture segment:

class MacSegment {
public:
	uint64 address(); // returns the address of the segment
	string name(); // returns the name of the segment
	uint64 size(); // returns the size of the segment
	int physicalOffset(); // returns the file position of the segment
	int physicalSize(); // returns the file size of the segment
	int flags(); // returns flags of the segment
	bool excludedFromPacking(); // returns the "Excluded from packing" property
};

A class to work with the list of Mach-O architecture sections:

class MacSections {
public:
	MacSection item(int index); // returns a section with the given index
	int count(); // returns the number of sections in the list
	MacSection itemByAddress(uint64 address); // returns a section at the given address
};

A class to work with a Mach-O architecture section:

class MacSection {
public:
	uint64 address(); // returns the address of the section
	string name(); // returns the name of the section
	uint64 size(); // returns the size of the section
	int offset(); // returns the file position of the section
	MacSegment segment(); // returns the parent segment
};

A class to work with the list of Mach-O architecture load commands:

class MacCommands {
public:
	MacCommand item(int index); // returns a command with the given index
	int count(); // returns the number of command in the list
	MacCommand itemByType(int type); // returns a command of the given type
};

A class to work with a Mach-O architecture load command:

class MacCommand {
public:
	uint64 address(); // returns the address of the command
	int type(); // returns the type of the command
	string name(); // returns the name of the command
	int size(); // returns the size of the command
};

A class to work with the list of Mach-O architecture symbols:

class MacSymbols {
public:
	MacSymbol item(int index); // returns a symbol with the given index
	int count(); // returns the number of symbols in the list
};

A class to work with a Mach-O architecture symbol:

class MacSymbol {
public:
	uint64 value(); // returns the value of the symbol
	string name(); // returns the name of the symbol
};

A class to work with the list of imported libraries for the Mach-O architecture:

class MacImports {
public:
	MacImport item(int index); // returns an imported library with the given index
	int count(); // returns the number of imported libraries in the list
	MacImport itemByName(string name); // returns an imported library with the given name
};

A class to work with a Mach-O architecture imported library:

class MacImport {
public:
	string name(); // returns the name of the imported library
	MacImportFunction item(int index); // returns an imported function with the given index
	int count(); // returns the number of imported functions in the list
	void setName(string name); // sets the name of the imported library
};

A class to work with a Mach-O architecture imported function:

class MacImportFunction {
public:
	uint64 address(); // returns the memory address where the address of the imported function is stored
	string name(); // returns the name of the imported function
};

A class to work with the list of exported functions for the Mach-O architecture:

class MacExports {
public:
	string name(); // returns the name of the library
	MacExport item(); // returns an exported function with the given index
	int count(); // returns the number of exported functions in the list
	void clear(); // clears the list
	MacExport itemByAddress(uint64 address); // returns an exported function at the given address
	MacExport itemByName(string name); // returns an exported function with the given name
};

A class to work with a Mach-O architecture exported function:

class MacExport {
public:
	uint64 address(); // returns the address of the exported function
	string name(); // returns the name of the exported function
	string forwardedName(); // returns the name of the function the exported function is forwarded to
	void destroy(); // destroys the exported function
};

A class to work with the list of fixups (relocations) for the Mach-O architecture:

class MacRelocs {
public:
	MacReloc item(int index); // returns an element with the given index
	int count(); // returns the number of elements in the list
	MacReloc itemByAddress(uint64 address); // returns an element at the given address
};

A class to work with a Mach-O architecture fixup:

class MacReloc {
public:
	uint64 address(); // returns the address of the element
};

Functions

A class to work with the list of functions:

class MapFunctions {
public:
	MapFunction item(int index); // returns a function with the given index
	int count(); // returns the number of functions in the list
	MapFunction itemByAddress(uint64 address); // returns a function at the given address
	MapFunction itemByName(string name); // returns a function with the given name
};

Types of functions:

enum ObjectType {
	Unknown,
	Code,
	Data,
	Export,
	Marker,
	APIMarker,
	Import,
	String
};

A class to work with a function:

class MapFunction {
public:
	uint64 address(); // returns the address of the function
	string name(); // returns the name of the function
	ObjectType type(); // returns the type of the function
	References references(); // returns the list of references
};

A class to work with the list of references:

class References {
public:
	Reference item(int index); // returns a reference with the given index
	int count(); // returns the number of references in the list
};

A class to work with a reference:

class Reference {
public:
	uint64 address(); // returns the address of the command
	uint64 operandAddress(); // returns the address of the references
};

Intel functions

A class to work with the list of Intel functions:

class IntelFunctions {
public:
	IntelFunction item(int index); // returns a function with the given index
	int count(); // returns the number of functions in the list
	void clear(); // clears the list
	IntelFunction itemByAddress(uint64 address); // returns a function at the given address
	IntelFunction itemByName(string name); // returns a function with the given name
	IntelFunction addByAddress(uint64 address, CompilationType type = ctVirtualization); 
		// Adds a new function with the given address and compilation type
};

Compilation types:

enum CompilationType {
	Virtualization,
	Mutation,
	Ultra
};

A class to work with an Intel function:

class IntelFunction {
public:
	uint64 address(); // returns the address of the function
	string name(); // returns the name of the function
	ObjectType type(); // returns the type of the function
	IntelCommand item(int index); // returns a command with the given index
	int count(); // returns the number of commands in the list
	CompilationType compilationType(); // returns the compilation type
	void setCompilationType(CompilationType value); // sets the compilation type
	CommandLinks links(); // returns the list of links
	IntelCommand itemByAddress(uint64 address); // returns a command at the given address
	void destroy(); // destroys the function
	Folder folder(); // returns the custom folder
	void setFolder(Folder folder); // sets the custom folder
};

Types of Intel commands:

enum IntelCommandType {
	Unknown, Push, Pop, Mov, Add, Xor, Test, Lea, Ud0, Ret, Ssh, Crc, Call, Jmp, 
	Fstsw, Fsqrt, Fchs, Fstcw, Fldcw, Fild, Fist, Fistp, Fld, Fstp, Fst, Fadd, 
	Fsub, Fsubr, Fisub, Fisubr, Fdiv, Fcomp, Fmul, Repe, Repne, Rep, DB, DW, DD, DQ,
	Movs, Cmps, Scas, Movzx, Movsx, Inc, Dec, Les, Lds, Lfs, Lgs, Lss, Xadd, Bswap,
	Jxx, And, Sub, Stos, Lods, Nop, Xchg, Pushf, Popf, Sahf, Lahf, Shl, Shr, Sal, 
	Sar, Rcl, Rcr, Rol, Ror, Shld, Shrd, Loope, Loopne, Loop, Jcxz, In, Ins, Out, 
	Outs, Wait, Cbw, Cwde, Cdqe, Cwd, Cdq, Cqo, Clc, Stc, Cli, Sti, Cld, Std, Not,
	Neg, Div, Imul, Idiv, Mul, Or, Adc, Cmp, Sbb, Pusha, Popa, Clflush, Pause, 
	Bound, Arpl, Daa, Das, Aaa, Aam, Aad, Aas, Enter, Leave, Int, Into, Iret, Set, 
	Cmov, Addpd, Addps, Addsd, Addss, Andpd, Andps, Andnpd, Andnps, Cmppd, Cmpps, 
	Cmpsd, Cmpss, Comisd, Comiss, Cvtdq2ps, Cvtpd2dq, Cvtdq2pd, Cvtpd2pi, Cvtps2pi,
	Cvtpd2ps, Cvtps2pd, Cvtpi2pd, Cvtpi2ps, Cvtps2dq, Cvtsd2si, Cvtss2si, Cvtsd2ss,
	Cvtss2sd, Cvttpd2pi, Cvttps2pi, Cvttpd2dq, Cvttps2dq, Cvttsd2si, Cvttss2si, 
	Divpd, Divps, Divsd, Divss, Maxpd, Maxps, Maxsd, Maxss, Minpd, Minps, Minsd, 
	Minss, Mulpd, Mulps, Mulsd, Mulss, Orpd, Orps, Movd, Movq, Movntq, Movapd, Movaps,
	Movdqa, Movdqu, Movdq2q, Movq2dq, Movhlps, Movhpd, Movhps, Movlhps, Movlpd, 
	Movlps, Movmskpd, Movmskps, Movnti, Movntpd, Movntps, Movsd, Movss, Movupd, 
	Movups, Pmovmskb, Psadbw, Pshufw, Pshufd, Pshuflw, Pshufhw, Psubb, Psubw, Psubd, 
	Psubq, Psubsb, Psubsw, Psubusb, Psubusw, Paddb, Paddw, Paddd, Paddq, Paddsb, 
	Paddsw, Paddusb, Paddusw, Pavgb, Pavgw, Pinsrw, Pextrw, Pmaxsw, Pmaxub, Pminsw, 
	Pminub, Pmulhuw, Pmulhw, Pmullw, Pmuludq, Psllw, Pslld, Psllq, Pslldq, Psraw, 
	Psrad, Psrlw, Psrld, Psrlq, Psrldq, Punpcklbw, Punpcklwd, Punpckldq, Punpcklqdq, 
	Punpckhqdq, Packusdw, Pcmpgtb, Pcmpgtw, Pcmpgtd, Pcmpeqb, Pcmpeqw, Pcmpeqd, 
	Emms, Packsswb, Packuswb, Punpckhbw, Punpckhwd, Punpckhdq, Packssdw, Pand, 
	Pandn, Por, Pxor, Pmaddwd, Rcpps, Rcpss, Rsqrtss, Movsxd, Shufps, Shufpd, Sqrtpd, 
	Sqrtps, Sqrtsd, Sqrtss, Subpd, Subps, Subsd, Subss, Ucomisd, Ucomiss, Unpckhpd, 
	Unpckhps, Unpcklpd, Unpcklps, Xorpd, Xorps, Bt, Bts, Btr, Btc, Xlat, Cpuid, 
	Rsm, Bsf, Bsr, Cmpxchg, Cmpxchg8b, Hlt, Cmc, Lgdt, Sgdt, Lidt, Sidt, Smsw, Lmsw, 
	Invlpg, Lar, Lsl, Clts, Invd, Wbinvd, Ud2, Wrmsr, Rdtsc, Rdmsr, Rdpmc, Fcom, 
	Fdivr, Fiadd, Fimul, Ficom, Ficomp, Fidiv, Fidivr, Faddp, Fmulp, Fsubp, Fsubrp, 
	Fdivp, Fdivrp, Fbld, Fbstp, Ffree, Frstor, Fsave, Fucom, Fucomp, Fldenv, Fstenvm,
	Fxch, Fabs, Fxam, Fld1, Fldl2t, Fldl2e, Fldpi, Fldlg2, Fldln2, Fldz, Fyl2x, 
	Fptan, Fpatan, Fxtract, Fprem1, Fdecstp, Fincstp, Fprem, Fyl2xp1, Fsincos, Frndint, 
	Fscale, Fsin, Fcos, Ftst, Fstenv, F2xm1, Fnop, Finit, Fclex, Fcompp, Sysenter, 
	Sysexit, Sldt, Str, Lldt, Ltr, Verr, Verw, Sfence, Lfence, Mfence, Prefetchnta, 
	Prefetcht0, Prefetcht1, Prefetcht2, Prefetch, Prefetchw, Fxrstor, Fxsave, Ldmxcsr, 
	Stmxcsr, Fcmovb, Fcmove, Fcmovbe, Fcmovu, Fcmovnb, Fcmovne, Fcmovnbe, Fcmovnu,
	Fucomi, Fcomi, Fucomip, Fcomip, Fucompp, Vmcall, Vmlaunch, Vmresume, Vmxoff, 
	Monitor, Mwait, Xgetbv, Xsetbv, Vmrun, Vmmcall, Vmload, Vmsave, Stgi, Clgi, 
	Skinit, Invlpga, Swapgs, Rdtscp, Syscall, Sysret, Femms, Getsec, Pshufb, Phaddw, 
	Phaddd, Phaddsw, Pmaddubsw, Phsubw, Phsubd, Phsubsw, Psignb, Psignw, Psignd, 
	Pmulhrsw, Pabsb, Pabsw, Pabsd, Movbe, Palignr, Rsqrtps, Vmread, Vmwrite, Svldt, 
	Rsldt, Svts, Rsts, Xsave, Xrstor, Vmptrld, Vmptrst, Maskmovq, Fnstenv, Fnstcw, 
	Fstp1, Fneni, Fndisi, Fnclex, Fninit, Fsetpm, Fisttp, Fnsave, Fnstsw, Fxch4, 
	Fcomp5, Ffreep, Fxch7, Fstp8, Fstp9, Haddpd, Hsubpd, Addsubpd, Addsubps, Movntdq, 
	Fcom2, Fcomp3, Haddps, Hsubps, Movddup, Movsldup, Cvtsi2sd, Cvtsi2ss, Movntsd, 
	Movntss, Lddqu, Movshdup, Popcnt, Tzcnt, Lzcnt, Pblendvb, Pblendps, Pblendpd, 
	Ptest, Movsxbw, Movsxbd, Movsxbq, Movsxwd, Movsxwq, Movsxdq, Muldq, Pcmpeqq, 
	Movntdqa, Xsaveopt, Maskmovdqu, Ud1, Pcmpgtq, Movzxbw, Movzxbd, Movzxbq, Movzxwd, 
	Movzxwq, Movzxdq
};

Intel segments:

enum IntelSegment {
	None,
	es,
	cs,
	ss,
	ds,
	fs,
	gs
};

Intel flags:

enum IntelFlag {
	C,
	P,
	A,
	Z,
	S,
	T,
	I,
	D,
	O
};

Intel registers:

enum IntelRegistr {
	eax,
	ecx,
	edx,
	ebx,
	esp,
	ebp,
	esi,
	edi,
	r8,
	r9,
	r10,
	r11,
	r12,
	r13,
	r14,
	r15
};

A class to work with an Intel command:

class IntelCommand {
public:
	uint64 address(); // returns the address of the command
	IntelCommandType type(); // returns the type of the command
	string text(); // returns the text representation
	int size(); // returns the size of the command
	int dump(int index); // returns data of the command with the given index
	CommandLink link(); // returns the command link
	int flags(); // returns command flags
	IntelSegment baseSegment(); // returns the base segment
	IntelCommandType preffix(); // returns the type of the prefix command
	IntelOperand operand(int index); // returns an operand with the given index
};

Operand types:

enum IntelOperandType {
	None,
	Value,
	Registr,
	Memory,
	SegmentRegistr,
	ControlRegistr,
	DebugRegistr,
	FPURegistr,
	HiPartRegistr,
	BaseRegistr,
	MMXRegistr,
	XMMRegistr
};

Operand sizes:

enum OperandSize {
	Byte,
	Word,
	DWord,
	QWord,
	TByte,
	OWord,
	FWord
};

A class to work with an operand of the Intel command:

class IntelOperand {
public:
	int type(); // returns the type of the operand
	OperandSize size(); // returns the size of the operand
	int registr(); // returns the register
	int baseRegistr(); // returns the base register
	int scale(); // returns the scale
	uint64 value(); // returns the value
};
class CommandLinks {
public:
	CommandLink item(int index); // returns a link with the given index
	int count(); // returns the number of links in the list
};

Link types:

enum LinkType {
	None,
	SEHBlock,
	FinallyBlock,
	DualSEHBlock,
	FilterSEHBlock,
	Jmp,
	JmpWithFlag,
	JmpWithFlagNSFS,
	JmpWithFlagNSNA,
	JmpWithFlagNSNS,
	Call,
	Case,
	Switch,
	Native,
	Offset,
	GateOffset,
	ExtSEHBlock,
	MemSEHBlock,
	ExtSEHHandler,
	VBMemSEHBlock
};
class CommandLink {
public:
	uint64 toAddress(); // returns the address the link refers
	LinkType type(); // returns the type of the link
	IntelCommand from(); // returns the parent command
};

A class to work with a library:

enum ParamType {
	"void",
	"byte",
	"char",
	"short",
	"ushort",
	"int",
	"uint",
	"long",
	"ulong",
	"size_t",
	"float",
	"double",
	"string",
	"pointer"
};

enum CallType {
	"default", 
	"cdecl", 
	"stdcall"
};

class FFILibrary {
public:
	string name(); // returns the name 
	uint64 address(); // returns the address in the memory
	void close();
	FFIFunction getFunction(string name, ParamType ret, ParamType param1, ...); // returns a function
	FFIFunction getFunction(string name, table (ParamType ret, CallType abi, ParamType, ...)); // returns a function
};

A class to work with a foreign function:

class FFIFunction {
	string name(); // returns the name
	uint64 address(); // returns the address in the memory
};

Built-in functions

Aside from class methods and properties of the script language, VMProtect offers to a user various functions to perform basic operations. There are general system functions to work with strings, dates and numbers, process the command line of an application and display messages; and specialized functions to work with the VMProtect core and watermarks:

namespace vmprotect {
	Core core(); // returns the VMProtect core
	string extractFilePath(string name); // extracts the path of a file
	string extractFileName(string name); // extracts the name of a file
	string extractFileExt(string name); // extracts the extension of a file
	table commandLine(); // returns the command line
	FFILibrary openLib(string name); // opens a library
};

Events

The built in script language is an efficient way to automate creation of protected applications with VMProtect. Procedures and functions required at various stages of building the protected file are invoked in certain events processed by the VMProtect core. You can set your own handlers for 5 events that are called by the VMProtect core in the following order:

function OnBeforeCompilation()
end

This event is invoked when the list of protection objects is created. In the OnBeforeCompilation handler, you can add new procedures to the project, or modify or delete already existing ones.

function OnBeforeSaveFile()
end

The event is invoked before all objects created during compilation are written to the output file. In the OnBeforeSaveFile event handler you can change the file and its properties (such as the list of resources, the list of exported function, section names etc.) that are automatically written to the output file.

function OnBeforePackFile()
end

The event is invoked before packing the protected file of the application. With OnBeforePackFile you can modify a file that is to be packed. This event is only invoked is the “Pack output file” option is enabled.

function OnAfterSaveFile()
end

This event is invoked after writing all objects created during compilation to the output file. The event handler can add new data to the output file or change previously written ones.

function OnAfterCompilation()
end

The even is invoked after compiling all objects of the project. At this stage a user has access to the compiled project, and can perform any actions with it, like adding a digital sign (certificate).

When writing even handlers you can create your own procedures and functions. Event names are displayed with a bold font in the script editor.

Watermarks

VMProtect offers a unique ability to add hidden information about the owner of the protected file to this file. A watermark is an array of bytes that should be unique to each user. If watermarks are incorporated to the protected file, you can always determine the owner of the leaked copy (for instance, if the cracked program is distributed) and act accordingly. The watermark database file is stored in “%ApplicationData%/VMProtect Software/VMProtect.dat” for Windows and in “/Users/Shared/VMProtect Software/VMProtect.dat” for Mac OS X.

The “Watermarks” dialog window contains two tabs:

Watermark setup

The “Setup” tab of the “Watermarks” dialog is for managing watermarks:

Configuring watermarks

To add a new watermark, click the corresponding button. The following dialog will open:

Add a watermark

  • Name – the name of the watermark;
  • Value – the value of the watermark. You can generate a random value for the watermark by clicking the “Generate” button. The “Value” field consists of two columns: the left column allows entering a Hex value, the right column – a symbolic value. When the watermark is inserted to the protected file, each “?” symbol is replaced with a random value.

To remove or rename a watermark, use the right-click menu:

Watermark menu

Searching for watermarks

The “Search” tab of the watermark dialog allows you to locate watermarks in an executable or a specified process of the protected application:

  • Search in file – look for watermarks in the application executable. Select a file in the “File Name” field where you want to look for watermarks in;
  • Search in module – search in a module of the selected process. Select a running process in the drop-down list, then select a module of this process to search watermarks in.

Results of the search are displayed in the list below:

  • Name – the name of the found watermark;
  • Count – the number of copies of the found watermark.

Important!

When you search for watermarks in an unpacked executable, any of this modes will do. However, if an executable is packed, you should search for watermarks in the running application only (the “Search in module” mode). Searching in the executable in this case is useless as watermarks (as well as the code and data) are packed and are only unpacked when the application is running.

Frequently Asked Questions

General questions

Is there a way to encrypt strings and data arrays automatically?
In VMProtect you can hide ANSI constants and Unicode constants. All other data the code operates with remain intact. We recommend storing all confidential information encrypted and decrypt it directly before use. The unpacker itself can be virtualized.

Is there a way to protect procedures that I call from various threads in my application?
VMProtect is 100% multi-thread compatible and there are no any specific limitations for this type of protection.

Can I use VMProtect along with another protector (packer)?
Using any other packers (protectors) after a file is processed by VMProtect can render the protected application non-functional.

Should I include VMProtectSDK32.dll/VMProtectSDK64.dll into the setup package of the program?
These libraries are only used at the debug stage of the program (before protecting it). After you protect the application with VMProtect all information about usage of these DLLs is removed completely, so you don’t have to include them into the release package.

Compiler messages

What does this error mean: VMProtectMarker “ToolButton1Click.1″.0044327D: Address is used by procedure “TForm1.ToolButton1Click”?
This error means the same command at the address 0044327D is used in two procedures that are included into the list of protected objects. To solve this issue, you should exclude one of procedures (in this particular case these are VMProtectMarker “ToolButton1Click.1″ and TForm1.ToolButton1Click) from the list of protected objects.

What does this error mean: [Error] TForm1.Test.004433F4: Minimum procedure size for compilation is 5 bytes?
This error means the procedure TForm1.Test is too small and cannot be protected. To solve this issue, exclude this procedure from the list of protected objects.

What does this error mean: The “.text” section allocates space required for the new section?
The following error usually take place when drivers are protected. It means that free space between the first section of the file and service information in the header of the file is too small to create a new section To solve this problem, increase the value of the section alignment parameter in driver’s source codes and rebuild the driver completely. (For example: if you compile the driver using WDK/DDK, you should add DRIVER_ALIGNMENT=0×200 into SOURCES).