• Home
  • Products
  • Spices.CodeAnonymizer: step forward in .Net code protection

Spices.CodeAnonymizer: step forward in .Net code protection

Using Spices.CodeAnonymizer to Protect Assemblies

Introduction

The Spices.CodeAnonymizer technology (U.S. Patent #7,937,693) is used in Spices.Net Obfuscator that assist you in developing high quality, industry standard applications.
Spices.CodeAnonymizer is another step forward in .Net code protection based on intelligent technology that fights disassemblers and decompilers.
Spices.CodeAnonymizer lets you scramble IL code so that it becomes completely unreusable after it is decompiled or disassembled.
Key Spices.CodeAnonymizer features and usage cases are outlined below.

This is the source C# code example:

public namespace smallTest
{
 class Test
 {
   public class DataHolder
   {
     private string msg;
     private int count;
     public DataHolder(string msg, int count)
     {
       this.msg = msg;
       this.count = count;
     } 
     public string Message
     {
       get {return msg;}
     } 
     public int Count 
     {
       get {return count;}
     }
   }

   public void HelloWorldTest()
   {
     DataHolder dh = new DataHolder("Hello World!!!", 4);
     Output(dh);
   }

   public int Output(DataHolder dh)
   {
    System.Console.WriteLine(string.Format("{0}. {1}", dh.Count.ToString(), dh.Message));
    return dh.Count;
   }
 }
}

Masquerading calls to IL Code calls to referenced types, methods and fields.
AnonymizeOptions.ReferencedMethods and AnonymizeOptions.ReferencedFields settings.

This is the source IL code example:

.method public hidebysig instance int32 Output(class smallTest.Test/DataHolder dh) cil managed
{
  // Code size: 46 bytes
  .maxstack 4
  .locals ([0] int32 V_0,
           [1] int32 V_1)
  IL_0000: ldstr "{0}. {1}"
  IL_0005: ldarg.1
  IL_0006: callvirt instance int32 smallTest.Test/DataHolder::get_Count()
  IL_000b: stloc.1
  IL_000c: ldloca.s V_1
  IL_000e: call instance string [mscorlib]System.Int32::ToString()
  IL_0013: ldarg.1
  IL_0014: callvirt instance string smallTest.Test/DataHolder::get_Message()
  IL_0019: call string [mscorlib]System.String::Format(string, class object, class object)
  IL_001e: call void [mscorlib]System.Console::WriteLine(string)
  IL_0023: ldarg.1
  IL_0024: callvirt instance int32 smallTest.Test/DataHolder::get_Count()
  IL_0029: stloc.0
  IL_002a: br.s IL_002c
  IL_002c: ldloc.0
  IL_002d: ret
}

This is IL code processed with Spices.CodeAnonymizer (with KeepReturnTypes, KeepParameterTypes options):

.method public hidebysig instance int32 Output(class smallTest.Test/DataHolder dh) cil managed
{
  // Code size: 46 bytes
  .maxstack 4
  .locals ([0] int32 V_0,
           [1] int32 V_1)
  IL_0000: call class object 0.-::Б()
  IL_0005: ldarg.1
  IL_0006: call int32 smallTest.Test/?::?(class object)
  IL_000b: stloc.1
  IL_000c: ldloca.s V_1
  IL_000e: call string smallTest.Test/?::?(class object)
  IL_0013: ldarg.1
  IL_0014: call string smallTest.Test/?::?(class object)
  IL_0019: call string smallTest.Test/?::?(string, class object, class object)
  IL_001e: call void smallTest.Test/?::?(string)
  IL_0023: ldarg.1
  IL_0024: call int32 smallTest.Test/?::?(class object)
  IL_0029: stloc.0
  IL_002a: br.s IL_002c
  IL_002c: ldloc.0
  IL_002d: ret
}

This is IL code processed with Spices.CodeAnonymizer (without KeepReturnTypes, KeepParameterTypes options):

.method public hidebysig instance int32 Output(class smallTest.Test/DataHolder dh) cil managed
{
  // Code size: 46 bytes
  .maxstack 4
  .locals ([0] int32 V_0,
           [1] int32 V_1)
  IL_0000: call class object 0.-::?()
  IL_0005: ldarg.1
  IL_0006: call int32 smallTest.Test/в::?(class object)
  IL_000b: stloc.1
  IL_000c: ldloca.s V_1
  IL_000e: call class object smallTest.Test/в::?(class object)
  IL_0013: ldarg.1
  IL_0014: call class object smallTest.Test/в::?(class object)
  IL_0019: call class object smallTest.Test/в::?(class object, class object, class object)
  IL_001e: call void smallTest.Test/в::?(class object)
  IL_0023: ldarg.1
  IL_0024: call int32 smallTest.Test/в::?(class object)
  IL_0029: stloc.0
  IL_002a: br.s IL_002c
  IL_002c: ldloc.0
  IL_002d: ret
}

This is C# code processed with Spices.CodeAnonymizer (without KeepReturnTypes, KeepParameterTypes options):

public int Output(smallTest.Test.DataHolder dh)
{
  smallTest.Test.в.?(
      smallTest.Test.в.?(0.-.?(),
      smallTest.Test.в.?(smallTest.Test.в.?(dh)),
      smallTest.Test.в.?(dh)));
  return smallTest.Test.в.?(dh);
}


Notice that calls to Int32.ToString(), String.Format, System.Console.WriteLine methods are anonymized, so giving you the following benefits:

  • Anonymizers have identical(same) names
  • Method execution flow made unclear;
  • Object creation is anonymized
  • Method is executed through an anonymized wrapper that uses anonymized parameters list;
  • Method return type anonymized so that it is very difficult now to understand what this method returns.
  • IL listing cannot be used with ILASM

 

Internal calls anonymization.
AnonymizeOptions.InternalMethods and AnonymizeOptions.InternalFields settings.

Internal calls are anonymized just as referenced, with respect to that even assembly members excluded with NineRays.Obfuscator.DontAnonymize attribute are also processed.

This is the source IL code example:

.method public hidebysig instance void HelloWorldTest() cil managed
{
 // Code size: 21 bytes
 .maxstack 3
 .locals ([0] class smallTest.Test/DataHolder dh)
 IL_0000: ldstr "Hello World!!!"
 IL_0005: ldc.i4.4
 IL_0006: newobj instance void smallTest.Test/DataHolder::.ctor(string, int32)
 IL_000b: stloc.0
 IL_000c: ldarg.0
 IL_000d: ldloc.0
 IL_000e: call instance int32 smallTest.Test::Output(class smallTest.Test/DataHolder)
 IL_0013: pop
 IL_0014: ret
}

This is IL code processed with Spices.CodeAnonymizer (with KeepReturnTypes, KeepParameterTypes options):

.method public hidebysig instance void HelloWorldTest() cil managed
{
  // Code size: 21 bytes
  .maxstack 3
  .locals ([0] class smallTest.Test/DataHolder V_0)
  IL_0000: call class object 0.-::?()
  IL_0005: ldc.i4.4
  IL_0006: call class smallTest.Test/DataHolder smallTest.Test/Д::_(string, int32)
  IL_000b: stloc.0
  IL_000c: ldarg.0
  IL_000d: ldloc.0
  IL_000e: call int32 smallTest.Test/Д::_(class object, class smallTest.Test/DataHolder)
  IL_0013: pop
  IL_0014: ret
}

And this is IL code processed with Spices.CodeAnonymizer (without KeepReturnTypes, KeepParameterTypes options):

.method public hidebysig instance void HelloWorldTest() cil managed
{
  // Code size: 21 bytes
  .maxstack 3
  .locals ([0] class smallTest.Test/DataHolder V_0)
  IL_0000: call class object 0.-::?()
  IL_0005: ldc.i4.4
  IL_0006: call class object smallTest.Test/_::П(class object, int32)
  IL_000b: stloc.0
  IL_000c: ldarg.0
  IL_000d: ldloc.0
  IL_000e: call int32 smallTest.Test/_::П(class object, class object)
  IL_0013: pop
  IL_0014: ret
}

This is C# code processed with Spices.CodeAnonymizer (without KeepReturnTypes, KeepParameterTypes options):

public void HelloWorldTest()
{
  smallTest.Test.в.?(this, smallTest.Test.в.?(0.-.?(), 4));
}

Notice that call to this.Output method is anonymized, so giving you the following benefits:

 

  • Anonymizers have identical(same) names
  • Method execution flow made unclear;
  • Object creation is anonymized
  • Method is executed through an anonymized wrapper that uses anonymized parameters list;
  • Method return type anonymized so that it is very difficult now to understand what this method returns.
  • IL listing cannot be used with ILASM

String encryption anonymization.
AnonymizeOptions.StringEncryption setting.

String Encryption Anonymization lets hide methods that load and decrypt string data. Several decompilers can trace decryption code and so retrieve the original string data.

Spices.CodeAnonymizer hides information about parameters and returned data and so prevents decompilers from generating correct and readable code.

Note: Additional precautions prevent so called disassembling round-trips, when ILDASM code listing is used to create an assembly copy with ILASM.

 

Using stubs
AnonymizeOptions.Stubs setting

Spices.CodeAnonymizer lets you replace unobfuscated methods with anonymized stubs.

Here is the sample code:

public bool PrintMessage(string msg)
{
  System.Console.WriteLine(msg);
}

//---- usage ------
if (PrintMessage("Hello!"))
{
  //...
}

Here is the above code after anonymization:

public bool PrintMessage(string msg)

  smallTest.Test.в.?(msg);//call anonymizer stub
}

//anonymizer stub in smallTest.Test.в
internal void ?(object a_0)
{
  System.Console.WriteLine(a_0);
}

//---- usage ------
if (smallTest.Test.в.?(0.-.?()())
{
  //....
}

Notice the following key points:

  • PrintMessage method is replaced with 0 method.
  • References to this method are also replaced in other calling methods.
  • Internal calls to PrintMessage referenced to PrintMessage stub directly.
  • Referenced members anonymization also affects method 0 body.

 

public bool PrintMessage(string msg)

  smallTest.Test.в.?(msg);
}

internal void ^(object a_0)

  smallTest.Test.в.?(a_0);
}

//---- usage ------
if (smallTest.Test.в.?(0.-.?())
{
 //...
}

 

Additional information.

  • Anonymized code performance is a little bit lower than that of the original code. Assembly performance, IL-code performance is a little bit lower because of anonymization specifics. Avoid anonymizing code critical to performance. Use the NineRays.Obfuscator.DontAnonymize attribute to mark such code in your project.
  • Anonymized code cannot be verified with PEVerify, because anonymization mangles type and parameter list information. Note however, that this does not affect the way how correctly the code is executed, but all anonymizer methods are internal to processed assembly and this assembly can be installed to GAC and remains CLSCompliant.