Inject Dll and Create Thread When Launching Process with DEBUG_PROCESS on Windows

Viewed 49

In the past, when not operating as a debugger, I have used this approach to inject a DLL and Create Thread In a Process which has worked well for me. Note: I need this to work on Windows XP 32-bit only (Although prefer methods that also work on latested OS):

#include <iostream>
#include <Windows.h>
#include <Psapi.h>
#include <pathcch.h>
#include "log.h"
#include <wchar.h>
#pragma comment(lib,"Pathcch.lib")

typedef void (WINAPI* PHookInit)();

HMODULE WINAPI GetRemoteModuleHandle(HANDLE hProcess, LPCWSTR lpModuleName)
{
    HMODULE* ModuleArray = NULL;
    DWORD ModuleArraySize = 100;
    DWORD NumModules = 0;
    WCHAR lpModuleNameCopy[MAX_PATH] = { 0 };
    WCHAR ModuleNameBuffer[MAX_PATH] = { 0 };

    if (lpModuleName == NULL) return NULL;
    ModuleArray = new HMODULE[ModuleArraySize];
    if (ModuleArray == NULL) return NULL;

    if (!EnumProcessModulesEx(hProcess, ModuleArray,
        ModuleArraySize * sizeof(HMODULE), &NumModules, LIST_MODULES_ALL))
    {
        DWORD dwResult = GetLastError();
        LOG_E("Unable to get modules in process Error %i", dwResult);
    }
    else
    {
        NumModules /= sizeof(HMODULE);
        if (NumModules > ModuleArraySize)
        {
            delete[] ModuleArray;
            ModuleArray = NULL;
            ModuleArray = new HMODULE[NumModules]; 
            if (ModuleArray != NULL)
            {
                ModuleArraySize = NumModules; 
                if (EnumProcessModulesEx(
                        hProcess, 
                    ModuleArray,
                    ModuleArraySize * sizeof(HMODULE), 
                    &NumModules, 
                    LIST_MODULES_ALL))
                {
                    NumModules /= sizeof(HMODULE);
                }
            }
        }
    }
    
    for (DWORD i = 0; i <= NumModules; ++i)
    {
        GetModuleBaseNameW(hProcess, ModuleArray[i],
            ModuleNameBuffer, MAX_PATH);
        LOG_I("Module = '%s'", ModuleNameBuffer);
        if (_wcsicmp(ModuleNameBuffer, lpModuleName) == 0)
        {
            LOG_I("Target module found!");
            HMODULE TempReturn = ModuleArray[i];
            delete[] ModuleArray;
            return TempReturn;
        }
    }

    if (ModuleArray != NULL)
        delete[] ModuleArray;

    return NULL;
}

