Introduction
Cracking Serial Keys
- using System;
- namespace CILComplexTest
- {
- static class LicenseKeyAuthentication
- {
- private static int Authentic_Key = 1111111;
- public static bool VerifyKey(int key)
- {
- return key == Authentic_Key;
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- Console.Write("Enter License key to unlock Software (7 digit):");
- var Keys = Int32.Parse(Console.ReadLine());
- if (LicenseKeyAuthentication.VerifyKey(Keys))
- {
- Console.WriteLine("Thank you!");
- }
- else
- {
- Console.WriteLine("Invalid license key; Continue evaluation.");
- }
- Console.ReadKey();
- }
- }
- }
After executing this software, it prompts to enter a seven-digit license key. Otherwise, it could not let you proceed in the case of a futile hit and trial as in the following.
Okay, don't bother yourself; we can still get through this application without having the real license keys. First things first, decompile the shipped executable file using ILDASM that produces the following IL opcode as in the following:

- .module SerialCrack
- .class private abstract auto ansi sealed beforefieldinit LicenseKeyAuthentication extends [mscorlib]System.Object
- {
- .field private static int32 Authentic_Key
- .method public hidebysig static bool VerifyKey(int32 key) cil managed
- {
- .maxstack 2
- .locals init ([0] bool CS$1$0000)
- IL_0000: nop
- IL_0001: ldarg.0
- IL_0002: ldsfld int32 LicenseKeyAuthentication::Authentic_Key
- IL_0007: ceq
- IL_0009: stloc.0
- IL_000a: br.s IL_000c
- IL_000c: ldloc.0
- IL_000d: ret
- }
- .method private hidebysig specialname rtspecialname static void .cctor() cil managed
- {
- // Code size 11 (0xb)
- .maxstack 8
- IL_0000: ldc.i4 0x10f447
- IL_0005: stsfld int32 LicenseKeyAuthentication::Authentic_Key
- IL_000a: ret
- }
- }
- .class private auto ansi beforefieldinit Program extends [mscorlib]System.Object
- {
- .method private hidebysig static void Main(string[] args) cil managed
- {
- .entrypoint
- .maxstack 2
- .locals init ([0] int32 Keys,[1] bool CS$4$0000)
- IL_0000: nop
- IL_0001: ldstr "Enter License key to unlock Software (7 digit):"
- IL_0006: call void [mscorlib]System.Console::Write(string)
- IL_000b: nop
- IL_000c: call string [mscorlib]System.Console::ReadLine()
- IL_0011: call int32 [mscorlib]System.Int32::Parse(string)
- IL_0016: stloc.0
- IL_0017: ldloc.0
- IL_0018: call bool LicenseKeyAuthentication::VerifyKey(int32)
- IL_001d: ldc.i4.0
- IL_001e: ceq
- IL_0020: stloc.1
- IL_0021: ldloc.1
- IL_0022: brtrue.s IL_0033
- IL_0024: nop
- IL_0025: ldstr "Thank you!"
- IL_002a: call void [mscorlib]System.Console::WriteLine(string)
- IL_002f: nop
- IL_0030: nop
- IL_0031: br.s IL_0040
- IL_0033: nop
- IL_0034: ldstr "Invalid license key; Continue evaluation."
- IL_0039: call void [mscorlib]System.Console::WriteLine(string)
- IL_003e: nop
- IL_003f: nop
- IL_0040: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
- IL_0045: pop
- IL_0046: ret
- }
- }
We can bypass or reveal such security constraints in multiple ways. In the Main() method declaration, if you deem over the following opcode instructions especially IL_0022 that implies that if the proper serial keys are not entered, then jump to the error message instruction. So here is the point, we change this jump code instruction to IL_0025 instead of IL_0033 then the code execution will always jump to the code block we intend it to, no matter what keys values we are entered.
- IL_0021: ldloc.1
- IL_0022: brtrue.s IL_0033 // put IL_0025
- IL_0024: nop
- IL_0025: ldstr "Thank you!"
The second trick resides again in the Main() method near to the key verify method. What this code is doing is checking the entered key values to the actual key value. If the values are correct then we can enter otherwise it throws us to the invalid message section using stloc.1. So if we change it to stloc.0 then our execution will always go to the block we intend it to, as in the following:
- IL_0018: call bool LicenseKeyAuthentication::VerifyKey(int32)
- IL_001d: ldc.i4.0
- IL_001e: ceq
- IL_0020: stloc.1 //put stloc.0
- IL_0021: ldloc.1
- IL_0022: brtrue.s IL_0033
Now, if you don't possess the right key values then you can still get into the software without having the exact key values as in the following:
The final trick is, if you examine the class constructor block, you can easily find the hard-coded key values. Here, in this instruction, we can guess that the key value is 0x10f447 so change it to decimal format and you have the exact key to validate. After finally employing one of these methods, you can bypass the serial key limitation despite not having the key as in the following:

