Introduction

We have taken tour of the syntax and semantics of raw CIL up till now. In this article, we shall be confronted with the rest of implementation in the context of CIL programming such as how to build and consume *.dll file components using MSIL programming opcodes instruction set. Apart from that, we will see how to integrate exception handling related opcode instruction into IL code in order to handle unwanted thrown exception. Finally, we’ll come across with some unconventional methods of inline IL programming by integrating its opcodes into existing high level language source code.

Building and Consuming *.DLLs files

DLLs (Dynamic Linking Library) files are deemed to library components of business logics for future reusability. We have seen creation of DLL file components in numerous examples using Visual Studio IDE earlier, which isn’t rocket science at all. But it is very cumbersome to build dll’s through CIL grammar.

Here the following code, defines two methods Hello() which simply displays a passed string over the screen and second method Addition() takes two integer values in order to calculate their sum as following:

Building DLLs File

.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
  .ver 4:0:0:0
}

.assembly TestLib
{
}
.module TestLib.dll                                                                                                          //mention the final type of file

.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003
.corflags 0x00000001    //  ILONLY

.class public auto ansi beforefieldinit TestLib.Magic  extends [mscorlib]System.Object
{
  .method public hidebysig specialname rtspecialname  instance void  .ctor() cil managed
  {
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  nop
    IL_0008:  nop
    IL_0009:  ret
  } // end of method Magic::.ctor

  .method public hidebysig instance string Hello(string str) cil managed
  {
     .maxstack  2
    .locals init ([0] string CS$1$0000)
    IL_0000:  nop
    IL_0001:  ldstr      "Hello"
    IL_0006:  ldarg.1
    IL_0007:  call       string [mscorlib]System.String::Concat(string, string)
    IL_000c:  stloc.0
    IL_000d:  br.s       IL_000f

    IL_000f:  ldloc.0
    IL_0010:  ret
  } // end of method Magic::Hello

  .method public hidebysig instance int32 Addition(int32 x,  int32 y) cil managed
  {
    .maxstack  2
    .locals init ([0] int32 CS$1$0000)
    IL_0000:  nop
    IL_0001:  ldarg.1
    IL_0002:  ldarg.2
    IL_0003:  add
    IL_0004:  stloc.0
    IL_0005:  br.s       IL_0007

    IL_0007:  ldloc.0
    IL_0008:  ret
  } // end of method Magic::Addition
}

After you finish coding, compile this TestLib.il file using ILASM in order to generate its corresponding *.dll file as the following:

ILASM.exe /dll TestLib.il

And later, it is recommended you verify the generated CIL using the peverify.exe as the following:

Consume DLLs File

It’s time to consume the previously generated TestLib.dll file into a client executable Main.exe file. So create a new file as main.il and define appropriate external reference of mscorlib.dll and TestLib.dll file. Don’t forget to place TestLib.dll copy into the client project solution directory as the following:

.assembly extern mscorlib                                                                                   // Define the Reference of mscorlib.dll
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
  .ver 4:0:0:0
}
.assembly extern TestLib                                                                                       // Define the Reference of TesLib.dll
{
  .ver 1:0:0:0
}
.assembly TestLibClient
{
  .ver 1:0:0:0
}
.module main.exe                                                                                                        // Define the final executable name

.class private auto ansi beforefieldinit Program extends [mscorlib]System.Object
{
  .method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    .maxstack  8
    .locals init ([0] class [TestLib]TestLib.Magic obj)                                                       //Init magic class obj
    IL_0000:  nop
    IL_0001:  newobj     instance void [TestLib]TestLib.Magic::.ctor()                         // initialize magic class constructor

    IL_0006:  stloc.0
    IL_0007:  ldloc.0
    IL_0008:  ldstr      "Ajay"                                                                                              // Pass “Ajay” string in Hello method
    IL_000d:  callvirt   instance string [TestLib]TestLib.Magic::Hello(string)
    IL_0012:  call          void [mscorlib]System.Console::WriteLine(string)                 // print Hello method
    IL_0017:  nop
    IL_0018:  ldstr      "Addition is:: {0}"
    IL_001d:  ldloc.0
    IL_001e:  ldc.i4.s   10                                                                                                   // define   x=10
    IL_0020:  ldc.i4.s   20                                                                                                  //define x=20
    IL_0022:  callvirt   instance int32 [TestLib]TestLib.Magic::Addition(int32, int32)        //call Addition()
    IL_0027:  box        [mscorlib]System.Int32
    IL_002c:  call         void [mscorlib]System.Console::WriteLine(string,  object)
    IL_0038:  ret
  }

}

Main.il

Finally, compile this program using ILASM.exe and you will notice that a main.exe file is created under the solution directory. It’s also recommended to verify the generated CIL code using peverify.exe utility.

Now test the executable by running it directly from the command prompt. It will produce the desired output as the following:

Exception Handling

Sometimes during conversion between different data type, our program is unable to handle unexpected occurrences of strange errors and our program does not produce the desired result or may be terminated. The following example defines Byte type variable and assigning some value beyond its capacity. So it obvious that this program throws an exception related to over size as the following:

.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
  .ver 4:0:0:0
}
.assembly ExcepTest
{

  .hash algorithm 0x00008004
  .ver 1:0:0:0
}
.module ExcepTest.exe

.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003
.corflags 0x00000003

// =============== CLASS MEMBERS DECLARATION ===================

