Hardware ID Licensing System Techniques

This post is dedicated on the inner workings of a past project called “PowerLicense” created for the purpose of controlling the distribution of software or shared libraries across the internet.

The Problem
You have just created a monthly subscription software that you want the end user to only be able to access on ONE of his computers. What can you do?

The Idea
Many current implementations of software licensing systems are embedded into the program itself at compile time. Although these systems may provide easier integration with the application, it ignores many post-compilation anti-debugging techniques that may be used to help improve the integrity of the software. By writing a wrapper that verifies the license before loading the program, it opens up a world of cool things you can do to make sure the license is valid.

PowerLicense
PowerLicense is a program wrapper and this post will be dedicated to the “DLL” version of the wrapper which can be used to protect software libraries. The license system consists of a “Packer” which encrypts the target library and a “Stub” which the Packer uses as a loading precursor to the library. The Stub uses an external library called Memory Module ported to Delphi in order to load the target library. The following will explain now the loading process works for the entity of the loader.

Initialization
Probably the most important step, initialization checks to make sure that the user is opening the program with a verified Hardware ID. In this step, a unique Hardware ID is generated based on the users system information. Generation of this ID varies between different companies but the function I use is given below. Note that although it is called a “Hardware ID” many of the components I hash together aren’t exactly hardware but rather something that is unique to the installation of windows they are on.

http://pastebin.com/BAxqMgem

As indicated, the function uses information that are unique about the system to generate a hashed value. The hashed value now (hopefully) uniquely represents the system in question. Note that some of the values I use change when they update their version of Windows. Whether it be a service pack or a complete upgrade.

The verification of the Hardware ID occurs in two parts. The first part is a simple system that resembles a typical Hardware ID lock. The purpose of this part is to catch potential crackers who do not fully reverse engineer the program.

http://pastebin.com/HFFtHTEJ

On the server, the PHP script simply checks the Hardware ID passed against a database and returns “#PZValid Hardware ID%PZ” if the check passes.

Of course, it is obvious that this code offers next to no protection and can be defeated simply by putting a breakpoint on InternetReadFile and noping the jump that follows which checks for the valid string. However, this isn’t the point of the check. Since after this check, the user is considered authenticated, any further failed checks will cause the server to block the user’s IP Address for a set duration which makes a cracker’s life a bit more difficult when they only bypass the first set of checks.

Memory Encryption
When the target library is packed into the Stub, it is encrypted using a unique key set by the Packer. This key is also found on the license server. When the second set of checks are passed, the license server returns a key that has been encrypted using the Hardware ID. The Stub then takes the key and decrypts it using the Hardware ID that was generated. The purpose of this procedure is to ensure that the checks in the initialization stage are not simply jumped over. If these checks were jumped over, the library would not be decrypted properly due to the lack of a key. In addition, this procedure prevents spoofing the response from another computer since the Hardware ID used to encrypt the key is different and would lead to incorrect decryption.

Server Authentication
After the initialization stage, all further requests to the server must pass through a handshake. The client will generate a random challenge that is sent along with every request to the server and the server will produce a response. If the response is invalid, the client will terminate itself.

Stolen Code
In the event that the first two parts are bypassed, the software can still rely on stolen code to protect itself. When the packer packs the file initially, specific, user defined functions are removed by the packer, replaced with a “CLI” or “STI” command and stored on the license server. The “CLI” and “STI” commands are privileged ring0 assembly instructions that causes STATUS_PRIVILEGED_INSTRUCTION when executed in ring3. By adding a Vectored Exception Handler, the Stub can catch these executions and request the proper code from the server. The code is then encrypted using a modified Hardware ID and returned to the client. After the client executes the code, the code is deleted and replaced with the “CLI” or “STI” instruction again. This technique makes dumping harder as without the Stub, the program would crash due to the unhanded STATUS_PRIVILEGED_INSTRUCTION exception.

Self Checks
One of the great things about this license system is the ability for the program to call functions within the Stub. It does not matter whether the program is written in Delphi or C++, as long as it has the proper include with the proper assembly, the function can be called. For example, the include in C++ is as follows:.

http://pastebin.com/79DCxfpY

When the HLT instruction (which is also a ring0 privileged instruction) is executed, the Stub is able to handle the instruction as if it was a function call. The “push 0” in the code indicates the function that it is calling. For example, 0 indicates that it wants to verify the integrity of the packer and 2 indicates that it wants to retrieve the Hardware ID. On the Delphi end, I process the call first by checking if the assembly matches that of a request then moving into a function processing switch as follows.

http://pastebin.com/RUWwLVYt

Each switch can be processed separately as a native Delphi function and force quit the program if any checks fail.

Conclusion
Sometimes only having protection inside the software is not enough. By integrating checks on the outside, the programmer can have access to more anti-reverse engineering techniques such as Stolen Code. The next function that I will be adding to the Stub is a Virtual Machine which is the highest form of Code Obfuscation. By virtualizing every single instruction within a function, the code cannot simply be dumped because it is executed by a pseudo CPU. Combining this technology with a unique encryption key online, a more secure system can be achieved.

Resources
Memory Module – http://www.joachim-bauch.de/tutorials/loading-a-dll-from-memory/

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s