How To Inject a Managed .NET Assembly (DLL) Into Another Process
Today we’re going to kill two birds—
- How to inject a .NET assembly (DLL) into a native process?
- How to inject a .NET assembly (DLL) into a managed process?
—with one stone: by using the CLR Hosting API.
But first, let’s talk about monsters.
To many .NET developers, the .NET runtime is like the slobbering, snaggle-toothed monster sitting in your barcalounger, that everybody in the household politely ignores because hey. It’s a frikkin’ monster; don’t make eye contact with it.
The monster occasionally grunts at you. It smells like a wet dog. And it clutters up half your living room with its bulk. But you tolerate it, because this is a useful monster. It vamooses those sticky kitchen leftovers; does the laundry; feeds the dog; vacuums the carpet; and makes sure the doors are locked at night. It takes care of you, even if your home doesn’t quite feel like your home anymore.
Now, we’re accustomed to thinking of Windows applications as being either:
- Native OR
Either our application has a big, smelly, snaggle-toothed monster sitting quietly on the barcalounger, or it doesn’t.
Well, what if I said that the whole managed-vs.-native dichotomy is an illusion that’s been pulled around your eyes to blind you to the truth?
Morpheus: The Matrix is everywhere. It is all around us. Even now, in this very room. You can see it when you look out your window or when you turn on your television. You can feel it when you go to work… when you go to church… when you pay your taxes. It is the world that has been pulled over your eyes to blind you from the truth.
Neo: What truth?
Morpheus: That you are a slave, Neo. Like everyone else you were born into bondage. Into a prison that you cannot taste or see or touch. A prison for your mind.
What truth? you ask.
That you are locked in a programmatic worldview designed to turn a human being into one of these:
Okay, scratch that.
After having a friend review this article prior to posting, I’m told I’m way out on a limb here.
James bud—you’re crazy. The .NET framework is nothing like the Matrix. Stop using the Matrix in your posts. For starters, .NET uses Unicode internally, whereas the Matrix uses [censored].
Ah. Right. The old what-programming-language-do-they-use-in-the-Matrix? debate.
I guess the point I was trying to make is this: there’s no fundamental difference between a managed process and a native one on Windows. A managed process is simply a native process in which some special code which we call the “.NET runtime” happens to be running, and in which we have access to a special library known as the “.NET framework”.
Now, this “.NET runtime” code is capable of doing some special things, to be sure. For example, it knows how to generate binary executable code on the fly in a process known as just-in-time compilation. It knows how to rig things properly such that “managed-ness” happens correctly, that our managed code runs by the rules we expect it to run by, and so forth.
But applications are not “managed” or “native”. They’re always native. Sometimes they eruct an infrastructure known as the managed runtime, and we can then start calling them “managed” but they never lose that core nativity. In fact, it’s impossible to execute managed code without executing native code! The entire phrase is a misnomer!
There is no such thing as managed executable code. Only managed code which is later converted to executable code.
Like everything else in programming, the managed runtime is an illusion, albeit a useful one.
Loading the .NET Runtime Yourself
So if a managed process is simply a native process in which some special code is running, there must be a way to load the .NET infrastructure into a native, non-.NET process, right?
Surely this is a complex and messy process, requiring special knowledge of Windows and .NET framework internals?
Maybe, but all the complexity has been placed behind one of these:
I say this because starting the .NET runtime is (pretty much) a one-liner, by design:
The only trick is getting the target process (the process into which you’d like to inject your managed assembly) to execute this code.
Step 1: Create the Managed Assembly
So you have some managed code you’d like to run inside the target process. Package this code inside (for example) a typical .NET class library. Here’s a simple C# class containing one method:
public class MyClass
// This method will be called by native code inside the target process…
public static int MyMethod(String pwzArgument)
This method should take a String and return an int (we’ll see why below). This is your managed code entry point—the function that the native code is going to call.
Step 2: Create the Bootstrap DLL
Here’s the thing. You don’t really “inject” a managed assembly into another process. Instead, you inject a native DLL, and that DLL executes some code which invokes the .NET runtime, and the .NET runtime causes your managed assembly to be loaded.
This makes sense, as the .NET runtime understands what needs to happen in order to load and start executing code in a managed assembly.
So you’ll need to create a (simple) C++ DLL containing code similar to the following:
// Bind to the CLR runtime..
ICLRRuntimeHost *pClrHost = NULL;
HRESULT hr = CorBindToRuntimeEx(
NULL, L“wks”, 0, CLSID_CLRRuntimeHost,
// Push the big START button shown above
hr = pClrHost->Start();
// Okay, the CLR is up and running in this (previously native) process.
// Now call a method on our managed C# class library.
DWORD dwRet = 0;
hr = pClrHost->ExecuteInDefaultAppDomain(
L“MyNamespace.MyClass”, L“MyMethod”, L“MyParameter”, &dwRet);
// Optionally stop the CLR runtime (we could also leave it running)
hr = pClrHost->Stop();
// Don’t forget to clean up.
This code makes a few simple calls to the CLR Hosting API in order to bind to and start the .NET runtime inside the target process.
- Call CorBindToRuntimeEx in order to retrieve a pointer to the ICLRRuntimeHost interface.
- Call ICLRRuntimeHost::Start in order to launch the .NET runtime, or attach to the .NET runtime if it’s already running.
- Call ICLRRuntimeHost::ExecuteInDefaultAppDomain to load your managed assembly and invoke the specified method—in this case, “MyClass.MyMethod”, which we implemented above.
The ExecuteInDefaultAppDomain loads the specified assembly and executes the specified method on the specified class inside that assembly. This method must take a single parameter, of type string, and it must return an int. We defined such a method above, in our C# class.
ExecuteInDefaultAppDomain will work for the majority of applications. But if the target process is itself a .NET application, and if it features multiple application domains, you can use other methods on the ICLRRunTimeHost interface to execute a particular method on a particular domain, to enumerate application domains, and so forth.
The single toughest thing about getting the above code running is dealing with the fact that the CLR Hosting API is exposed, like many core Windows services, via COM, and working with raw COM interfaces isn’t everybody’s idea of fun.
Step 3: Inject the Bootstrap DLL into the Target Process
The last step is to inject the bootstrap DLL into the target process. Any DLL injection method will suffice, and as this topic is covered thoroughly elsewhere on the Internet and here on Coding the Wheel, I won’t rehash it. Just get your bootstrap DLL into the target process by any means necessary.
Of course, we’ve only scratched the surface of the CLR Hosting API and its powers. For a more in-depth description, consult Bart De Smet’s CLR Hosting Series. If that’s more detail than you need, Damian Mehers has an interesting post describing how he used .NET code injection to tweak Windows Media Center. Last but not least, if you’re planning on hosting the CLR in production code, you might want to go all-out and pick up a copy of Customizing the Microsoft .NET Framework Common Language Runtime from Microsoft Press, though this shouldn’t be necessary for everyday use.
And for those of you who are following the poker botting series, this technique allows you to have the best of both worlds: you can create AI or controller logic using a managed language like C#, while retaining most of the benefits of having code run inside the poker client process.