How To Inject a Managed .NET Assembly (DLL) Into Another Process
Friday, August 22, 2008   

Introduction

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.

Monsters, Inc.

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
  • Managed

Either our application has a big, smelly, snaggle-toothed monster sitting quietly on the barcalounger, or it doesn't.

Right?

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.

Cut.

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?

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:

HRESULT hr = pointerToTheDotNetRuntimeInterface->Start();

The only trick is getting the target process (the process into which you'd like to inject your managed assembly) to execute this code.

Let's explore.

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:

namespace MyNamespace
{
    public class MyClass
    {
        // This method will be called by native code inside the target process...
        public static int MyMethod(String pwzArgument)
        {
            MessageBox.Show("Hello World");
            return 0;
        }

    }
}

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:

#include "MSCorEE.h"

void StartTheDotNetRuntime()
{
    // Bind to the CLR runtime..
    ICLRRuntimeHost *pClrHost = NULL;
    HRESULT hr = CorBindToRuntimeEx(
        NULL, L"wks", 0, CLSID_CLRRuntimeHost,
        IID_ICLRRuntimeHost, (PVOID*)&pClrHost);

    // 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"c:\\PathToYourManagedAssembly\\MyManagedAssembly.dll",
        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.
    pClrHost->Release();
}

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.

  1. Call CorBindToRuntimeEx in order to retrieve a pointer to the ICLRRuntimeHost interface.
  2. Call ICLRRuntimeHost::Start in order to launch the .NET runtime, or attach to the .NET runtime if it's already running.
  3. 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.

Conclusion

That's it.

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.

Good luck.


Posted by James Devlin   36 comment(s)

It's interesting. This is all straightforward stuff and yet I would say not 1 out of 5 .NET developers understand the foundation on which their code runs. That's why when you do searches for this kind of information, there's really not much there. I'd like to see a follow-up to this post describing, from the perspective of a typical programmer, how to support large-scale CLR integration in native projects?

Or maybe there is some resource out there already which describes this? Maybe? Possibly?

Anonymous on 8/23/2008 7:21 AM (565 days ago)

Also, how did you do the blue diagram under 'create bootstrap dll' with the arrows?

Anonymous on 8/23/2008 7:22 AM (565 days ago)

The post I've been waiting for. Thanks. Now I will be converting all your code to managed. Thanks. Smile

Ed K. on 8/23/2008 9:16 AM (565 days ago)

I just knew I was going to learn something new today. Now on to my normal weekend allusion, ie cutting the grass....

Anonymous on 8/23/2008 9:40 AM (565 days ago)

I just, okay. Is there NO WAY TO DO THIS WITHOUT RESORTING TO BITS AND PIECES NATIVE CODE?

Let's say you live in a world in which C# is your only tool and you can't write the C++ shim DLL.

What then?

Angry on 8/23/2008 10:48 AM (565 days ago)

Lol the bit about the matrix made me laugh. Another great article. Keep up the good work you're doing great at distracting me from revising today.

Poker is Rigged | Forums on 8/23/2008 10:56 AM (565 days ago)

I came across your page from your profile on the StackOverflow beta. Good article, but you should update it to mention that if your class library is a different version of .NET (i.e., injecting a .NET 3.5 library into a .NET 2.0 host) you can cause problems for the target app.

Anonymous on 8/23/2008 11:22 AM (565 days ago)

lolz. kudos.

Singularity on 8/23/2008 11:23 AM (565 days ago)

I ran the code, works great. Kewl. Well I haven't tried it with any poker clients, but it works.

Anonymous on 8/23/2008 2:40 PM (565 days ago)

What a fast update! You're spoiling us!

Terry Smith on 8/23/2008 5:15 PM (564 days ago)

Don't stop using the Matrix James! There are few things that don't fit with the Matrix. And the designers of the Matrix (the super-artificially-intelligent robots) obviously directly write all their code in binary.

ehsanul on 8/24/2008 12:47 AM (564 days ago)

These are the kinds of posts I mean. I vote for more of these posts, and less of the botting posts. Enough poker botting, already. Let's just talk programming.

Anonymous on 8/25/2008 1:10 PM (563 days ago)

http://www.codeplex.com/easyhook

Anonymous on 8/25/2008 3:08 PM (563 days ago)

Anonymous... I believe you are missing the entire point of the website...

Anonymous on 8/25/2008 3:20 PM (563 days ago)

Im using easyhook to inject my dll in a poker client. It works great but the problem is that the client doesn's use DrawText, DrawTextEx or ExtTextOut to write text to the chat box (everyones action is printed in the chat box). If I use the browser version of the same poker client it works with the ExtTextOut api though. Any ideas on which api to hook for the .exe version of the poker client?

Could it be that the exe version uses images to print the text in the chat box?

Great articles btw

Anonymous on 8/25/2008 4:49 PM (562 days ago)

Before you go converting your all your code to .NET, go download a copy of Reflector and look at how easy it is to decompile any .NET assembly (dll)

http://www.red-gate.com/products/reflector/

Anonymous on 8/26/2008 4:41 PM (561 days ago)

So why would we use this way rather than using the method of InstallingHook that would done in building a poker bot part 1?

LastChance on 8/27/2008 3:36 PM (560 days ago)

Me encanta esta pagina!
Saludos desde ARgentina!

Juan Manuel Rojas Cavaliere on 8/27/2008 8:07 PM (560 days ago)

Ah well. I guess it's about time for Daniel Radcliffe to start posting again. He still hasn't told us if he boned the chick who plays Hermione yet.

Terry Smith on 9/3/2008 6:41 AM (554 days ago)

lol @ terry's comment but somehow i don't think emma watson is the type of chick you "bone"...

JeremyX on 9/3/2008 6:43 AM (554 days ago)

What, are you saying she's unbonable, like the chick in Code Monkeys?

Sorry about these off topic posts folks, but I'm bored and, like Tucker max, when I get bored I make my own entertainment.

If only there was, say, a POKER BOT posting to unbore me....

Terry Smith on 9/4/2008 3:24 PM (553 days ago)

Does this work on Mono? Anybody know?

Anonymous on 9/5/2008 4:56 AM (552 days ago)

I don't know if you knew this, but the RSS feed hasn't updated yet...

Terry Smith on 9/6/2008 2:58 PM (551 days ago)

Great Article! I'm not very familiar with what goes on "behind the scenes" in the .NET runtime process. I was wondering, though, is the a way of loading .NET assembly from your bootstrap dll straight from a memory structure instead of a managed dll? In other words, instead of this line:

pClrHost->ExecuteInDefaultAppDomain(L"c:\\PathToYourManagedAssembly\\MyManagedAssembly.dll", ....

have something like:

pClrHost->ExecuteInDefaultAppDomain(*pointer_to_mem_location_full_of_.NET_assembly, ...

If it is feasible, let me know. I have a few other things I'd like to run by you in regards to this. Please hit me up in email if you get the chance.

cd on 9/11/2008 3:12 AM (546 days ago)

I am feeling a little stupid, so ok, you have 'injected' your c# code into your target process, how do you then overload the print text methods? I thought this had to be done with c++ or am I missing the point (which wouldn't suprise me!).

Flava on 11/21/2008 8:38 PM (474 days ago)

This is a great article, just the one i've been waiting for ! Thanks !

But i faced a problem here,

I've created the native dll and the .net assembly dll with a simple class.

In the native dll:
I've called CorBindToRuntimeEx, i successfully got a pointer to ICLRRuntimeHost.

When i try to ->Start(), the process actually loads the run time, the workstation dll, and mscorlib.dll and then the CPU usage gets to 45% and the application keeps running normally, BUT upon any user input to the application (like clicking on a button) it completely hangs up, and the CPU usage goes to almost 50% and stays like that until i End Task the application.

So i couldn't get it to ->Start(). Frown

Anyone can help me here? and Thanks.

Anmar on 12/11/2008 11:06 AM (455 days ago)

Just to give more information about my problem that could help.

My code runs by a simple call from DllMain, at DLL_PROCESS_ATTACH

Any help appreciated

Anmar on 12/11/2008 11:09 AM (455 days ago)

Ok, i solved it. The problem was that i was doing this from the DllMain.
I created a thread and let everything happen from the thread, and it worked successfully.

Thanks for the nice article..

Anmar on 12/11/2008 7:35 PM (454 days ago)

Exactly what I was looking for. Very nice, thanks. Please don't stop using the matrix in your posts. Wait. You didn't. You only wanted us to think you did. Maybe I should get some sleep...

Lunar Hamster on 1/13/2009 5:33 AM (422 days ago)

Is there a way to handle garbled input in the lpString parameter of the ExtTextOutW API function? I've tried hooking it in C# and the hook works - but the contents lpString doesn't belong to the unicode charset (passing it on to the original ExtTextOutW works fine though).

Thanks

Anonymous on 2/3/2009 5:37 PM (400 days ago)

Thanks!

Poker Bot on 6/2/2009 6:25 PM (281 days ago)

Bytecode, P-Code, Assembly,... All the same? The point you make is true, on the lowest level they are,... but isn't everything on the lowest level the same? aka nothing...

Laughing

Anonymous on 7/8/2009 11:00 AM (246 days ago)

I had the chance to use this example in an IPC case, and I found it useful, straightforward.
I have two questions:
1 - how can I get back from C# a string of characters (BSTR, TCHAR) by calling ExecuteInDefaultAppDomain() instead of a DWORD, the dwRet in your example.
2 - I understand that in the calling (native) process space is in fact loaded just the boostrapper, and that the C# objects are "elsewhere". This is important to know - idf true - since it solves major IPC issues in mobile environments (if the construction works for Compact Frameworks ...)
Thanks!

Opariti on 12/11/2009 1:15 PM (90 days ago)

Hello, where can I find MSCorEE.h file? i need it to compile c++ dll

thanks

Manu on 1/25/2010 9:02 AM (45 days ago)

I tried the code and was able to inject the dll, but my manage dll didn't run?
Any help?

Thanks

Tri on 1/28/2010 6:35 AM (42 days ago)

If I run the code from .exe (without injection) then it work. When I tried to run it by using injection, the managed code didn't do nothing.

Do you know why? Thanks

Tri on 1/29/2010 5:17 AM (41 days ago)

Comment on this post:

Thanks for your interest in Coding the Wheel. All fields are optional.