1
votes

I've written a dll in C which I'm injecting via CreateRemoteThread() into a C console program.

The C program simply calls Sleep(INFINITE), basically acting as a host for the injected dll.

This is DllMain:

HINSTANCE thisDllHandle = NULL;

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD entryReason, void *impLoad)
{
    switch (entryReason)
    {
        case DLL_PROCESS_ATTACH:
        {
            thisDllHandle = hInstance;
            HANDLE thread = (HANDLE)_beginthreadex(NULL, 0, Boss, NULL, 0, NULL);
            if (thread) CloseHandle(thread);
        }
    }
    return TRUE;
}

The thread created via CreateRemoteThread() then returns, and my Boss thread is the only thread running this dll's code.

If I then tell Boss to exit, after cleaning up it calls FreeLibraryAndExitThread(thisDllHandle, 0); - the thread exits but the dll remains loaded in my host process.

Using the idea in the answer by Brandon over here, I made the host program tell me what modules are loaded and show the GlblcntUsage and ProccntUsage of the module. After injecting the dll, the counts are 1. After FreeLibraryAndExitThread(), the counts are zero - but the dll is still loaded! Why?

Incidentally, if I call FreeLibrary(thisDllHandle) instead, the host program crashes (as expected) but the dll gets unloaded.

EDIT

To summarise what I'm trying to do: I'm trying to inject a dll in a remote process by creating a remote thread that loads my dll; that dll spawns another thread which runs the same dll's code and sticks around, and the original thread exits; I then want that thread to unload the dll and exit.

Whilst trying to reduce the code in order to post it in response to @David Heffernan's comment, I managed to get it to work - by making the exiting thread call FreeLibrary before calling FreeLibraryAndExitThread. However I'd like to understand why I had to free it twice - I don't really know much about this stuff. I don't think this requires code as it's quite straightforward:

  1. Get handle to remote process.
  2. Get address of LoadLibraryW in own kernel32 module.
  3. Allocate memory in remote process, write [path to dll] to said memory.
  4. Call CreateRemoteThread on remote process handle, passing in address from step 2 as start address, and address from step 3 as parameter.

This causes the thread in the remote process to load my dll using LoadLibraryW. When my Dll is loaded, it kicks off a new thread (see DllMain code earlier) which runs some other function in my dll (the content in this other function is unimportant - I can make it sleep 10 seconds and then call FreeLibraryAndExitThread, and the behaviour is the same). The original thread I created remotely dies as LoadLibraryW returns. So now there is only one thread running my dll's code.

When that thread calls FreeLibraryAndExitThread, I expect my dll to get unloaded from the remote process, as the thread has freed the library and exited. But the dll remains loaded, and only by freeing it twice does it unload.

1
I can't work out what you are doing here. Can't we have a minimal reproducible example.David Heffernan
@DavidHeffernan See edit404
I think in this context you need to use CreateThread, not _beginthreadex, because the runtime library isn't designed with thread injection in mind. Try that and see if it helps.Harry Johnston
@HarryJohnston Indeed, using CreateThread to create the thread in DllMain results in the dll subsequently being unloaded with a single call to FreeLibraryAndExitThread.404

1 Answers

1
votes

Since you are inside an injected DllMain function, and since you are not exiting the thread by calling the _endthreadex() function, you should probably use CreateThread() instead of _beginthreadex() to launch the new thread. It might also be wise to avoid using any C runtime library functions, particularly if you cannot ensure that your C library matches that of the target process.

[Exactly why and under what circumstances the runtime library behaves in the manner described is not clear to me, but the OP reports that using CreateThread() corrected the problem. My best guess is that it has something to do with _onexit() support.]