Appearance
Last review: Sept 15,2025
The previous parts of this article explain how a PE loader runs an executable. However, if you build your binary with C#, things are a bit different. Indeed, the result of a C# binary will be a .NET assembly.
Basically, an assembly is a series of instructions in an intermediate language. It cannot run directly on the processor but needs to be interpreted by the Common Language Runtime (CLR).
If we want our PE loader to be able to execute such an assembly, we need to run the CLR first.
Let’s take the following C# code as an example, which can be built with csc.exe Program.cs, resulting in an assembly (which is also in PE format):
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CLRHello1
{
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello world");
return;
}
public static int spotlessMethod(String pwzArgument)
{
Console.WriteLine("Hi from CLR");
return 1;
}
}
}The following code provides a well-known example of how to load the generated assembly. More information can be found in the references provided at the end of this page.
CPP
#include <iostream>
#include <windows.h>
#include <mscoree.h>
#include <metahost.h>
#pragma comment(lib, "MSCorEE.lib")
#import "mscorlib.tlb" auto_rename
bool readBinFile_mem_v1(const char fileName[], char*& bufPtr, DWORD& length) {
if (FILE* fp = fopen(fileName, "rb")) {
fseek(fp, 0, SEEK_END);
length = ftell(fp);
bufPtr = new char[length + 1];
fseek(fp, 0, SEEK_SET);
fread(bufPtr, sizeof(char), length, fp);
return true;
}
else return false;
}
int main(int argc, wchar_t* argv[]) {
PCHAR ptrBinary;
DWORD lenBinary;
bstr_t bstrClassName("CLRHello1.Program");
bstr_t bstrStaticMethodName(L"spotlessMethod");
variant_t vtStringArg(L"test");
mscorlib::_TypePtr spType = NULL;
SAFEARRAY* psaStaticMethodArgs = NULL;
SAFEARRAY* pSafeArray = NULL;
variant_t vtEmpty;
HRESULT hr;
LONG index = 0;
VARIANT _result;
ICLRRuntimeInfo* pRuntimeInfo = NULL;
ICorRuntimeHost* pRuntimeHost = NULL;
ICLRMetaHost* pMetaHost = NULL;
mscorlib::_AssemblyPtr pAssembly = NULL;
void* pvData = NULL;
IUnknownPtr pAppDomainThunk = NULL;
mscorlib::_AppDomainPtr pDefaultAppDomain = NULL;
readBinFile_mem_v1("C:/Users/seb/GIT/REDTEAM_CSharp-Loader/Program.exe", ptrBinary, lenBinary);
CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (VOID**)&pMetaHost);
pMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (VOID**)&pRuntimeInfo);
pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (VOID**)&pRuntimeHost);
pRuntimeHost->Start();
// fetch the default domain
pRuntimeHost->GetDefaultDomain(&pAppDomainThunk);
pAppDomainThunk->QueryInterface(__uuidof(mscorlib::_AppDomain), (LPVOID*)&pDefaultAppDomain);
pSafeArray = SafeArrayCreate(VT_UI1, 1, new SAFEARRAYBOUND{ lenBinary , 0 });
SafeArrayAccessData(pSafeArray, &pvData);
memcpy(pvData, LPBYTE(ptrBinary), lenBinary);
SafeArrayUnaccessData(pSafeArray);
pAssembly = pDefaultAppDomain->Load_3(pSafeArray);
spType = pAssembly->GetType_2(bstrClassName);//ok
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg);
VariantInit(&_result);
hr = spType->raw_InvokeMember_3(bstrStaticMethodName, static_cast<mscorlib::BindingFlags>(mscorlib::BindingFlags_InvokeMethod | mscorlib::BindingFlags_Static | mscorlib::BindingFlags_Public), NULL, vtEmpty, psaStaticMethodArgs, &_result);//OK
std::cout << std::hex << hr << std::endl;
return 0;
}References
- https://stackoverflow.com/questions/31258514/loading-assemblies-from-memory-when-hosting-the-clr-in-unmanaged-programs
- https://stackoverflow.com/questions/56359368/appdomainptr-load-3-method-example
- https://gist.github.com/Arno0x/386ebfebd78ee4f0cbbbb2a7c4405f74
- https://gist.github.com/KINGSABRI/e2f7df7972fdb665972bc31b26ac1eb3
- https://gist.github.com/aaaddress1/f351d0f75448ae26bcd6ee578536112b
- https://0xpat.github.io/Malware_development_part_9
- https://www.reddit.com/r/programminghelp/comments/12icrlv/cannot_load_net_assemblies_in_memory/?tl=fr
- https://github.com/etormadiv/HostingCLR/issues/1
- https://learn.microsoft.com/en-us/windows/win32/midl/com-dcom-and-type-libraries
- https://learn.microsoft.com/en-us/answers/questions/215345/running-managed-executables-inside-an-unmanaged-ex
- https://learn.microsoft.com/en-us/answers/questions/370576/ildasm-exe-wont-work (try opening dll with ildasm)