Before reading this article, I highly recommend reading the previous part.
1.NET Binary Reverse Engineering: Part 1
Abstract
The first article of this series touched on the most significant aspect of the MSIL code instructions, for instance, how is a program authored in ILASM code and how to define the basic components (classes, fields, functions, and methods). In this article, we shall encounter the use of rudimentary IL code, various data types, opcode instructions, and remaining sophisticated features (interfaces, boxing, and branching) of the .NET CLR and ILASM coding. After that, we"'ll get an understanding of the detailed analysis of each opcode instruction, and how to integrate an IL code into an existing high-level C# written code, and how to convert an already built C# code into IL code directly, in order to free the programmer from writing complex IL instruction code.
CIL Data Types
CIL also has data types like other high-level languages for mapping the data into their specific compartment. The following table shows how a .NET base class type maps to the equivalent C# keyword and how each C# keyword maps into CIL codes with a constant.

MSIL Code Labels
Perhaps you would have noticed in the earlier article sample code in which each line of implementation is prefixed or annotated with a special token of the form IL_XXX (for example, IL_0000, IL _0002). These tokens are called code labels and they are completely optional and can be named in any manner. When we dump the assembly source code file using ILDASM.exe, it will automatically generate code labels. However, you may change them to make the code more descriptive. We can extract the token information from an assembly using the following command.
ILDASM /Token test.exe
This command produces the corresponding token information with IL_XXXX as in the following.
.method /*06000001*/ private hidebysig static
void Main(string[] args) cil managed
{
.entrypoint
.maxstack 2
.locals init ([0] string str)
IL_0000: nop
IL_0001: ldstr "Ajay"
IL_0006: stloc.0
IL_0007: ldstr "Hello"
IL_000c: ldloc.0
IL_000d: call string
IL_0012: call void
IL_0017: nop
IL_0018: ret
}
We can transform the label information into more descriptive information as in the following (it doesn't matter what information we are putting in the label information because they are optional).
- Nothing_1: nop
- Load String: ldstr Ajay
- Memory_Loca1: stloc.0
- Load Constant: ldstr Hello
- Memory_Loca2: ldloc.0
- Print_console: call string
- Call Method: call void
- Nothing_2: nop
- Leave Function: ret
MSIL Opcodes
This section will explain various MSIL instructions that are generally termed operation codes (opcodes). Some of the instructions have already appeared in the previous article sample code but they have not been reviewed in detail so far. Opcodes are typically CIL tokens used to build the implementation logic, such as if you need to load a string variable into memory, you need to use the ldstr opcode rather than the friendly Load Function. The complete set of CIL opcodes can be grouped into the following three broad segments.
- Retrieve Instructions
- Control Instructions
- Operations Instructions


Detailed Analysis of Opcode Instructions
We have concentrated on individual opcode instructions up until now to understand each opcode instruction's meaning in detail, we, therefore, are presenting some complex sample code that encapsulates numerous tasks such as executing a loop, creating new class types, and so on. Basically, our prime motive is to encounter multiple instruction sets.
The following C# code does the addition of two local integer variables.
public int Operation(int a, int b)
{
return (a + b);
}
Now the preceding code will be converted into its corresponding CIL code and interpreted in opcode terminology as in the following.
.method public hidebysig instance int32 Operation(int32 a, int32 b) cil managed
{
.maxstack 2
// Initialize the Local variable “a” and “b”
.locals init ([0] int32 a, [0] int32 b)
// Blank Instruction, no operation
IL_0000: nop
// Loading a, b into memory
IL_0001: ldarg.1
IL_0002: ldarg.2
// Performing Addition of a and b
IL_0003: add
// Store the calculated value at index ‘0’
IL_0004: stloc.0
// Jump to IL_0007 instruction
IL_0005: br.s IL_0007
// Load this value at index ‘0’
IL_0007: ldloc.0
// exiting from Method
IL_0008: ret
}
Branching
The iteration is done using for, foreach, and while loop constructs in the C# programming language. Here, the following C# code simply executes a for loop 7 times and adds all the numbers from 1 to 5 until the loop local variable reaches 5 as in the following.
public int braching()
{
int x = 0;
for (int i = 0; i < 7; i++)
{
x = x + i;
if (i == 5)
break;
}
return x;
}
Here, the blt, be, and bgt opcodes are used to create a break in the flow when some condition has been met. Here, the CIL opcode labels would be interpreted as the following.
.method public hidebysig instance void braching() cil managed
{
.maxstack 2
.locals init ([0] int32 x, [1] int32 i, [2] bool CS$4$0000)
IL_0000: nop
// Load “x” value into memory
IL_0001: ldc.i4.0
// Store ‘x’ value at index ‘0’
IL_0002: stloc.0
// Load “i” value into memory
IL_0003: ldc.i4.0
// Store ‘i’ value at index ‘1’
IL_0004: stloc.1
// Jump to IL_001e instruction
IL_0005: br.s IL_001e
IL_0007: nop
// Load Value of ‘x’ variable at index ‘0’
IL_0008: ldloc.0
// Load Value of ‘i’ variable at index ‘1’
IL_0009: ldloc.1
// Add current value on the memory at index ‘0’
IL_000a: add
// Store addition value in the local variable 0
IL_000b: stloc.0
// Load value of local variable 1 in memory
IL_000c: ldloc.1
// Load Integer value 5 into memory
IL_000d: ldc.i4.5
// Test the Equality
IL_000e: ceq
// Load integer value 0 into memory
IL_0010: ldc.i4.0
// Compare two variables
IL_0011: ceq
// Retrieve value from memory and store in variable 2
IL_0013: stloc.2
// Load value of local variable 2 on memory
IL_0014: ldloc.2
// branch to IL_0019
IL_0015: brtrue.s IL_0019
// Jump to IL_0026 instruction
IL_0017: br.s IL_0026
// No Instruction
IL_0019: nop
// Load value of local variable 1 on memory
IL_001a: ldloc.1
// Load Integer value 0 into memory
IL_001b: ldc.i4.1
// Perform Addition
IL_001c: add
// Store Addition value in the local variable 1
IL_001d: stloc.1
// Load value of local variable 1 in memory
IL_001e: ldloc.1
// Load integer value 7 into memory
IL_001f: ldc.i4.7
// Compare less than
IL_0020: clt
// Get value from stack and store in variable 2
IL_0022: stloc.2
// Load the value of 2 in memory
IL_0023: ldloc.2
// branch to IL_0007 ( if the integer value is non-zero)
IL_0024: brtrue.s IL_0007
// Load the value of 0 in memory
IL_0026: ldloc.0
// Calling Console.WriteLine() method
IL_0027: call void [mscorlib]System.Console::WriteLine(int32)
IL_002c: nop
IL_002d: ret
}


Hemant MahajanPosted May 30, 2016, 4:45 AM
Hi Ajay very informative article this is. Kudos!!. A quick question for you: .locals init ([0] int32 a,[0] int32 b) Does this statement decide the size of variable allocated on stack OR // Load Integer value 10 into memory 08. IL_0001: ldc.i4.s 10 this opcode decide the size of memory allocation to store this variable value?
Sam HobbsPosted Oct 26, 2014, 4:18 PM
I thought that the CIL is an assembly language for a virtual machine (like the Java bytecode of the Java Virtual Machine). Is it? The CIL virtual machine is the Common Language Runtime (CLR), correct? If the CIL is an assembly language then that is why the instructions are called opcodes. Assembler languages (each one varies based on the machine instructions) are never easy, right?
Sam HobbsPosted Oct 26, 2014, 3:59 PM
The article describes both the MSIL and the CIL. Is the MSIL the same thing as the Common Intermediate Language (CIL)? Is the CIL in this article the same as ECMA standard 335?
Anupam SinghPosted Oct 26, 2014, 4:10 AM
good explanation of IL. very useful.