.NET Reverse Engineering: Part 3

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.   .publickeytoken = (B7 7 A 5 C 56 19 34 E0 89)  
  3.     .ver 4: 0: 0: 0  
  4. }  
  5.   
  6. .assembly TestLib {}  
  7. .module TestLib.dll //mention the final type of file    
  8.   
  9.   .imagebase 0x00400000  
  10.   .file alignment 0x00000200  
  11.   .stackreserve 0x00100000  
  12.   .subsystem 0x0003  
  13.   .corflags 0x00000001 //  ILONLY    
  14.   
  15.   .class public auto ansi beforefieldinit TestLib.Magic extends[mscorlib] System.Object {  
  16.     .method public hidebysig specialname rtspecialname instance void.ctor() cil managed {  
  17.         .maxstack 8  
  18.         IL_0000: ldarg .0  
  19.         IL_0001: call instance void[mscorlib] System.Object::.ctor()  
  20.         IL_0006: nop  
  21.         IL_0007: nop  
  22.         IL_0008: nop  
  23.         IL_0009: ret  
  24.       } // end of method Magic::.ctor    
  25.   
  26.       .method public hidebysig instance string Hello(string str) cil managed {  
  27.         .maxstack 2  
  28.           .locals init([0] string CS$1$0000)  
  29.         IL_0000: nop  
  30.         IL_0001: ldstr "Hello"  
  31.         IL_0006: ldarg .1  
  32.         IL_0007: call string[mscorlib] System.String::Concat(string, string)  
  33.         IL_000c: stloc .0  
  34.         IL_000d: br.s IL_000f  
  35.   
  36.         IL_000f: ldloc .0  
  37.         IL_0010: ret  
  38.       } // end of method Magic::Hello    
  39.   
  40.       .method public hidebysig instance int32 Addition(int32 x, int32 y) cil managed {  
  41.         .maxstack 2  
  42.           .locals init([0] int32 CS$1$0000)  
  43.         IL_0000: nop  
  44.         IL_0001: ldarg .1  
  45.         IL_0002: ldarg .2  
  46.         IL_0003: add  
  47.         IL_0004: stloc .0  
  48.         IL_0005: br.s IL_0007  
  49.   
  50.         IL_0007: ldloc .0  
  51.         IL_0008: ret  
  52.       } // end of method Magic::Addition    
  53.   } 
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 7 A 5 C 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.   .ver 1: 0: 0: 0  
  12. }  
  13. .module main.exe // Define the final executable name        
  14.   
  15.   .class private auto ansi beforefieldinit Program extends[mscorlib] System.Object {  
  16.     .method private hidebysig static void Main(string[] args) cil managed {  
  17.       .entrypoint  
  18.         .maxstack 8  
  19.         .locals init([0] class[TestLib] TestLib.Magic obj) //Init magic class obj    
  20.       IL_0000: nop  
  21.       IL_0001: newobj instance void[TestLib] TestLib.Magic::.ctor() // initialize magic class constructor    
  22.   
  23.       IL_0006: stloc .0  
  24.       IL_0007: ldloc .0  
  25.       IL_0008: ldstr "Ajay" // Pass “Ajay” string in Hello method    
  26.       IL_000d: callvirt instance string[TestLib] TestLib.Magic::Hello(string)  
  27.       IL_0012: call void[mscorlib] System.Console::WriteLine(string) // print Hello method    
  28.       IL_0017: nop  
  29.       IL_0018: ldstr "Addition is:: {0}"  
  30.       IL_001d: ldloc .0  
  31.       IL_001e: ldc.i4.s 10 // define   x=10            
  32.       IL_0020: ldc.i4.s 20 //define x=20    
  33.       IL_0022: callvirt instance int32[TestLib] TestLib.Magic::Addition(int32, int32) //call Addition()    
  34.       IL_0027: box[mscorlib] System.Int32  
  35.       IL_002c: call void[mscorlib] System.Console::WriteLine(string, object)  
  36.       IL_0038: ret  
  37.     }  
  38.   
  39.   } 
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 their capacity. So it is obvious that this program throws an exception related to overflow as in the following:
  1. .assembly extern mscorlib {  
  2.     .publickeytoken = (B7 7 A 5 C 56 19 34 E0 89)  
  3.         .ver 4: 0: 0: 0  
  4. }  
  5. .assembly ExcepTest {  
  6.   
  7.     .hash algorithm 0x00008004  
  8.         .ver 1: 0: 0: 0  
  9. }  
  10. .module ExcepTest.exe  
  11.   
  12.     .imagebase 0x00400000  
  13.     .file alignment 0x00000200  
  14.     .stackreserve 0x00100000  
  15.     .subsystem 0x0003  
  16.     .corflags 0x00000003  
  17.   
  18.     // =============== CLASS MEMBERS DECLARATION ===================    
  19.   
  20.     .class private auto ansi beforefieldinit test.Program extends[mscorlib] System.Object {  
  21.         .method private hidebysig static void Main(string[] args) cil managed {  
  22.             .entrypoint  
  23.                 .maxstack 2  
  24.   
  25.                 // init two  variable x and bVar    
  26.                 .locals init([0] int32 x, [1] uint8 bVar)  
  27.             IL_0000: nop  
  28.   
  29.             // assign x= 2000    
  30.             IL_0001: ldc.i4 2000  
  31.             IL_0006: stloc .0  
  32.             IL_0007: ldloc .0  
  33.   
  34.             // convert integer to byte type (bVar=x)    
  35.             IL_0008: call uint8[mscorlib] System.Convert::ToByte(int32)  
  36.             IL_000d: stloc .1  
  37.             IL_000e: ldstr "Value="  
  38.             IL_0013: ldloc .1  
  39.             IL_0014: box[mscorlib] System.Byte  
  40.             IL_0019: call string[mscorlib] System.String::Concat(object, object)  
  41.   
  42.             // print bVal                                                          
  43.             IL_001e: call void[mscorlib] System.Console::WriteLine(string)  
  44.   
  45.             IL_0023: nop  
  46.             IL_0024: ret  
  47.         } // end of method Program::Main     
  48.     } 
    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 then thrown exception handled in the catch block as in the following:
     
      1. .method private hidebysig static void Main(string[] args) cil managed {  
      2.   .entrypoint  
      3.     .maxstack 2  
      4.     .locals init([0] int32 x, [1] uint8 bVar)  
      5.   IL_0000: nop  
      6.   IL_0001: ldc.i4 0x7d0  
      7.   IL_0006: stloc .0  
      8.     .try {  
      9.       IL_0007: nop  
      10.       IL_0008: ldloc .0  
      11.       IL_0009: call uint8[mscorlib] System.Convert::ToByte(int32)  
      12.       IL_000e: stloc .1  
      13.       IL_000f: ldstr "Value="  
      14.       IL_0014: ldloc .1  
      15.       IL_0015: box[mscorlib] System.Byte  
      16.       IL_001a: call string[mscorlib] System.String::Concat(object, object)  
      17.       IL_001f: call void[mscorlib] System.Console::WriteLine(string)  
      18.       IL_0024: nop  
      19.       IL_0025: nop  
      20.       IL_0026: leave.s IL_0038  
      21.   
      22.     } // end .try    
      23.   catch [mscorlib] System.Exception {  
      24.     IL_0028: pop  
      25.     IL_0029: nop  
      26.     IL_002a: ldstr "Size is overflow"  
      27.     IL_002f: call void[mscorlib] System.Console::WriteLine(string)  
      28.     IL_0034: nop  
      29.     IL_0035: nop  
      30.     IL_0036: leave.s IL_0038  
      31.   
      32.   } // end handler    
      33.   IL_0038: nop  
      34.   IL_0039: ret  

        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 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.   #if IL  
        3.   ldarg n  
        4.   ldarg n2  
        5.   add  
        6.   ret  
        7.   #endif  
        8.   return 0; // place holder so method compiles    

        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 disassembled 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, a thorough understanding of IL grammar, which 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.