- .method private hidebysig specialname rtspecialname static
- void .cctor() cil managed
- {
- .maxstack 8
- IL_0000: ldc.i4 0x10f447
- IL_0005: stsfld int32 LicenseKeyAuthentication::Authentic_Key
- IL_000a: ret
- }

Cracking Passwords

- .method private hidebysig instance bool UserAuth(string usr,string pwd) cil managed
- {
- .maxstack 2
- .locals init ([0] string USR, [1] string PWD, [2] bool status, [3] bool CS$1$0000, [4] bool CS$4$0001)
- IL_0000: nop
- IL_0001: ldstr "ajay"
- IL_0006: stloc.0
- IL_0007: ldstr "1234"
- IL_000c: stloc.1
- IL_000d: ldc.i4.0
- IL_000e: stloc.2
- IL_000f: ldarg.1
- IL_0010: ldloc.0
- IL_0011: call bool [mscorlib]System.String::op_Equality(string, string)
- IL_0016: brfalse.s IL_0024
- IL_0018: ldarg.2
- IL_0019: ldloc.1
- IL_001a: call bool [mscorlib]System.String::op_Equality(string, string)
- IL_001f: ldc.i4.0
- IL_0020: ceq
- IL_0022: br.s IL_0025
- IL_0024: ldc.i4.1
- IL_0025: stloc.s CS$4$0001
- IL_0027: ldloc.s CS$4$0001
- IL_0029: brtrue.s IL_002f
- IL_002b: nop
- IL_002c: ldc.i4.1
- IL_002d: stloc.2
- IL_002e: nop
- IL_002f: ldloc.2
- IL_0030: stloc.3
- IL_0031: br.s IL_0033
- IL_0033: ldloc.3
- IL_0034: ret
- }
If we rigorously scrutinize that code then we can reach a conclusive result by obtaining some significant information. We can easily determine here that instructions IL_0001 and IL_0007 are storing the actual user name and password information as “ajay” and “1234”.
- IL_0000: nop
- IL_0001: ldstr "ajay"
- IL_0006: stloc.0
- IL_0007: ldstr "1234"
- IL_000c: stloc.1
The second important thing is, we can conclude from these opcodes that IL_000d is responsible for setting a boolean value to true and false. As in the UserAuth() method, if the user enters a correct user name and password then this boolean value is set to true, otherwise, it would be always false.
IL_000d: ldc.i4.0.
So here is the trick, if we change it to True right here, then it doesn't matter what the user inputs because the boolean value would always be true.
IL_000d: ldc.i4.1
In another observation, we can imply some substantial information from the btnLog_Click() method. In fact, this method takes a user name and password from the user and validates them against the predefined parameters.
- .method private hidebysig instance void btnLog_Click(object sender,class [mscorlib]System.EventArgs e) cil managed
- {
- .maxstack 3
- .locals init ([0] bool CS$4$0000)
- IL_0000: nop
- IL_0001: ldarg.0
- IL_0002: ldarg.0
- IL_0003: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox DummySoftware.Login::txtUser
- IL_0008: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
- IL_000d: ldarg.0
- IL_000e: ldfld class [System.Windows.Forms]System.Windows.Forms.TextBox DummySoftware.Login::Password
- IL_0013: callvirt instance string [System.Windows.Forms]System.Windows.Forms.Control::get_Text()
- // ---------- Modification Required here--------------------
- IL_0018: call instance bool DummySoftware.Login::UserAuth(string,string)
- IL_001d: ldc.i4.0
- IL_001e: ceq
- IL_0020: stloc.0
- IL_0021: ldloc.0
- IL_0022: brtrue.s IL_0039
- //----------------------------------------------------------------
- IL_0024: nop
- IL_0025: ldarg.0
- IL_0026: ldfld class [System.Windows.Forms]System.Windows.Forms.Label DummySoftware.Login::label3
- IL_002b: ldstr "Login Successful"
- IL_0030: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
- IL_0035: nop
- IL_0036: nop
- IL_0037: br.s IL_004c
- IL_0039: nop
- IL_003a: ldarg.0
- IL_003b: ldfld class [System.Windows.Forms]System.Windows.Forms.Label DummySoftware.Login::label3
- IL_0040: ldstr "Login Failed"
- IL_0045: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
- IL_004a: nop
- IL_004b: nop
- IL_004c: ret
- }
Now look at these sorts of instructions, these are actually checking the input credentials against the predefined. If the user enters the correct information then okay otherwise it throws the execution to IL_0039 that shows some invalid message or something else.
- IL_0018: call instance bool DummySoftware.Login::UserAuth(string,string)
- IL_001d: ldc.i4.0
- IL_001e: ceq
- IL_0020: stloc.0
- IL_0021: ldloc.0
- IL_0022: brtrue.s IL_0039
So, here is a loophole, if we throw the execution to instruction IL0024 rather than IL_0039, then our program always runs perfectly, it doesn't matter what credentials we enter.
IL_0022: brtrue.s IL_0024
In other tactics, if we bypass the equal condition, where the credentials are validated then we can breach the software easily. These instructions are responsible for equating the condition as in the following:
- L_0018: call instance bool DummySoftware.Login::UserAuth(string,string)
- // ---------- Modification Required here--------------------
- IL_001d: ldc.i4.0
- //------------------------------------------------------------------
- IL_001e: ceq
- IL_0020: stloc.0
- IL_0021: ldloc.0
- IL_0022: brtrue.s IL_0039
So, if we change the IL_001d instruction to ldc.i4.1 then the equal never would be checked and we can breach the login screen easily as in the following:

