Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / C
Article

A More Complete DLL Injection Solution Using CreateRemoteThread

Rate me:
Please Sign up or sign in to vote.
4.83/5 (48 votes)
17 Aug 200714 min read 265.5K   13K   167   33
This article explores a more complete method of injecting a DLL into another process using the CreateRemoteThread approach.

Screenshot - completeinject1.png

Contents

Introduction

Now I know a few of you are going to roll your eyes at another DLL injection article. CodeProject must already have enough, does it not? Before you stop reading and hit the back button, please take a moment to read this. You just might come across something you have not seen before. If you don't learn anything new, perhaps you could leave a review or comment on what is presented here so I can get some feedback. We are all here to learn.

The purpose of this article is to expand upon the CreateRemoteThread DLL injection method to eliminate a few flaws and add a bit of needed functionality. The core concepts of the implementation are the same, however, I take the implementation a few steps further for a more "complete" solution. I will define a "complete" solution as a solution that:

  • Implements a DLL that abides by the Microsoft "Best Practices for Creating DLLs" article
  • Can inject a DLL and clean up by freeing its allocated memory as well as optionally unloading the DLL after it is done
  • Provides error handling to let the end user know when something went wrong if the DLL could not be injected
  • And of course, uses the CreateRemoteThread API function for injection

Background Information

As stated in the introduction, I will be expanding upon the CreateRemoteThread DLL injection method. If you need a review of this technique, have a look at this CodeProject article: Three Ways to Inject Your Code into Another Process. There are many other tutorials on CodeProject that deal with this method, so feel free to reference them as well.

The next set of important information can be found in the Microsoft document: Best Practices for Creating DLLs. This handy little document goes over some important (yet often abused) practices for creating DLLs. While I have noticed that you do not have to abide by these guidelines to create a working DLL, I have designed my code and the DLL to be injected around these guidelines for more "completeness". Let me stress that I am not trying to say the Best Practices article is the way to do things; all I am saying is that the information is given to us for a reason. Let us assume we had to follow that set of guidelines. With that said, consider the following except from the document:

The ideal DllMain would be just an empty stub. However, given the complexity of many applications, this is generally too restrictive. A good rule of thumb for DllMain is to postpone as much initialization as possible. Lazy initialization increases robustness of the application because this initialization is not performed while the loader lock is held. Also, lazy initialization enables you to safely use much more of the Windows API.

In almost every article or tutorial on DLL injection, the DllMain is not an empty stub. I do understand that it does not have to be, but let us consider why hardly anyone has an "ideal" DllMain function. In the majority of all articles, you will find a call to CreateThread or LoadLibrary is made from the DllMain function. I bring this out because according to the best practices:

You should never perform the following tasks from within DllMain:
* Call LoadLibrary or LoadLibraryEx (either directly or indirectly).
* ...
* Call CreateProcess. Creating a process can load another DLL.
* ...

Once again let me stress that I am not trying to advocate that the Best Practices article is the only way to do things. Let us just assume we had to follow that set of guidelines for this tutorial. If we eliminated the calls to LoadLibrary and CreateThread in the DllMain function, our DLL would be pretty useless with the CreateRemoteThread DLL injection method. This is because there is no way to execute any other functions in the DLL from the loader with this technique. So, what do we do now?

Let us stop for a moment and consider what we do to inject our DLL into a process in the first place. After we have the target process, we first allocate a chunk of memory in a process. Next, we write the name of the DLL to inject into that process. Finally, we execute a thread that uses a kernel32 function and a pointer to that memory we allocated. While this works, it is not compatible with the Best Practices guidelines and requires a call to CreateThread or LoadLibrary to initialize the DLL.

