In one of the lasts post, I’ve showed you what tools to use to play with IL. I’ve also showed how to change the entrypoint of the application. Now lets learn some basic structure of IL code. Let’s dive in.

Actually it’s not that different from C# code. We do have assemblies, classes and of course code. So it’s the same concepts as we know them from .NET – but it’s a bit different. Let’s see.

The IL structure

// Microsoft (R) .NET Framework IL Disassembler. Version 4.6.1055.0
// Copyright (c) Microsoft Corporation. All rights reserved.
// Metadata version: v4.0.30319
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0
}
.assembly HelloIL
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 14 43 6F 6E 73 6F 6C 65 41 70 70 6C 69 63 // ...ConsoleApplic
61 74 69 6F 6E 31 34 00 00 ) // ation14..
.custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 14 43 6F 6E 73 6F 6C 65 41 70 70 6C 69 63 // ...ConsoleApplic
61 74 69 6F 6E 31 34 00 00 ) // ation14..
.custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 12 43 6F 70 79 72 69 67 68 74 20 C2 A9 20 // ...Copyright ..
20 32 30 31 36 00 00 ) // 2016..
.custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::.ctor(bool) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 36 31 65 39 31 63 30 35 2D 35 30 33 31 // ..$61e91c05-5031
2D 34 31 61 37 2D 62 38 61 62 2D 30 37 39 31 33 // -41a7-b8ab-07913
39 63 36 65 61 62 61 00 00 ) // 9c6eaba..
.custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 ) // ...1.0.0.0..
.custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 1C 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B // ....NETFramework
2C 56 65 72 73 69 6F 6E 3D 76 34 2E 35 2E 32 01 // ,Version=v4.5.2.
00 54 0E 14 46 72 61 6D 65 77 6F 72 6B 44 69 73 // .T..FrameworkDis
70 6C 61 79 4E 61 6D 65 14 2E 4E 45 54 20 46 72 // playName..NET Fr
61 6D 65 77 6F 72 6B 20 34 2E 35 2E 32 ) // amework 4.5.2
.hash algorithm 0x00008004
.ver 1:0:0:0
}
.module HelloIL.exe
// MVID: {8A01AD62-12EB-4BD5-AC78-FB55512F5276}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00020003 // ILONLY 32BITPREFERRED
// Image base: 0x00C20000
// =============== CLASS MEMBERS DECLARATION ===================
.class private auto ansi beforefieldinit HelloIL.Program
extends [mscorlib]System.Object
{
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Hello IL"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Program::.ctor
} // end of class HelloIL.Program
// =============================================================
// *********** DISASSEMBLY COMPLETE ***********************
// WARNING: Created Win32 resource file hello.res
view raw hello.il hosted with ❤ by GitHub

Above we can see some Hello World program decompiled to IL. As you can see there’s a lot of strange and unknown text. Let’s just forget about them for a moment – we will discuss them in a separate post and for now just strip them. This is what the basics are:

.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 4:0:0:0
}
.assembly HelloIL
{
}
.module HelloIL.exe
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00020003 // ILONLY 32BITPREFERRED
.class private auto ansi beforefieldinit HelloIL.Program
extends [mscorlib]System.Object
{
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Hello IL"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method Program::Main
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
}
}

We start by defining that our assembly will be referencing mscorlib. The next goes the section where we define how our assembly will be named. Here also can be put all those meta attributes that we saw in the non-stripped example of Hello IL application. We don’t need to focus on those here. Let’s skip that and move to a .module directive.

Remember modules? When .NET was introduced MS put a nice vision to our heads about assemblies and modules. Assemblies could have more than one module but I that never hit off. I bet that you probably never had more than one module in assembly. Visual Studio doesn’t make that easier.

After we defined a module some directives about our EXE file are defined. The base address, some stack information and flags. Useful but not for our purpose in this article.

Now we are at what’s the mose interesting part of IL. The .class directive is to define class (duh). Here we also see the default inheritance from System.Object. Even though we did not explicitly put that in our code.

Next goes the methods, Main and and the default constructor. Also we did not specified the latter one. Main method has an .entrypoint directive set, but as we showed in
static void Main is not the .entrypoint you are looking for… it doesn’t have to be in the Main.

Inside the methods we are down to IL code and directives. For this post we will stop here. That’s all.

Of course this is only scratching the surface but it gives some idea how the code is structured. I encourage you to play with ildasm and check the code structure yourself.