I recently remembered a fellow student who, a couple of years ago, told me that he injected a (native) DLL into a foreign process and started the .NET runtime there, that is, had C# code run in a foreign native process. Well, bright eyes on my side and then I forgot about it.
Anyway, some days ago I gave it a try and it found out that this isn’t too hard to achieve. Running your .NET assembly in a native application requires basically four steps using the .NET 4 Hosting Interfaces:
- Retrieving an interface to the CLR (meta) host.
- Requesting an interface to the required runtime.
- Retrieving the actual interface of the runtime.
- Executing the assembly in the default application domain.
So here we go, ignoring all return codes on the way:
Step 1: Retrieve the CLR Meta Host interface
First of all, we reference mscoree.dll
and include the required headers.
#pragma comment(lib, "mscoree.lib") // [sic] #include <mscoree.h> #include <metahost.h>
We now create an instance of the CLR meta host interface. This is basically the mother of all hosts, and we’ll use it to fetch information about a specific runtime later.
ICLRMetaHost *pMetaHost = NULL; CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
Step 2: Request an interface to the runtime
Using the meta host, we’ll request an interface describing the version and status of the runtime we’d like to start.
ICLRRuntimeInfo *pRuntimeInfo = NULL; pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
In this case, we request the .NET runtime v4.0.30319. Note that, according to the documentation, the v
is mandatory and the given folder has to exist in your %SystemRoot%\Microsoft.NET\Framework
directory.
Step 3: Retrieve and start the runtime
From the given ICLRRuntimeInfo
we can now retrieve the ICLRRuntimeHost
interface, which is the .NET 4 equivalent to the now deprecated .NET 1 ICorRuntimeHost
, an interface that enables us to start and stop the CLR, create and control domains.
ICLRRuntimeHost *pClrRuntimeHost = NULL; pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pClrRuntimeHost)); // fire up the runtime. note that this step may // take ages when running with an attached debugger. pClrRuntimeHost->Start();
Step 4: Execute your assembly in the default application domain
Assuming your assembly contains a class YourClass
in the Your.Namespace
namespace and that this class contains a method static int YourMethod(string parameter)
, this is how you invoke it:
static const LPCWSTR assemblyPath = L"C:\\path\\to\\your\\assembly.dll"; static const LPCWSTR classFqn = L"Your.Namespace.YourClass"; static const LPCWSTR methodName = L"YourMethod"; static const LPCWSTR parameter = L"your argument"; DWORD dwRet = 0; pClrRuntimeHost->ExecuteInDefaultAppDomain( assemblyPath, classFqn, methodName, parameter, &dwRet);
And that’s it. What’s left is partying hard and a bit of cleanup if you’re a nice guy. Or gal. Or person.
Step 5 and 6: ??? and Profit
Rumor has it that shutting down and releasing the runtime itself isn’t necessarily needed; You should release the meta host though.
// you might skip these steps in order to keep the runtime running pClrRuntimeHost->Stop(); pClrRuntimeHost->Release(); // release the meta host pMetaHost->Release();
Either way, now’s a good time for a cup of coffee. And that party.
As for the whole injection thing, I have some patchy hacky code on GitHub.