Considering that we are already writing data into the process, why not write a DLL loader into the space we allocated? Rather than start the remote thread on the address of LoadLibrary, let us start it on user code and make the process load the injected DLL itself. By this I mean, let us write a small procedure in assembly that will load the injected DLL, get the address of an export function, and call it. By doing this, we will have an empty stub for our DllMain, any initialize code will be safely called in the export function we load and run, and we can clean up after ourselves in the end. This approach is a bit tricky and will take some extra code and work, but we are not programmers because we like things easy. Let us get started!

Additional Background Information

Before I get to the actual code, I need to explain some of the techniques and concepts I employ.

  1. "Workspace". Rather than write directly to the memory of the application for each line of code, I choose to build the code we are going to inject into the process locally. By doing this, I can quickly build up the code in the loader itself and write it all at once into the process itself. The reason I do this is to make building the code more easy. Writing assembly code is hard, but writing virtual assembly code byte by byte is even harder. In the code you will see things such as:

    C++
    workspace[workspaceIndex++] = 0xCC;

    This notation is simply writing the expected hex characters to the virtual workspace. The variable workspace is the local memory allocated as the buffer. The variable workspaceIndex is the index to write to. The postfix use of the ++ operator will allow for the current byte to be written out into the buffer and then the index increment afterwards. This allows cleaner code, since the alternative would look like:

    C++
    workspace[workspaceIndex] = 0xCC;
    workspaceIndex++;

  2. "Assume user32.dll is not loaded". In most applications, user32.dll is loaded along with kernel32.dll, but this may not always be the case. If the executable is packed with something such as UPX, ASProtect, etc..., chances are the user32.dll will not be loaded at startup when you inject the DLL. As a result, code has been added to load this DLL first and obtain the address of the MessageBoxA function. By doing this, this solution keeps to the "completeness" that is sought after.

  3. "Let the user know if something is wrong" . A bulk of this code is the error checking and MessageBox calls to let the user know what is wrong. A big flaw that I have noticed in a lot of injectors is that you do not know if the DLL was actually injected or not right off the bat. You would have to add code to the injected DLL or attach a debugger to the process to verify the DLL was actually injected. In this solution, if anything goes wrong, the user will know though a MessageBox. Furthermore, the process will be terminated. If "nothing" happens and the program loads normally, it can be assumed the DLL injection was successful, barring any errors in the injected DLL that may crash the program. If you really wanted the smallest cleanest code, you could rip out the error reporting details, but for the sake of the end users, it would probably be a better idea to leave it in.

  4. "Comment code like there is no tomorrow" . Over the years I have acquired quite a bit of knowledge. When I go back to older code I wrote to revaluate it, there are sparse comments and at times I have no idea what I was doing. When you read my code, it will be like reading another article, I use abundant comments to explain what is going on. Some comments are more valuable than others and some might seem pointless, but I try my best to keep things documented for anyone that reads my code. I know the topic of code commenting is a largely debated one, so as a warning, I prefer lots of comments in my code.

  5. "Take advantage of the lpStartAddress parameter". In order to use strings in assembly, we have to write them out somewhere in memory. If you have never done this before, you might just write out all your code first with place holders, then write out the string data, and finally update the placeholder addresses with the correct values. This works and is Ok, but it is not the best means to go about it. Instead, what you can do is write out your string data, store those addresses as they are, then write your code and fill in the addresses as you go along. This eliminates the need to update address placeholders for the strings, since you will already know the address. Since we can specify the starting address though the lpStartAddress in CreateRemoteThread, we can use that to our advantage. If this point does not make any sense, do not worry. Once you go though the code and re-read this again, it might make a little more sense.

Using the code

With all of the background information finally done, it is time to get to the code. The injection function is quite large to the comments and the design of making things as straightforward as possible. Error code is duplicated in two parts, but writing it out twice is a lot more effective than trying to make another function to reduce the line count. The code is written to generate the basic amount of assembly to accomplish our task. It is not necessary to try to optimize or reduce the size of it since it will only be called once per DLL that needs injection. To begin, I will present the function documentation:

Function:
    Inject