Extending trial Duration

- .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
- {
- .maxstack 8
- IL_0000: ldarg.0
- // ---------- Modification Required here--------------------
- IL_0001: ldc.i4 0x7dd
- IL_0006: ldc.i4.7
- IL_0007: ldc.i4.s 30
- //-------------------------------------------------------------------
- IL_0009: newobj instance void [mscorlib]System.DateTime::.ctor(int32,int32,int32)
- IL_000e: stfld valuetype [mscorlib]System.DateTime TrailSoftware.Form1::expDate
- IL_0013: ldarg.0
- IL_0014: ldnull
- IL_0015: stfld class [System]System.ComponentModel.IContainer TrailSoftware.Form1::components
- IL_001a: ldarg.0
- IL_001b: call instance void [System.Windows.Forms]System.Windows.Forms.Form::.ctor()
- IL_0020: nop
- IL_0021: nop
- IL_0022: ldarg.0
- IL_0023: call instance void TrailSoftware.Form1::InitializeComponent()
- IL_0028: nop
- IL_0029: nop
- IL_002a: ret
- }
After doing some R&D, we found some instructions that show the expiry date as 30/7/2013 of this software as in the following:
IL_0001: ldc.i4 0x7dd
IL_0006: ldc.i4.7
IL_0007: ldc.i4.s 30
So if we modify the instruction IL_0007 to some other value and recompile it using ILASM then we can still use this software.
In another code review, that checks the current date to an expiry date whether or not it is less as in the following.
- .method private hidebysig instance void Form1_Load(object sender, class [mscorlib]System.EventArgs e) cil managed
- {
- .maxstack 2
- .locals init ([0] bool CS$4$0000)
- IL_0000: nop
- IL_0001: ldarg.0
- IL_0002: ldfld valuetype [mscorlib]System.DateTime TrailSoftware.Form1::expDate
- IL_0007: call valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
- IL_000c: call bool [mscorlib]System.DateTime::op_GreaterThan(valuetype [mscorlib]System.DateTime, valuetype [mscorlib]System.DateTime)
- // ---------- Modification Required here--------------------
- IL_0011: ldc.i4.0
- IL_0012: ceq
- IL_0014: stloc.0
- IL_0015: ldloc.0
- //--------------------Until Here---------------------------------------
- IL_0016: brtrue.s IL_0052
- ------
- }
If we delete some code instruction that shows the expiry date message then we can bypass this restriction as in the following:
- IL_0012: ceq
- IL_0014: stloc.0
- IL_0015: ldloc.0
Wipeout these aforementioned instructions from the code file, save it and recompile it again and finally run this software. Here the modified code is as in the following:
- IL_0011: ldc.i4.0
- IL_0016: brtrue.s IL_0052
Finally, this software works fine as in the following:


Comments
Join the conversation! Your thoughts help the community grow.