.NET Reverse Engineering: Part 3

In this article, we shall be confronted with the rest of the implementation in the context of CIL programming as such, how to build and consume DLL file components using the MSIL programming opcodes instruction set.

Before reading this article, I highly recommend reading the previous parts:
 

Abstract

As yet, we have taken a tour of the syntax and semantics of raw CIL coding. In this article, we shall be confronted with the rest of the implementation in the context of CIL programming as such, how to build and consume DLL file components using the MSIL programming opcodes instruction set. Apart from that, we will see how to integrate exception handling related opcode instructions into IL code to handle unwanted thrown exceptions. Finally, we'll explore some unconventional methods of inline IL programming by integrating its opcodes into existing high-level language source code.

Building and Consuming DLL files

Dynamic Linking Library (DLL) files are library components of business logic for reuse. We have seen the creation of DLL file components in numerous examples using the Visual Studio IDE earlier, that is in fact no rocket science at all. But it is very cumbersome to build DLLs in the CIL grammar context.

Building DLL Files

Here in the following code, two methods are defined, Hello() that simply displays a passed string over the screen and another method Addition() that takes two integer values to calculate their sum as in the following. These methods would be bundled in the final generated library file that shall be consumed later into the client application to expose its methods.

  1. .assembly extern mscorlib  
  2. {  
  3.   .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                          
  4.   .ver 4:0:0:0  
  5. }  
  6.   
  7. .assembly TestLib  
  8. {                                                                                                                                       
  9. }  
  10. .module TestLib.dll                                                                                                          //mention the final type of file  
  11.   
  12. .imagebase 0x00400000  
  13. .file alignment 0x00000200  
  14. .stackreserve 0x00100000  
  15. .subsystem 0x0003         
  16. .corflags 0x00000001    //  ILONLY  
  17.   
  18. .class public auto ansi beforefieldinit TestLib.Magic  extends [mscorlib]System.Object  
  19. {  
  20.   .method public hidebysig specialname rtspecialname  instance void  .ctor() cil managed  
  21.   {  
  22.     .maxstack  8  
  23.     IL_0000:  ldarg.0  
  24.     IL_0001:  call       instance void [mscorlib]System.Object::.ctor()  
  25.     IL_0006:  nop  
  26.     IL_0007:  nop  
  27.     IL_0008:  nop  
  28.     IL_0009:  ret  
  29.   } // end of method Magic::.ctor  
  30.   
  31.   .method public hidebysig instance string Hello(string str) cil managed  
  32.   {  
  33.      .maxstack  2  
  34.     .locals init ([0] string CS$1$0000)  
  35.     IL_0000:  nop  
  36.     IL_0001:  ldstr      "Hello"  
  37.     IL_0006:  ldarg.1  
  38.     IL_0007:  call       string [mscorlib]System.String::Concat(stringstring)  
  39.     IL_000c:  stloc.0  
  40.     IL_000d:  br.s       IL_000f  
  41.   
  42.     IL_000f:  ldloc.0  
  43.     IL_0010:  ret  
  44.   } // end of method Magic::Hello  
  45.   
  46.   .method public hidebysig instance int32 Addition(int32 x,  int32 y) cil managed  
  47.   {  
  48.     .maxstack  2  
  49.     .locals init ([0] int32 CS$1$0000)  
  50.     IL_0000:  nop  
  51.     IL_0001:  ldarg.1  
  52.     IL_0002:  ldarg.2  
  53.     IL_0003:  add  
  54.     IL_0004:  stloc.0  
  55.     IL_0005:  br.s       IL_0007  
  56.   
  57.     IL_0007:  ldloc.0  
  58.     IL_0008:  ret  
  59.   } // end of method Magic::Addition  
  60. }  

After doing the coding, compile this TestLib.il file using the ILASM.exe utility to generate the corresponding library DLL file as in the following:

ILASM.exe /dll TestLib.il



Later, it is recommended to verify the generated CIL using the peverify.exe to confirm that the generated library is compliant with the CLR, as in the following:



Consume DLL Files