Parameters:
    hProcess - The handle to the process to inject the DLL into.

    const char* dllname - The name of the DLL to inject into the process.

    char* funcname - The name of the function to call once the DLL has
                     been injected.

Description:
    This function will inject a DLL into a process and execute an exported
    function the DLL to "initialize" it. The function should be in the format
    shown below, no parameters and no return type. Do not forget to prefix
    extern "C" if you are in C++.

            __declspec(dllexport) void FunctionName(void)

    The function that is called in the injected DLL    -MUST- return, the
    loader waits for the thread to terminate before removing the allocated
    space and returning control to the Loader. This method of DLL injection
    also adds error handling, so the end user knows if something went wrong.

There are only two header files needed for this code to compile on VS6, VS7, VS8, and Dev-CPP. They are:

C++
#include <windows.h>
#include <stdio.h> 

What follows now are the main marked sections of the code with additional comments. There is too much code to actually paste it into the article.

Function variables

This section defines the variables used in this function. Since the code was made to compile in C or C++, they are at the top. The variables themselves should be self-explanatory by their names, the main ones of interest are the DWORD xxxAddr = 0; set. These variables are the placeholders for the data we write into the process at the top. Refer to the "Data and string writing" section for more details.

Variable initialization

This section starts off by obtaining the handle to the kernel32.dll on the user's computer and then loads a set of functions our code will call. After that, it will build the text strings that will be written into the process. Finally, it will allocate the local workspace memory and allocate memory in the target.

Data and string writing

This section will write out all of the data and strings we need injected into the process. Included is the place holder for the user32.dll, MessageBoxA function address, and the names of the DLLs, functions, and error messages used. Before each is written out, the address is stored in the placeholder as mentioned above at the end of the "Function variables" section. At the end of this section, a few INT 3s are written out and then the final address to begin execution is saved. Note that I have added a commented out section that can be used for debugging:

C++
// For debugging - infinite loop, attach onto process and step over
    //workspace[workspaceIndex++] = 0xEB;
    //workspace[workspaceIndex++] = 0xFE;

When program execution hits this, the program enters an infinite loop. At this point, you can attach your debugger, step over this part and trace though the following code to watch it step by step. Note that you might want to change the priority of the process beforehand to idle or below average so it does not chew up your CPU.

User32.dll loading

This section will load the user32.dll and then the MessageBoxA function. I choose not to add any error checking since it should not fail. If it does fail, not quite sure why it would, the program will crash only if there is an error and the MessageBoxA function is invoked. The handle to the DLL and the function are both saved in the data area for later use.

Injected DLL loading

This section is the meat of the function. The high level implementation of this code could be viewed as follows:

C++
// Load the injected DLL into this process
HMODULE h = LoadLibrary("mydll.dll");
if(!h)
{
    MessageBox(0, "Could not load the dll", "Error", MB_ICONERROR);
    ExitProcess(0);
}

// Get the address of the export function
FARPROC p = GetProcAddress(h, "Initialize");
if(!p)
{
    MessageBox(0, "Could not load the function", "Error", MB_ICONERROR);
    ExitProcess(0);
}

// So we do not need a function pointer interface
__asm call p

// Exit the thread so the loader continues
ExitThread(0);

It seems like a little C/C++ code, but it translates into a good deal of Assembly code due to the error handling. Do not be discouraged or intimidated by it though! Follow the assembly comments in the source and you should be fine.

Exiting from the injected DLL

This section gives the user two choices on how the DLL should be exited from. The first choice is to simply exit the thread and let the DLL reside in memory. That way, the DLL itself can unload when it needs to later on, or the process will unload it on a clean exit. The second choice is to free the library and exit the thread at the same time. By doing this, the DLL will execute and then be removed from the process. To control which one is used, simply change the preprocessor define so the code that you want to execute is compiled. By default, the first choice is compiled.

C++
// Call ExitThread to leave the DLL loaded
#if 1
    ...
#endif


