.NET Injection Cecil

3 minute read Published:

Getting into .NET injection with Mono


This may not be news for everyone but I find it interesting. Mono.Cecil is a impressive work and can provide a lot of cool features such as runtime .NET assembly manipulation. We can inject opcodes (IL instructions) into a target assembly, transforming it as we wish. Here’s the test scenario:

A dummy C# application like the one below, compile it to get it’s executable file, that’s what we need (https://github.com/guitmz/msil-cecil-injection).

using System;

namespace Dummy
{
	class Program
	{
		public static void Main(string[] args)
		{
			Console.WriteLine("DUMMY APP HERE YO!");
			Console.ReadLine();
		}
	}
}

We also have this other application which will be our injector. You’ll need to download the Mono.Cecil DLL file and add it as reference in the injector project.

using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Diagnostics; 

namespace Injector
{
	class MainClass
	{
		public static void Main (string[] args)
		{
			Console.WriteLine("> INJECTING INTO 12345.EXE..." + Environment.NewLine);
			
			//Reading the .NET target assembly
			AssemblyDefinition asm = AssemblyDefinition.ReadAssembly(@"C:\dummy.exe");
			
			//Creating the Console.WriteLine() method and importing it into the target assembly
			//You can use any method you want
			var writeLineMethod = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) });
			var writeLineRef = asm.MainModule.Import(writeLineMethod);
			
			//Creating the Process.Start() method and importing it into the target assembly
			var pStartMethod = typeof(Process).GetMethod("Start", new Type[] { typeof(string) });
			var pStartRef = asm.MainModule.Import(pStartMethod);

			foreach (var typeDef in asm.MainModule.Types) //foreach type in the target assembly
			{
				foreach (var method in typeDef.Methods) //and for each method in it too
				{
					//Let's push a string using the Ldstr Opcode to the stack
					method.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Ldstr, "INJECTED!"));

					//We add the call to the Console.WriteLine() method. It will read from the stack
					method.Body.Instructions.Insert(1, Instruction.Create(OpCodes.Call, writeLineRef));

					//We push the path of the executable you want to run to the stack
					method.Body.Instructions.Insert(2, Instruction.Create(OpCodes.Ldstr, @"calc.exe"));

					//Adding the call to the Process.Start() method, It will read from the stack
					method.Body.Instructions.Insert(3, Instruction.Create(OpCodes.Call, pStartRef));

					//Removing the value from stack with pop
					method.Body.Instructions.Insert(4, Instruction.Create(OpCodes.Pop));
				}
			}
			asm.Write("12345.exe"); //Now we just save the new assembly and it's ready to go

			Console.WriteLine("> DONE!");
			Console.ReadKey(true);
		}
	}
}

Ok, that’s a nice start, we can print a message and execute a file from an injected assembly! Here’s the IL code from before and after the injection.

Before

.method public hidebysig static 
	void Main (
		string[] args
	) cil managed 
{
	// Method begins at RVA 0x2050
	// Code size 19 (0x13)
	.maxstack 8
	.entrypoint

	IL_0000: nop
	IL_0001: ldstr "DUMMY APP HERE YO!"
	IL_0006: call void [mscorlib]System.Console::WriteLine(string)
	IL_000b: nop
	IL_000c: call string [mscorlib]System.Console::ReadLine()
	IL_0011: pop
	IL_0012: ret
} // end of method Program::Main

After

.method public hidebysig static 
	void Main (
		string[] args
	) cil managed 
{
	// Method begins at RVA 0x2050
	// Code size 40 (0x28)
	.maxstack 8
	.entrypoint

	IL_0000: ldstr "INJECTED!!!"
	IL_0005: call void [mscorlib]System.Console::WriteLine(string)
	IL_000a: ldstr "calc.exe"
	IL_000f: call class [System]System.Diagnostics.Process [System]System.Diagnostics.Process::Start(string)
	IL_0014: pop
	IL_0015: nop
	IL_0016: ldstr "Hello World!"
	IL_001b: call void [mscorlib]System.Console::WriteLine(string)
	IL_0020: nop
	IL_0021: call string [mscorlib]System.Console::ReadLine()
	IL_0026: pop
	IL_0027: ret
} // end of method Program::Main

If you now execute the newly generated “12345.exe” file, you will get something like the this.

That’s pretty much the basics, we can easly add a method to execute any application we want inside another .NET assembly. You could also import the a WebClient and create a downloader, a file dropper or whatever. I might even use this in future projects, who knows!

TMZ