The section would show how to consume the previously generated TestLib.dll file in a client executable Main.exe file. Hence, create a new file as main.il and define the external references in the form of mscorlib.dll and TestLib.dll files. Don't forget to place the TestLib.dll copy into the client project solution directory as in the following.

  1. .assembly extern mscorlib                                                                                   // Define the Reference of mscorlib.dll  
  2. {  
  3.   .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                           
  4.   .ver 4:0:0:0  
  5. }  
  6. .assembly extern TestLib                                                                                       // Define the Reference of TesLib.dll  
  7. {  
  8.   .ver 1:0:0:0  
  9. }  
  10. .assembly TestLibClient  
  11. {                             
  12.   .ver 1:0:0:0  
  13. }  
  14. .module main.exe                                                                                                        // Define the final executable name      
  15.   
  16. .class private auto ansi beforefieldinit Program extends [mscorlib]System.Object  
  17. {  
  18.   .method private hidebysig static void  Main(string[] args) cil managed  
  19.   {  
  20.     .entrypoint  
  21.     .maxstack  8  
  22.     .locals init ([0] class [TestLib]TestLib.Magic obj)                                                       //Init magic class obj  
  23.     IL_0000:  nop                                                                                                                                            
  24.     IL_0001:  newobj     instance void [TestLib]TestLib.Magic::.ctor()                         // initialize magic class constructor  
  25.   
  26.     IL_0006:  stloc.0                                                                                                                                    
  27.     IL_0007:  ldloc.0                                                                                                                                
  28.     IL_0008:  ldstr      "Ajay"                                                                                              // Pass “Ajay” string in Hello method  
  29.     IL_000d:  callvirt   instance string [TestLib]TestLib.Magic::Hello(string)                
  30.     IL_0012:  call          void [mscorlib]System.Console::WriteLine(string)                 // print Hello method  
  31.     IL_0017:  nop                                                                                                                                      
  32.     IL_0018:  ldstr      "Addition is:: {0}"                                                                                                
  33.     IL_001d:  ldloc.0                                                                                                                                
  34.     IL_001e:  ldc.i4.s   10                                                                                                   // define   x=10          
  35.     IL_0020:  ldc.i4.s   20                                                                                                  //define x=20  
  36.     IL_0022:  callvirt   instance int32 [TestLib]TestLib.Magic::Addition(int32, int32)        //call Addition()  
  37.     IL_0027:  box        [mscorlib]System.Int32                                                                        
  38.     IL_002c:  call         void [mscorlib]System.Console::WriteLine(string,  object)                     
  39.     IL_0038:  ret                                                                                                                                        
  40.   }   
  41.     

Finally, compile this program using the ILASM.exe utility and you'll notice that the main.exe file is created in the solution directory. It is also advisable to verify the generated CIL code using the peverify.exe utility.



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



Exception Handling

Sometimes when converting between data types, 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 variables and assigns some value beyond its capacity. So it is obvious that this program throws an exception related to overflow as in the following:

  1. .assembly extern mscorlib  
  2. {  
  3.   .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )      
  4.   .ver 4:0:0:0  
  5. }  
  6. .assembly ExcepTest  
  7. {  
  8.     
  9.   .hash algorithm 0x00008004  
  10.   .ver 1:0:0:0  
  11. }  
  12. .module ExcepTest.exe  
  13.   
  14. .imagebase 0x00400000  
  15. .file alignment 0x00000200  
  16. .stackreserve 0x00100000  
  17. .subsystem 0x0003      
  18. .corflags 0x00000003    
  19.   
  20. // =============== CLASS MEMBERS DECLARATION ===================  
  21.   
  22. .class private auto ansi beforefieldinit test.Program extends [mscorlib]System.Object  
  23. {  
  24.   .method private hidebysig static void  Main(string[] args) cil managed  
  25.   {  
  26.     .entrypoint  
  27.     .maxstack  2  
  28.   
  29.     // init two  variable x and bVar  
  30.     .locals init ([0] int32 x,[1] uint8 bVar)                                                                 
  31.     IL_0000:  nop  
  32.   
  33.     // assign x= 2000  
  34.     IL_0001:  ldc.i4     2000                                                                                          
  35.     IL_0006:  stloc.0  
  36.     IL_0007:  ldloc.0  
  37.   
  38.     // convert integer to byte type (bVar=x)  
  39.     IL_0008:  call       uint8 [mscorlib]System.Convert::ToByte(int32)                 
  40.     IL_000d:  stloc.1  
  41.     IL_000e:  ldstr      "Value="  
  42.     IL_0013:  ldloc.1  
  43.     IL_0014:  box        [mscorlib]System.Byte  
  44.     IL_0019:  call       string [mscorlib]System.String::Concat(objectobject)     
  45.   
  46.     // print bVal                                                        
  47.     IL_001e:  call       void [mscorlib]System.Console::WriteLine(string)                     
  48.   
  49.     IL_0023:  nop  
  50.     IL_0024:  ret  
  51.   } // end of method Program::Main   

Now compile this code and after running the executable file, the code would be unable to handle the overflow size because the Byte data type can handle the size of the data up to 255 and here since we are manipulating a value greater than 255 our code throws the exception as in the following.

The previous program was not able to handle unexpected errors that occur during program execution. In order to run the program in the appropriate order, we must include a 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 in the following:


  1. .method private hidebysig static void  Main(string[] args) cil managed  
  2.   {  
  3.     .entrypoint  
  4.     .maxstack  2  
  5.     .locals init ([0] int32 x,[1] uint8 bVar)  
  6.     IL_0000:  nop  
  7.     IL_0001:  ldc.i4     0x7d0  
  8.     IL_0006:  stloc.0  
  9.     .try  
  10.     {  
  11.       IL_0007:  nop  
  12.       IL_0008:  ldloc.0  
  13.       IL_0009:  call       uint8 [mscorlib]System.Convert::ToByte(int32)  
  14.       IL_000e:  stloc.1  
  15.       IL_000f:  ldstr      "Value="  
  16.       IL_0014:  ldloc.1  
  17.       IL_0015:  box        [mscorlib]System.Byte  
  18.       IL_001a:  call       string [mscorlib]System.String::Concat(objectobject)  
  19.       IL_001f:  call       void [mscorlib]System.Console::WriteLine(string)  
  20.       IL_0024:  nop  
  21.       IL_0025:  nop  
  22.       IL_0026:  leave.s    IL_0038  
  23.   
  24.     }  // end .try  
  25.     catch [mscorlib]System.Exception   
  26.     {  
  27.       IL_0028:  pop  
  28.       IL_0029:  nop  
  29.       IL_002a:  ldstr      "Size is overflow"  
  30.       IL_002f:  call       void [mscorlib]System.Console::WriteLine(string)  
  31.       IL_0034:  nop  
  32.       IL_0035:  nop  
  33.       IL_0036:  leave.s    IL_0038  
  34.   
  35.     }  // end handler  
  36.     IL_0038:  nop  
  37.     IL_0039:  ret  
  38.   } 

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



Inline MSIL Code

Typically, there is no provision for IL inline coding in .NET code. We can't execute an IL opcode instruction with a high-level language coding in parallel. In the following sample, we are creating a method that takes two integer types of arguments and later defines the additional functionality using IL coding instructions as in the following:

  1. public static int Add(int n, int n2)  
  2.         {  
  3.                 
  4.             #if IL  
  5.                 ldarg n  
  6.                 ldarg n2  
  7.                 add  
  8.                 ret  
  9.             #endif  
  10.             return 0; // place holder so method compiles  
  11.         } 

But a prominent developer Mike Stall has made a tool called inlineIL, that can execute IL code side by side with the existing C# code. In this process, we first compile our C# code using the regular csc or vbc compiler in debug mode and generate a *.pdb file. The compiler won't be confused with the instruction defined in the #if block and is ignored 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 that is produced from the first step as in the following:

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 inserted ILAsm as in the following.

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

Although it does not make sense to integrate IL code into a C# code file. This experiment is done just for educational purposes. We must download the Mike Stall tool to see this implementation.

Summary

As you can see, an IL opcode can directly open various new possibilities. We can drill down the opcode to manipulate it as needed. In this article, we have learned how to build your own DLL file component to consume it into front-end client programs and protect code by applying exception handling. So until now, we have obtained, thorough an understanding of IL grammar, what is substantially required for .NET reverse engineering. Now it is time to mess with hard-core reverse engineering and you will see in the forthcoming articles, how to manipulate .NET code to crack passwords, reveal serial keys and many other significant possibilities.