// Call FreeLibraryAndExitThread to unload DLL
#if 0
    ...
#endif

Code injection and cleanup

The last section deals with finally writing out the build code and then cleaning up after ourselves. Before we write to our allocated memory in the process, we first call VirtualProtect to make sure we have the right permissions. After the memory is written and the previous page access is restored, we have to call FlushInstructionCache to ensure the changes are made right away. At this point the code is written into the process so we can free the local workspace with a call to HeapFree. The only thing left to do is to execute our thread and then deallocate the memory that it once used. Now, the DLL has been injected into the process and we have cleaned up behind ourselves!

Conclusion

The fruits of our labor can be seen in the article's screenshot at the top (taken from OllyDbg). If all is done right, we now have a way to use the CreateRemoteThread DLL injection method that uses a DLL designed in accordance to the Best Practices article by Microsoft. At the end of the day, if you look at what we have done and ask yourself the question "was it worth it?", you may not have a definite answer. If the only goal is to make a DLL injector that "works", the general way of going about this is fine. However, if you were interested in a bit more robust solution, then this approach might be just it for you. Either way, thank you for reading this and I hope you have enjoyed it.

Here is a simple WinMain that acts as a loader for a process to inject a DLL into. In this example, I load the Pinball game and inject a DLL into it. Note that everything is hardcoded just for this example. Ideally, you would place the DLL to be injected in the same folder as the process as well as the loader. That makes life a bit more easier.

C++
// Program entry point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPTSTR lpCmdLine, int nCmdShow)
{
    // Structures for creating the process
    STARTUPINFO si = {0};
    PROCESS_INFORMATION pi = {0};
    BOOL result = FALSE;

    // Hardcoded just for a demo, you will need to use a game/program of
    // your own to test. It is not a good idea to use stuff in system32
    // due to the DEP enabled on those apps.
    char* exeString = 
       "\"C:\\Program Files\\Windows NT\\Pinball\\PINBALL.EXE\" -quick";
    char* workingDir = "C:\\Program Files\\Windows NT\\Pinball";

    // Holds where the DLL should be
    char dllPath[MAX_PATH + 1] = {0};

    // Set the static path of where the Inject DLL is, hardcoded for a demo
    _snprintf(dllPath, MAX_PATH, "InjectDLL.dll");

    // Need to set this for the structure
    si.cb = sizeof(STARTUPINFO);

    // Try to load our process
    result = CreateProcess(NULL, exeString, NULL, NULL, FALSE,
                           CREATE_SUSPENDED, NULL, workingDir, &si, &pi);
    if(!result)
    {
        MessageBox(0, "Process could not be loaded!", "Error", MB_ICONERROR);
        return -1;
    }

      // Inject the DLL, the export function is named 'Initialize'
    Inject(pi.hProcess, dllPath, "Initialize");

    // Resume process execution
    ResumeThread(pi.hThread);

    // Standard return
    return 0;
}

I also include a sample DLL to inject into a process to test it. I did not make a project for it however, it is up to you to make your own DLL to inject. Here is the source for it though in case you want to make one yourself.

C++
#include <windows.h>

// Define the DLL's main function
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReason, LPVOID lpReserved)
{
    // Get rid of compiler warnings since we do not use this parameter
    UNREFERENCED_PARAMETER(lpReserved);

    // If we are attaching to a process
    if(ulReason == DLL_PROCESS_ATTACH)
    {
        // Do not need the thread based attach/detach messages in this DLL
        DisableThreadLibraryCalls(hModule);
    }

    // Signal for Loading/Unloading
    return (TRUE);
}

extern "C" __declspec(dllexport) void Initialize()
{
    MessageBox(0, "Locked and Loaded.", "DLL Injection Successful!", 0);
}

You do not have to name your function Initialize, but if you change it, make sure you update your loader to use the correct function name in the call to Inject. Also make sure to verify that the compiler does not optimize out your exported function with a program such as Dependency Walker. Also make sure you do not forget to use the extern "C" on a C++ export function so the function name is not mangled.

