Problem with small DLL-Injection Project in C

83 views Asked by At

I'm trying to get my DLL-Injection Program in C to work. I have no clue why it doesn't work and am now seeking help from the community ;)

To summarize: The purpose of my program is to Inject and execute the User32!MessageBoxA function within the target program notepad.exe using a remotely called LoadLibraryA which gets executed after the loader sucessfully loaded Kernel32.dll.

For now I just want to inject USER32.dll. My comment on line no. 76 describes my Issue in much more detail:

/** Issue at line no. 76:
 *  NOW: The previous steps all went as expected.
 *  BUT NOW HERE'S WHERE IS THE PROBLEM.
 *  PROBLEM: Ideally, on the next Iteration the remote Thread should call LoadLibraryA with USER32.dll causing the USER32.dll to be injected into the target.
 *           But by using the gcc debugger within VSCode I found that directly after breaking the current switch case and entering the
 *           next Iteration, the LOAD_DLL_DEBUG_EVENT will indeed be triggered BUT not with USER32.dll.
 *          
 *           This lets me to believe that the LoadLibraryA call from the remote thread must not be executing at all.
 * 
 *           Please let me know how I can get this to be working as expected.
 *           Any help will be strongly appreciated!
 */
                 

This is the full code including my annotations:

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <psapi.h>

