Reverse engineering

.NET Reverse Engineering – 3

Ajay Yadav
July 31, 2013 by
Ajay Yadav

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

[plain]

.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

}

[/plain]

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:

[plain]

.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

}

}
[/plain]

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:

[plain]

.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

}

[/plain]

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:

[plain]

.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

}

[/plain]

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:

[c language="#"]

public static int Add(int n, int n2)

{

#if IL

ldarg n

ldarg n2

add

ret

#endif

return 0; // place holder so method compiles

}

[/c]

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.

Ajay Yadav
Ajay Yadav

Ajay Yadav is an author, Cyber Security Specialist, SME, Software Engineer, and System Programmer with more than eight years of work experience. He earned a Master and Bachelor Degree in Computer Science, along with abundant premier professional certifications. For several years, he has been researching Reverse Engineering, Secure Source Coding, Advance Software Debugging, Vulnerability Assessment, System Programming and Exploit Development.

He is a regular contributor to programming journal and assistance developer community with blogs, research articles, tutorials, training material and books on sophisticated technology. His spare time activity includes tourism, movies and meditation. He can be reached at om.ajay007[at]gmail[dot]com