int wmain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, INT nCmdShow)
{
    LPWSTR* argv;
    int argc;
    argv = CommandLineToArgvW(GetCommandLineW(), &argc);
    LOG_I(L"LaunchAndInject Started");

    wchar_t CurrentProcessDirectory[MAX_PATH];
    wchar_t TargetDllFilename[MAX_PATH];

#ifdef _WIN64
    wchar_t TargetDllName[] = L"HookInit64.dll";
#else
    wchar_t TargetDllName[] = L"HookInit32.dll";
#endif

    char TargetFunctionName[] = "HookInit";
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    DWORD dwTimeOut = 60000;

    if (argc < 2)
    {
        LOG_E(L"No command line parameters specified.");
        return 1;
    }

    wchar_t* cmd_pos = wcsstr(GetCommandLine(), argv[1]) - 1;

    if (cmd_pos)
    {
        if (cmd_pos[0] != L'"')
        {
            cmd_pos += 1;
        }
    }

    LOG_I(L"Command Line='%s'", cmd_pos);
    DWORD dwResult = GetModuleFileNameW(NULL, CurrentProcessDirectory, MAX_PATH);
    PathCchRemoveFileSpec(CurrentProcessDirectory, MAX_PATH);
    PathCchCombine(TargetDllFilename, MAX_PATH, CurrentProcessDirectory, TargetDllName);

    LOG_I(L"Current Directory='%s' Result='%i'", CurrentProcessDirectory, dwResult);
    LOG_I(L"Target DLL='%s'", TargetDllFilename);

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    // Start the child process. 
    if (!CreateProcess(NULL,    // No module name (use command line)
        cmd_pos,                // Command line
        NULL,                   // Process handle not inheritable
        NULL,                   // Thread handle not inheritable
        FALSE,                  // Set handle inheritance to FALSE
        CREATE_SUSPENDED,       // No creation flags
        NULL,                   // Use parent's environment block
        NULL,                   // Use parent's starting directory 
        &si,
        &pi)
        )
    {
        dwResult = GetLastError();
        LOG_E(L"CreateProcess Failed with Error #%i", dwResult);
        return 1;
    }

    LOG_I(L"Suspended Process created with PID '%i'", pi.dwProcessId);
    LOG_I("Loading Target DLL");

    // load DLL in this process first so we can calculate function offset
    HMODULE hModuleTargetDll = LoadLibraryW(TargetDllFilename);
    __int64 iTargetProcAddress = 0;
    __int64 iTargetOffset = 0;
    if (hModuleTargetDll != NULL)
    {
        iTargetProcAddress = (__int64)GetProcAddress(hModuleTargetDll, TargetFunctionName);
        iTargetOffset = iTargetProcAddress - (__int64)hModuleTargetDll;
        LOG_I("Function Target Offset = %i", iTargetOffset);
    }
    HMODULE hModuleKernel32 = GetModuleHandle(L"kernel32.dll");
    LPVOID pLoadLibraryAddress = NULL;
    
    if (hModuleKernel32 != NULL)
    {
        pLoadLibraryAddress = (LPVOID)GetProcAddress(hModuleKernel32, "LoadLibraryW");
    }
    else
    {
        LOG_E("Unable to get module handle for kernel32.dll");
    }

    if (pLoadLibraryAddress == NULL) {
        dwResult = GetLastError();
        LOG_E(L"ERROR: Unable to find LoadLibraryW in Kernel32.dll Error: %i", dwResult);
    }

    // allocate space for LoadLibrary arguments in target process
    size_t iTargetDllSize = (wcslen(TargetDllFilename) + 1) * sizeof(wchar_t);
    LPVOID pLoadLibraryArguments = (LPVOID)VirtualAllocEx(
        pi.hProcess,
        NULL,
        iTargetDllSize,
        MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);


    if (pLoadLibraryArguments == NULL) {
        dwResult = GetLastError();
        LOG_E(L"ERROR: Unable to allocate %i bytes in target process Error: %i",
            iTargetDllSize,
            dwResult);
    }
    else
    {
        if (!WriteProcessMemory(
            pi.hProcess,
            pLoadLibraryArguments,
            TargetDllFilename,
            iTargetDllSize,
            NULL))
        {
            dwResult = GetLastError();
            LOG_E("Unable to write bytes into target process address space. Error %i", dwResult);
        }
        else
        {
            LOG_I("LoadLibrary Arguments Successfully written to target process address space.");
            HANDLE hThread = NULL;
            
            if (pLoadLibraryAddress != NULL)
            {
                hThread = CreateRemoteThread(
                    pi.hProcess,
                    NULL,
                    0,
                    (LPTHREAD_START_ROUTINE)pLoadLibraryAddress,
                    pLoadLibraryArguments,
                    NULL,
                    NULL);
            }

            if (hThread == NULL) {
                dwResult = GetLastError();
                LOG_E("The remote thread calling LoadLibrary could not be created. Error %i", dwResult);
            }
            else {
                LOG_I("Remote Thread for LoadLibrary successfully created.");
                dwResult = WaitForSingleObject(hThread, dwTimeOut);
                if (dwResult == WAIT_FAILED)
                {
                    dwResult = GetLastError();
                    LOG_I("Remote Thread for LoadLibrary Failed Error %i", dwResult);
                }

                if (dwResult == WAIT_TIMEOUT)
                {
                    LOG_E("Remote Thread for LoadLibrary in hung state");
                }
                
                HMODULE hInjected = GetRemoteModuleHandle(pi.hProcess, TargetDllName);
                PHookInit pHookInit = NULL;
                if (hInjected == NULL)
                {
                    LOG_E("Unable to get module handle in target process");
                }
                else
                {
                    pHookInit = (PHookInit)((__int64)hInjected + iTargetOffset);
                }

                if (pHookInit != NULL)
                {
                    LOG_I("Running HookInit function!");
                    hThread = CreateRemoteThread(pi.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pHookInit, NULL, NULL, NULL);
                    if (hThread == NULL)
                    {
                        dwResult = GetLastError();
                        LOG_E("The remote thread calling HookInit could not be created. Error %i", dwResult);
                    }
                    else
                    {
                        LOG_I("HookInit function started!");
                        dwResult = WaitForSingleObject(hThread, dwTimeOut);
                        if (dwResult == WAIT_FAILED)
                        {
                            dwResult = GetLastError();
                            LOG_I("Remote Thread for HookInit Failed Error %i", dwResult);
                        }

                        if (dwResult == WAIT_TIMEOUT)
                        {
                            LOG_E("Remote Thread for HookInit in hung state");
                        }
                    }
                }
            }
        }
    }
    
    LOG_I("Resuming threads in target process");
    ResumeThread(pi.hThread);
    LOG_I("Process Resumed. Waiting for process to exit");

    dwResult = WaitForSingleObject(pi.hProcess, INFINITE);

    DWORD exitCode = 0;
    if (GetExitCodeProcess(pi.hProcess, &exitCode))
    {
        LOG_I("Process Terminated with exit code %i", exitCode);
    }
    else
    {
        LOG_W("Process terminated, unable to determine Exit Code");
    }

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}