.class private auto ansi beforefieldinit test.Program extends [mscorlib]System.Object
{
  .method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    .maxstack  2
    .locals init ([0] int32 x,[1] uint8 bVar)                                                               // init two  variable x and bVar
    IL_0000:  nop
    IL_0001:  ldc.i4     2000                                                                                        // assign x= 2000
    IL_0006:  stloc.0
    IL_0007:  ldloc.0
    IL_0008:  call       uint8 [mscorlib]System.Convert::ToByte(int32)               // convert integer to byte type (bVar=x)
    IL_000d:  stloc.1
    IL_000e:  ldstr      "Value="
    IL_0013:  ldloc.1
    IL_0014:  box        [mscorlib]System.Byte
    IL_0019:  call       string [mscorlib]System.String::Concat(object, object)
    IL_001e:  call       void [mscorlib]System.Console::WriteLine(string)           // print bVal
    IL_0023:  nop
    IL_0024:  ret
  } // end of method Program::Main
}

Now compile this code and run the executable file, the code is unable to handle the overflow size because the Byte data type can handle the size of data up to 255 and here, we are manipulating greater than 255 so our code throws the exception as the following:

The previous program was not able to handle unexpected occurring errors during the program execution. In order to run the program in the appropriate order, we must have to include try/catch block. The suspicious code that might cause some irregularities should be placed in a try block and the thrown exception handled in the catch block as the following:

.method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    .maxstack  2
    .locals init ([0] int32 x,[1] uint8 bVar)
    IL_0000:  nop
    IL_0001:  ldc.i4     0x7d0
    IL_0006:  stloc.0
    .try
    {
      IL_0007:  nop
      IL_0008:  ldloc.0
      IL_0009:  call       uint8 [mscorlib]System.Convert::ToByte(int32)
      IL_000e:  stloc.1
      IL_000f:  ldstr      "Value="
      IL_0014:  ldloc.1
      IL_0015:  box        [mscorlib]System.Byte
      IL_001a:  call       string [mscorlib]System.String::Concat(object, object)
      IL_001f:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0024:  nop
      IL_0025:  nop
      IL_0026:  leave.s    IL_0038

    }  // end .try
    catch [mscorlib]System.Exception
    {
      IL_0028:  pop
      IL_0029:  nop
      IL_002a:  ldstr      "Size is overflow"
      IL_002f:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0034:  nop
      IL_0035:  nop
      IL_0036:  leave.s    IL_0038

    }  // end handler
    IL_0038:  nop
    IL_0039:  ret
  }

After you applied the exception handling implementations in the code, now you need to compile it using ILASM and run the generated exe file. This time the try/catch block handle the thrown exception related to size overflow as following:

Inline MSIL Code

Typically, there isn’t a provision for IL Inline coding in .NET CLR. We can’t execute IL opcode instruction with high level language coding in parallel. In the following sample, we are creating a method which takes two integer type of arguments and later defines the addition functionality using IL coding instruction as:

public static int Add(int n, int n2)
        {

            #if IL
                ldarg n
                ldarg n2
                add
                ret
            #endif
            return 0; // place holder so method compiles
        }

But a prominent developer, Mike Stall has made a tool called inlineIL which can execute IL code side by side with the existing C# code. In this process, we first compile our C# code using regular csc or vbc compiler in debug mode and generate a *.pdb file. The compiler won’t confuse with instruction defined in #if block and skipped by the compiler.

csc %inputfile% /debug+ /out:%outputfile*.pdb%

The original source code is diassembled, and the ILASM opcodes are extracted and injected into the disassembly code. The line number information for the injection comes from the PDB file which produced from first step as

ildasm %*.pdb % /linenum /out=%il_output%

Finally, the modified IL code is assembled using ILASM. The resulting assembly contains everything including the code defined in the ILAsm inserts as following

ilasm %il_output% /output=%output_file *.exe% /optimize /debug

Although, it does not make sense to integrate IL code into C# code file. This experiment is done just for a knowledge point of view. We must download the tool Mike Stall developed in order to see this implementation.

Summary

As you can see, IL opcode has directly opened various ways of new possibilities. We can drill down the opcode in order to manipulate it as per our requirements. In this article, we have learned how to build our own dll file component in order to consume it into a front end clients program, and protected code by applying exception handling. So up till now, we have obtained thorough understanding of IL grammar which is substantially required for .NET reverse engineering. Now it’s time to mess with hard core reverse engineering and as you will see in the forthcoming articles, how to manipulate .NET code in order to crack passwords, reveal serial keys and lots of other significant possibilities.

Want to learn more?? The InfoSec Institute Reverse Engineering course teaches you everything from reverse engineering malware to discovering vulnerabilities in binaries. These skills are required in order to properly secure an organization from today's ever evolving threats. In this 5 day hands-on course, you will gain the necessary binary analysis skills to discover the true nature of any Windows binary. You will learn how to recognize the high level language constructs (such as branching statements, looping functions and network socket code) critical to performing a thorough and professional reverse engineering analysis of a binary. Some features of this course include:

  • CREA Certification
  • 5 days of Intensive Hands-On Labs
  • Hostile Code & Malware analysis, including: Worms, Viruses, Trojans, Rootkits and Bots
  • Binary obfuscation schemes, used by: Hackers, Trojan writers and copy protection algorithms
  • Learn the methodologies, tools, and manual reversing techniques used real world situations in our reversing lab.