Image 2

History

  • 2007.08.18 - Version 1.0

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionStrange behavior when attaching to 32 bit process under x64 windows 10, entire process exits at ExitThread(0); Pin
LordHexahedron30-Jul-17 21:13
LordHexahedron30-Jul-17 21:13 
QuestionNewbie question: Does the injection work automatically as soon as the target process run? Or does another process needs to run to inject the dll? Pin
jk-newbie13-May-14 14:18
jk-newbie13-May-14 14:18 
AnswerRe: Newbie question: Does the injection work automatically as soon as the target process run? Or does another process needs to run to inject the dll? Pin
pei.zhang19-Mar-17 15:31
pei.zhang19-Mar-17 15:31 
QuestionNot working on win7x64 Pin
Member 443327419-May-13 23:25
Member 443327419-May-13 23:25 
AnswerRe: Not working on win7x64 Pin
Member 443327420-May-13 13:33
Member 443327420-May-13 13:33 
GeneralRe: Not working on win7x64 Pin
Member 443327421-May-13 4:54
Member 443327421-May-13 4:54 
GeneralMy vote of 5 Pin
Mazen el Senih31-Jan-13 6:10
professionalMazen el Senih31-Jan-13 6:10 
GeneralMy vote of 5 Pin
gndnet23-Sep-12 3:26
gndnet23-Sep-12 3:26 
QuestionGetting the error Pin
shaish28-Aug-12 3:48
shaish28-Aug-12 3:48 
Questionseems one error occured Pin
ZengQuanGui24-Feb-12 8:05
ZengQuanGui24-Feb-12 8:05 
AnswerRe: seems one error occured Pin
ZengQuanGui24-Feb-12 9:28
ZengQuanGui24-Feb-12 9:28 
QuestionPlease give me your opinion Pin
henrique16095-Aug-11 2:41
henrique16095-Aug-11 2:41 
GeneralIt doesn't supported with Win7/Vista ? Pin
wangningyu26-May-11 16:15
wangningyu26-May-11 16:15 
GeneralMy vote of 5 Pin
zpy_codeproject13-Apr-11 16:02
zpy_codeproject13-Apr-11 16:02 
GeneralMy vote of 5 Pin
rrossenbg3-Apr-11 8:58
rrossenbg3-Apr-11 8:58 
QuestionWhy it doesn't work without vs2010?? Pin
hitme2-Feb-11 16:49
hitme2-Feb-11 16:49 
GeneralMr Proffesor, you're very great. [modified] Pin
nenfa8-Feb-10 16:51
nenfa8-Feb-10 16:51 
GeneralRe: Mr Proffesor, you're very great. Pin
nenfa8-Feb-10 21:32
nenfa8-Feb-10 21:32 
GeneralInjecting Multiple DLLs into a process Pin
W4Rl0CK4727-Oct-09 8:05
W4Rl0CK4727-Oct-09 8:05 
GeneralResource Leak Pin
krishy195-Aug-09 5:17
krishy195-Aug-09 5:17 
GeneralExiting after Injection Pin
larrystevens200725-Feb-09 12:47
larrystevens200725-Feb-09 12:47 
GeneralRe: Exiting after Injection Pin
Drew_Benton25-Feb-09 13:29
Drew_Benton25-Feb-09 13:29 
GeneralRe: Exiting after Injection Pin
larrystevens200725-Feb-09 17:23
larrystevens200725-Feb-09 17:23 
GeneralInteresting Link collection Pin
Elmue29-Sep-08 17:06
Elmue29-Sep-08 17:06 
QuestionHow to inject on the fly? Pin
pcspeaker14-May-08 16:27
pcspeaker14-May-08 16:27 
Hi, your article is cool, It works! But is it possible to inject my dll after the target process is launched?
Thank you very much for your kind answer, Big Grin | :-D

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.