However in this case I need to capture various debug events of process, and hook the process via Image Execution Debugger registry key (as I don't have control of its launch), including for child processes. While the inject code works fine with previous approach, when launched as a debugger I'm trying to work out how to create the remote thread (and have it complete) before resuming main application execution. While I can create the remote thread fine, it hangs when trying to wait for its completion when using the approach below. I'm trying to work out what method to use to create my remote thread and wait for it to complete before resuming main application.

// Start the child process. 
    if (!CreateProcess(NULL,    // No module name (use command line)
        cmd_pos,                // Command line
        NULL,                   // Process handle not inheritable
        NULL,                   // Thread handle not inheritable
        FALSE,                  // Set handle inheritance to FALSE
        DEBUG_PROCESS,          // Debug
        NULL,                   // Use parent's environment block
        NULL,                   // Use parent's starting directory 
        &si,
        &pi)
        )
    {
        dwResult = GetLastError();
        LOG_E(L"CreateProcess Failed with Error #%i", dwResult);
        return 1;
    }

    DebugSetProcessKillOnExit(TRUE);
    DebugActiveProcess(pi.dwProcessId);
    DEBUG_EVENT DebugEv = { 0 };
    DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation 


    LOG_I(L"Debug Process created with PID '%i'", pi.dwProcessId);
    LOG_I("Loading Target DLL");
    /*
    // load DLL in this process first so we can calculate function offset
    

    */
    for (;;)
    {
        // Wait for a debugging event to occur. The second parameter indicates
        // that the function does not return until a debugging event occurs. 

        WaitForDebugEvent(&DebugEv, INFINITE);

        // Process the debugging event code. 

        switch (DebugEv.dwDebugEventCode)
        {
        case EXCEPTION_DEBUG_EVENT:
            // Process the exception code. When handling 
            // exceptions, remember to set the continuation 
            // status parameter (dwContinueStatus). This value 
            // is used by the ContinueDebugEvent function. 
            OutputDebugString(L"EXCEPTION\r\n");
            switch (DebugEv.u.Exception.ExceptionRecord.ExceptionCode)
            {
            case EXCEPTION_ACCESS_VIOLATION:
                // First chance: Pass this on to the system. 
                // Last chance: Display an appropriate error. 
                break;

            case EXCEPTION_BREAKPOINT:
                // First chance: Display the current 
                // instruction and register values. 
                break;

            case EXCEPTION_DATATYPE_MISALIGNMENT:
                // First chance: Pass this on to the system. 
                // Last chance: Display an appropriate error. 
                break;

            case EXCEPTION_SINGLE_STEP:
                // First chance: Update the display of the 
                // current instruction and register values. 
                break;

            case DBG_CONTROL_C:
                // First chance: Pass this on to the system. 
                // Last chance: Display an appropriate error. 
                break;

            default:
                // Handle other exceptions. 
                break;
            }

            break;

        case CREATE_THREAD_DEBUG_EVENT:
            OutputDebugString(L"CREATETHREAD\r\n");
        //  dwContinueStatus = OnCreateThreadDebugEvent(&DebugEv);
            break;

        case CREATE_PROCESS_DEBUG_EVENT:
            dwContinueStatus = OnCreateProcessDebugEvent(&DebugEv);
            break;

        case EXIT_THREAD_DEBUG_EVENT:
            // Display the thread's exit code. 
            OutputDebugString(L"EXITTHREAD\r\n");
        //  dwContinueStatus = OnExitThreadDebugEvent(&DebugEv);
            break;

        case EXIT_PROCESS_DEBUG_EVENT:
            // Display the process's exit code. 
            OutputDebugString(L"EXITPROCESS\r\n");
//          dwContinueStatus = OnExitProcessDebugEvent(&DebugEv);
            break;

        case LOAD_DLL_DEBUG_EVENT:
            // Read the debugging information included in the newly 
            // loaded DLL. Be sure to close the handle to the loaded DLL 
            // with CloseHandle.
            OutputDebugString(L"LOADDLL\r\n");
    //      dwContinueStatus = OnLoadDllDebugEvent(&DebugEv);
            break;

        case UNLOAD_DLL_DEBUG_EVENT:
            // Display a message that the DLL has been unloaded. 
            OutputDebugString(L"UNLOADDLL\r\n");
        //  dwContinueStatus = OnUnloadDllDebugEvent(&DebugEv);
            break;

        case OUTPUT_DEBUG_STRING_EVENT:
            OutputDebugString(L"OUTPUTDEBUG\r\n");
            // Display the output debugging string. 

//          dwContinueStatus = OnOutputDebugStringEvent(&DebugEv);
            break;

        case RIP_EVENT:
            OutputDebugString(L"RIP\r\n");
    //      dwContinueStatus = OnRipEvent(&DebugEv);
            break;
        }

        // Resume executing the thread that reported the debugging event. 
        OutputDebugString(L"CONTINUE\r\n");
        ContinueDebugEvent(DebugEv.dwProcessId,
            DebugEv.dwThreadId,
            dwContinueStatus);
    }

DWORD OnCreateProcessDebugEvent(const LPDEBUG_EVENT DebugEv)
{
    DWORD dwResult;
    HMODULE hModuleTargetDll = LoadLibraryW(TargetDllFilename);
    __int64 iTargetProcAddress = 0;
    __int64 iTargetOffset = 0;
    if (hModuleTargetDll != NULL)
    {
        iTargetProcAddress = (__int64)GetProcAddress(hModuleTargetDll, TargetFunctionName);
        iTargetOffset = iTargetProcAddress - (__int64)hModuleTargetDll;
        LOG_I("Function Target Offset = %i", iTargetOffset);
    }
    HMODULE hModuleKernel32 = GetModuleHandle(L"kernel32.dll");
    LPVOID pLoadLibraryAddress = NULL;

    if (hModuleKernel32 != NULL)
    {
        pLoadLibraryAddress = (LPVOID)GetProcAddress(hModuleKernel32, "LoadLibraryW");
    }
    else
    {
        LOG_E("Unable to get module handle for kernel32.dll");
    }

    if (pLoadLibraryAddress == NULL) {
        dwResult = GetLastError();
        LOG_E(L"ERROR: Unable to find LoadLibraryW in Kernel32.dll Error: %i", dwResult);
    }

    // allocate space for LoadLibrary arguments in target process
    size_t iTargetDllSize = (wcslen(TargetDllFilename) + 1) * sizeof(wchar_t);

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, DebugEv->dwProcessId);
    LPVOID pLoadLibraryArguments = (LPVOID)VirtualAllocEx(
        hProcess,
        NULL,
        iTargetDllSize,
        MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);


    if (pLoadLibraryArguments == NULL) {
        dwResult = GetLastError();
        LOG_E(L"ERROR: Unable to allocate %i bytes in target process Error: %i",
            iTargetDllSize,
            dwResult);
    }
    else
    {
        if (!WriteProcessMemory(
            hProcess,
            pLoadLibraryArguments,
            TargetDllFilename,
            iTargetDllSize,
            NULL))
        {
            dwResult = GetLastError();
            LOG_E("Unable to write bytes into target process address space. Error %i", dwResult);
        }
        else
        {
            LOG_I("LoadLibrary Arguments Successfully written to target process address space.");
            HANDLE hThread = NULL;

            if (pLoadLibraryAddress != NULL)
            {
                hThread = CreateRemoteThread(
                    hProcess,
                    NULL,
                    0,
                    (LPTHREAD_START_ROUTINE)pLoadLibraryAddress,
                    pLoadLibraryArguments,
                    NULL,
                    NULL);
            }

            if (hThread == NULL) {
                dwResult = GetLastError();
                LOG_E("The remote thread calling LoadLibrary could not be created. Error %i", dwResult);
            }
            else {
                LOG_I("Remote Thread for LoadLibrary successfully created.");
                ResumeThread(hThread);
                dwResult = WaitForSingleObject(hThread, dwTimeOut);
                if (dwResult == WAIT_FAILED)
                {
                    dwResult = GetLastError();
                    LOG_I("Remote Thread for LoadLibrary Failed Error %i", dwResult);
                }

                if (dwResult == WAIT_TIMEOUT)
                {
                    LOG_E("Remote Thread for LoadLibrary in hung state");
                }

                HMODULE hInjected = GetRemoteModuleHandle(hProcess, TargetDllName);
                PHookInit pHookInit = NULL;
                if (hInjected == NULL)
                {
                    LOG_E("Unable to get module handle in target process");
                }
                else
                {
                    pHookInit = (PHookInit)((__int64)hInjected + iTargetOffset);
                }

                if (pHookInit != NULL)
                {
                    LOG_I("Running HookInit function!");
                    hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pHookInit, NULL, NULL, NULL);
                    if (hThread == NULL)
                    {
                        dwResult = GetLastError();
                        LOG_E("The remote thread calling HookInit could not be created. Error %i", dwResult);
                    }
                    else
                    {
                        LOG_I("HookInit function started!");
                        dwResult = WaitForSingleObject(hThread, dwTimeOut);
                        if (dwResult == WAIT_FAILED)
                        {
                            dwResult = GetLastError();
                            LOG_I("Remote Thread for HookInit Failed Error %i", dwResult);
                        }

                        if (dwResult == WAIT_TIMEOUT)
                        {
                            LOG_E("Remote Thread for HookInit in hung state");
                        }
                    }
                }
            }
        }
    }

    return DBG_CONTINUE;
}
0 Answers
Related