The content of this post can be used for good and bad purposes. Modifying the source code to bypass trial/license checks is what crackers do in order to get paid software for free. Be advised that the purpose of this article is not to teach you how to steal. My target for this article are the .NET developers who should understand what a cracker will (try to) do in order to get access to paid features.

Before reading any further you should understand that each protection measure (as long as the cracker can access the source code) is useless. Is just a matter of time, for a motivated person, before she will bypass any protection.

For the demo, we are going to use a very simple Windows Forms Application that will display a message box with a trial message and will exit after that. The goal is to show a few techniques that will prevent the application from exiting (and will remove the trial message).

The code for the 'trial' application is kept in just one class. There is just one variable for checking the trial and we'll consider that is always true - it makes no difference if there was a function call to determine if the trial has expired.

public partial class Form1 : Form
{
    bool hasExpired = true;
    public Form1()
    {
        InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
        CheckTrialApp();
    }
    private void CheckTrialApp()
    {
        if (hasExpired)
        {
            MessageBox.Show("Trial has expired");
            Application.Exit();
        }
    }
}

The binary used was compiled on the x86 Release configuration with VS2010 having .NET 4.0 as target framework. The IL Disassembler from VS2010 and a free application called CFF Explorer are used to view and edit the binary.

Opening the 'TrialApp.exe' file (the target binary) in IL Dissasembler will reveal all the statements from each method. This is important but, more important is the RVA of the method containing the trial check, the bytes for each statement and their position relative to the RVA.

By knowing the RVA you are able to navigate to that address using CFF explorer and locate the bytes for the calls. Even without seeing the actual bytes, one is able to locate the calls (and their length) by looking at the offsets (ie: the byte 2C is located 0006 bytes from the beginning of the implementation) - more on this in Part2.

Having access to all this information gives not one but many possibilities of bypassing the trial check:

  1. Remove the two calls to Application.Exit and MessageBox.Show.
  2. Change the if check.
  3. Remove the 'CheckTrialApp' call from 'Form1_Load'.

This post will cover just the first two possibilities, since the third is similar to the first.

1. Remove the calls to Exit and Show

The bytes from the method implementation:

02 7B 02 00 00 04 2C 10 72 01 00 00 70
28 16 00 00 0A 26 28 17 00 00 0A 2A

A call to a method has the opcode 28. The next 4 bytes following the opcode represent the location of the method in the methods table (you can see this table using CFF explorer).

Now here comes the magic: in order to remove the calls to Exit and Show, one must replace with NOP, all the bytes associated with these methods. Basically we are going the introduce a NOP byte (00) for each byte in the call.

02 7B 02 00 00 04 2C 10 72 01 00 00 70
00 00 00 00 00 26 00 00 00 00 00 2A

That's all. Save the file and the trial is bypassed.

2. Change the if check

If you look in the disassembled IL you can see that at offset 0x6 we have a brfalse.s opcode. This is a branch instruction that will branch to offset 0x18 (IL_0018) if false. However, in the case of 'TrialApp', since hasExpired is always true, the branch will never take place and the code following it will be executed.

In order to change the meaning of the code - in other words "give the trial message if the application has NOT expired" - the check will be changed. Currently, is checking against false using the instruction brfalse.s, having the opcode 2C. By looking on MSDN, the opcode for brtrue.s can be found: 2D. Replacing 2C with 2D will make the branch happen always.

The method inside the binary, after replacing the brfalse.s opcode:

02 7B 02 00 00 04 2D 10 72 01 00 00 70
28 16 00 00 0A 26 28 17 00 00 0A 2A

That's all. The message box will not be displayed since the body of the if statement is no longer executed.

There are some techniques that will make cracking difficult. Obfuscating the code is one of them. However, part 2 of this article will cover the modification of obfuscated binaries.