c++dll-injection

Error: Module Not Found When Injecting DLL


I'm trying to learn how to inject a DLL into a process. To do so, I've set up a new project with:

When I run injector.exe, I only get the HelloWorld message box and nothing from the injected DLL (I expect another message box).

To give you more context, walking through the process with x64dbg showed that the last error was ERROR_MOD_NOT_FOUND.

Below is more information on my approach trying to solve the problem.

And here is the code and my configuration to compile it:

Injector:

#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <stdio.h>

STARTUPINFO             startupInfo;
PROCESS_INFORMATION     processInformation;


BOOL InjectDLL(HANDLE hProcess, LPCTSTR dllPath)
{
    // I read on some other stack question that size matter so i used +1 for the null terminator
    SIZE_T dllPathSize = (_tcslen(dllPath) + 1) * sizeof(TCHAR);
 
    // Allocating memory in the remote process
    LPVOID pRemoteDllPath = VirtualAllocEx(hProcess, NULL, dllPathSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (pRemoteDllPath == NULL)
    {
        return FALSE;
    }
    else
    {
        //Put some info so i can see the memory address and confirm in x64dbg
        _tprintf(TEXT("Memory allocated at: %p\n"), pRemoteDllPath);
    }

    //Writing the DLL path to the remote process
    if (!WriteProcessMemory(hProcess, pRemoteDllPath, (LPVOID)dllPath, dllPathSize, NULL))
    {
        VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
        return FALSE;
    }
    else
    {
         //Put some info so i can see the memory address and confirm in x64dbg
        _tprintf(TEXT("DLL path written to memory at: %p\n"), pRemoteDllPath);
    }

    // Reading the memory back to verify
    TCHAR* localBuffer = (TCHAR*)malloc(dllPathSize);
    if (localBuffer == NULL)
    {
        VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
        return FALSE;
    }

    if (!ReadProcessMemory(hProcess, pRemoteDllPath, localBuffer, dllPathSize, NULL))
    {
        free(localBuffer);
        VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
        return FALSE;
    }
    else
    {
        //Here i wanted to check if the memory was read correctly
        _tprintf(TEXT("DLL path read from memory: %s\n"), localBuffer);
    }

    // Even displaying the raw bytes
    for (SIZE_T i = 0; i < dllPathSize; i++)
    {
        _tprintf(TEXT("%02X "), ((BYTE*)localBuffer)[i]);
        if ((i + 1) % 16 == 0) _tprintf(TEXT("\n"));
    }
    _tprintf(TEXT("\n"));

    free(localBuffer);

    //Getting the address of LoadLibraryW in kernel32.dll
    LPVOID pLoadLibraryW = (LPVOID)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryW");
    if (pLoadLibraryW == NULL)
    {
        VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
        return FALSE;
    }

    //Creating a remote thread to load the DLL
    HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryW, pRemoteDllPath, 0, NULL);
    if (hRemoteThread == NULL)
    {
        VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
        return FALSE;
    }
    else
    {
        // Print the handle of the remote thread
        _tprintf(TEXT("Handle du thread: %p\n"), hRemoteThread);

        // Get the ID of the remote thread
        DWORD threadId = GetThreadId(hRemoteThread);
        _tprintf(TEXT("ID du thread: %lu\n"), threadId);
    }
    // Wait for the remote thread to complete
    WaitForSingleObject(hRemoteThread, INFINITE);

    // Free the allocated memory and close the thread handle
    VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
    CloseHandle(hRemoteThread);

    return TRUE;
}

int _tmain(int argc, TCHAR *argv[])
{
    TCHAR targetPath[MAX_PATH];
    TCHAR hookdllpath[MAX_PATH];
    TCHAR modulePath[MAX_PATH];
    TCHAR *lastSlash;

    // Get the path of the current executable
    if (GetModuleFileName(NULL, modulePath, MAX_PATH) == 0)
    {
        return FALSE;
    }

    // Find the last backslash in the path
    lastSlash = _tcsrchr(modulePath, '\\');
    if (lastSlash == NULL)
    {
        return FALSE;
    }

    // Replace the executable name with "helloworld.exe"
    _tcscpy(lastSlash + 1, TEXT("helloworld.exe"));

    // Initialize the STARTUPINFO structure
    ZeroMemory(&startupInfo, sizeof(startupInfo));
    startupInfo.cb = sizeof(startupInfo);

    // Initialize the PROCESS_INFORMATION structure
    ZeroMemory(&processInformation, sizeof(processInformation));

    // Starting a new process
    if (!CreateProcess(modulePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startupInfo, &processInformation))
    {
        return FALSE;
    }

    // Get the path of the DLL to inject
    if (GetFullPathName(TEXT("hooking_msg.dll"), MAX_PATH, targetPath, NULL) == 0)
    {
        return FALSE;
    }


    // Inject the DLL into the process
    if (!InjectDLL(processInformation.hProcess, targetPath))
    {
        return FALSE;
    }


    // Resume the suspended process
    ResumeThread(processInformation.hThread);

    // Wait for the process to complete
    WaitForSingleObject(processInformation.hProcess, INFINITE);

    // Close process and thread handles
    CloseHandle(processInformation.hProcess);
    CloseHandle(processInformation.hThread);

    return TRUE;
}