int main() {
    // Get the system directory for later use
    char* sysdir = calloc(MAX_PATH, sizeof(char));
    size_t sysdir_length = strlen(sysdir);

    sysdir_length = GetSystemDirectory(sysdir, MAX_PATH);

    if(sysdir_length == 0) {
        printf("Couldn't get system directory!\n");
        return EXIT_FAILURE;
    }

    // This specifies the absolute path to the target program that the DLL will be injected into
    const char* targetProcessPath = "C:\\Windows\\System32\\notepad.exe";

    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    memset(&si, 0, sizeof(STARTUPINFO));
    memset(&pi, 0, sizeof(PROCESS_INFORMATION));
    
    // Create the target Program with Debug Flag set
    if (!CreateProcess(targetProcessPath, NULL, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi)) {
        printf("Failed to create the process: %d\n", GetLastError());
        return 1;
    }

    printf("Debugging process %s (PID: %d)\n", targetProcessPath, pi.dwProcessId);
    printf("Now Printing information from the loader...\n");


    // Create pointer to store the address of the target's LoadLibraryA function
    FARPROC remote_LoadLibraryA;

    // Set up the debug Event Handler    
    DEBUG_EVENT debugEvent;
    HANDLE remoteThread; 
    char* modulename = calloc(MAX_PATH, sizeof(char));


    /** Debug loop.
     *  1. Logs information everytime a DLL is being loaded within the target (including the loader)
     *  2. Resolves the address for KERNEL32!LoadLibraryA within the target and stores it in remote_LoadLibraryA
     *  3. Creates a remote thread to execute LoadLibraryA() once Kernel32 has been loaded by the loader
     *  4. Receives the Event for the remotely created Thread and continues execution
     *  5. PROBLEM: Does not Load the specified USER32.dll
     */
    while (1) {
        if (!WaitForDebugEvent(&debugEvent, INFINITE)) {
            printf("WaitForDebugEvent failed: %d\n", GetLastError());
            break;
        }

        switch (debugEvent.dwDebugEventCode) {    
            // This will be triggered every time the target program tries to create a thread
            // Here we'll also receive our remotely created thread
            case CREATE_THREAD_DEBUG_EVENT:
                // Quick error Handling
                if(debugEvent.u.CreateThread.hThread == NULL) break; 


                // This will check if the thread created is our external LoadLibrary call
                if(GetThreadId(debugEvent.u.CreateThread.hThread) != GetThreadId(remoteThread)) break;  // break if that's not the case
                
                printf("Received the remote Thread creation for our LoadLibraryA function call!\n");
                printf("Expecting the next Module to be User32.dll\n");
                ContinueDebugEvent(debugEvent.dwProcessId, GetThreadId(remoteThread), DBG_CONTINUE);
                /**
                 *  NOW: The previous steps all went as expected.
                 *  BUT NOW HERE'S WHERE IS THE PROBLEM.
                 *  PROBLEM: Ideally, on the next Iteration the remote Thread should call LoadLibraryA with USER32.dll causing the USER32.dll to be injected into the target.
                 *           But by using the gcc debugger within VSCode I found that directly after breaking the current switch case and entering the
                 *           next Iteration, the LOAD_DLL_DEBUG_EVENT will indeed be triggered BUT not with USER32.dll.
                 *          
                 *           This lets me to believe that the LoadLibraryA call from the remote thread must not be executing at all.
                 * 
                 *           Please let me know how I can get this to be working as expected.
                 *           Any help will be strongly appreciated!
                 */
                break;

            
            case EXCEPTION_DEBUG_EVENT:
                // This will prevent our target from executing after the loader has finished it's job
                if(debugEvent.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) {
                    printf("Breakpoint hit at address 0x%p\n", debugEvent.u.Exception.ExceptionRecord.ExceptionAddress);
                    printf("Press Enter to continue...\n");
                    getchar(); // Wait for user input
                }
                break;

            // Triggers every time the target or the loader executes a LoadLibrary variant
            case LOAD_DLL_DEBUG_EVENT: 
                // Whatever the DLL is that the target wants to load we always want to get the Module Handle first...
                HMODULE hModule = (HMODULE)debugEvent.u.LoadDll.lpBaseOfDll;                
                if(hModule == NULL) {
                    printf("Error: A Null-module has been logged!\n");
                    break;  
                }
                // and print out the module name!
                if(GetModuleFileName(hModule, modulename, MAX_PATH) <= 0) {
                    printf("Error: Module name could not be resolved!\n");
                    break;
                }
                printf("Module %s loaded.\n", modulename);
                

                // Now we can check if the module's the one from which we want to execute LoadLibrary from
                if(_stricmp(modulename+sysdir_length, "\\kernel32.dll") != 0) break;    // break if that's not the case
                
                // Otherwise let the User know we found what we we're searching for!
                printf("Kernel32.dll found! We can now execute a LoadLibraryA externally to inject our DLL!\n");

                // Then get the address of the function we're interested in calling
                remote_LoadLibraryA = GetProcAddress(hModule, "LoadLibraryA");

                // Quick error handling
                if(remote_LoadLibraryA == NULL) {
                    printf("Could not resolve the Address for LoadLibraryA! Exit now...\n");
                    return EXIT_FAILURE;
                }
                // Also print the external address for debugging purposes
                printf("LoadLibraryA located at %p\n", remote_LoadLibraryA);
                
                
                // Create a String containing the absolute path to the DLL we want to inject
                const char* user32_str = "user32.dll";
                char* local_param = calloc(sysdir_length+strlen(user32_str), sizeof(char));
                memcpy(local_param, sysdir, sysdir_length);
                strcat(local_param, user32_str);
                printf("Now trying to inject %s...\n", local_param);
                
                // Copy String to targets memory
                LPVOID ext_param = VirtualAllocEx(pi.hProcess, NULL, strlen(local_param), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
                if(ext_param == NULL) {
                    printf("Could not allocate virtual memory on target! Exit now...\n");
                    return EXIT_FAILURE;
                }
                size_t bw = 0;
                if(!WriteProcessMemory(pi.hProcess, ext_param, local_param, strlen(local_param), &bw) || bw != strlen(local_param)) {
                    printf("Could not write to the targets memory! Exit now...\n");
                    return EXIT_FAILURE;
                }

                // This is the remote Thread that will use LoadLibraryA to inject our DLL
                remoteThread = CreateRemoteThread(\
                                                 pi.hProcess,  // Thread will be created inside of our target. If it was successfull we'll catch the creation within the next iteration
                                                        NULL,  // I'm not quite sure about the Security Attributes needed here
                                                           0,  // No specific stack reservation needed
                (LPTHREAD_START_ROUTINE) remote_LoadLibraryA,  
                                                   ext_param, // Pass our remotely allocated String as a Parameter to our external LoadLibrary function
                                               DEBUG_PROCESS, // Make sure to set the debug flag to capture the Event (LoadLibrary) within the next iteration of our debugging loop
                                                        NULL\
                );

                // Quick Error handling
                if (remoteThread == NULL) {
                    printf("Remote thread creation failed\n");
                    return EXIT_FAILURE; 
                }

                 
                //  Notice: After breaking the switch, the next Iteration will cause the CREATE_THREAD_DEBUG_EVENT to occur
                break;

            case EXIT_PROCESS_DEBUG_EVENT:
                printf("Process has exited.\n");
                CloseHandle(pi.hProcess);
                CloseHandle(pi.hThread);
                return 0;
        }

        ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
    }
      
    free(modulename);
    free(sysdir);
    return 0;
}
0

There are 0 answers