Compiled with:

{
            "type": "cppbuild",
            "label": "Build injector",
            "command": "cl.exe",
            "args": [
                "/Zi",
                "/EHsc",
                "/nologo",
                "/Fe:",
                "${workspaceFolder}/injector.exe", 
                "${workspaceFolder}/injector.cpp", 
                "user32.lib",
                "/link",
                "/MACHINE:X86"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$msCompile"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Task generated by Debugger."
        }

hook_msg.dll :

#include <Windows.h>
#include "MinHook.h"

typedef int (WINAPI *MESSAGEBOXW)(HWND, LPCWSTR, LPCWSTR, UINT);

// Pointer for calling original MessageBoxW.
MESSAGEBOXW fpMessageBoxW = NULL;

// Detour function which overrides MessageBoxW.
int WINAPI DetourMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)
{
    return fpMessageBoxW(hWnd, L"Hooked!", lpCaption, uType);
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{



    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        // Initialize MinHook.
        if (MH_Initialize() != MH_OK)
        {
            return FALSE;
        }

        // Get the address of MessageBoxW from user32.dll.
        HMODULE hUser32 = GetModuleHandleW(L"user32.dll");
        if (hUser32 == NULL)
        {
            return FALSE;
        }

        FARPROC pMessageBoxW = GetProcAddress(hUser32, "MessageBoxW");
        if (pMessageBoxW == NULL)
        {
            return FALSE;
        }

        // Create a hook for MessageBoxW, in disabled state.
        if (MH_CreateHook(pMessageBoxW, &DetourMessageBoxW, reinterpret_cast<LPVOID*>(&fpMessageBoxW)) != MH_OK)
        {
            return FALSE;
        }

        // Enable the hook for MessageBoxW.
        if (MH_EnableHook(pMessageBoxW) != MH_OK)
        {
            return FALSE;
        }
        break;
    }
    case DLL_PROCESS_DETACH:
    {
        // Disable the hook for MessageBoxW.
        if (MH_DisableHook(&MessageBoxW) != MH_OK)
        {
            return FALSE;
        }

        // Uninitialize MinHook.
        if (MH_Uninitialize() != MH_OK)
        {
            return FALSE;
        }
        break;
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    }
    return TRUE;
}

compiled with:

{
            "label": "build DLL",
            "type": "shell",
            "command": "cl.exe",
            "args": [
                "/LD", 
                "/EHsc",
                "/MTd",
                "/Fe:",
                "${workspaceFolder}/hooking_msg.dll", 
                "${workspaceFolder}/hooking_msg.cpp", 
                "user32.lib",
                "/I", "G:\\01_Doc\\05_Projects\\MinHook_133_lib\\include",
                "/link",
                "/MACHINE:X86",
                "/LIBPATH:G:\\01_Doc\\05_Projects\\MinHook_133_lib\\lib",
                "G:\\01_Doc\\05_Projects\\MinHook_133_lib\\lib\\libMinHook-x86-v141-mtd.lib"

                
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": ["$msCompile"]
        }

Hello world:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    MessageBoxW(NULL, L"HelloWorld", L"Message", MB_OK);
    return 0;
}

compiled with:

{
            "type": "cppbuild",
            "label": "C/C++: cl.exe build active file",
            "command": "cl.exe",
            "args": [
                "/Zi",
                "/EHsc",
                "/nologo",
                "/Fe:",
                "${workspaceFolder}/helloworld.exe", 
                "${workspaceFolder}/helloworld.cpp",
                "user32.lib",
                "/link",
                "/SUBSYSTEM:WINDOWS",
                "/MACHINE:X86"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$msCompile"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Task generated by Debugger."
        }

I first read this post on stack CreateRemoteThread() succeeds yet doesn't do anything

As it wasn't solving my issue I tryed to log some info to be sure that the path is well written in the target process, the log are ok:

injector logs

I also used a dependency walker to see if there was unsolved dependency but it seems to be ok.

Then i used x64dbg to walk throught the code of both injector and target process, first i run injector from x64dbg, I break just before the creation of the target thread. Then i attach an other instance of x64dbg to helloworld.exe to watch the call of LoadLibraryW. The dll path is correctly given to the target process with full path, yet the function LoadLibraryW end with LastError ERROR_MOD_NOT_FOUND and LastStatus STATUS_DLL_NOT_FOUND. Maybe my hook implementation is not good? Or something else... I don't know what to check now to troubleshot.

Thank you for your help.


Solution

  • You are injecting the DLL into a new process that is created with CREATE_SUSPENDED. The process's main thread is not running yet, and so other DLLs that the process uses are not loaded until the main thread begins running. That includes kernel32.dll, which your hook is trying to use.

    If you want your hook to run when the new process begins running, you will have to use a different technique to load the DLL, such as queuing an APC request to the process's main thread, or even patching the process's entry point (MS Detours can handle that for you), etc.