Sometimes we are working from which we would like to extract data on the fly and use it in our own program. However, the developer doesn’t provide an API, either deliberately, to block the use of his application, or just by lack of time.

For examples, video games which (generally) do not offer an API allowing you to see the enemy’s hit points, or even to recover the entire map.

Other times it will be a more professional application, displaying data, but not allowing it to be simply retrieved for processing. We can also try to automate the operation of this kind of software by creating a bot.

Today we will see the basics for creating this kind of API through DLL injection.

C/CPP DLL injection

The easiest solution to injecting code into a native Windows process is through DLL injection.

To do this, just follow these steps:

  • Create a DLL running the code of your choice in the DllMain entry point
  • Allocate memory in the target process space to store the DLL location
  • Create a thread in the target process that will run LoadLibraryA from the Windows API
  • Once this is done, the code located in the DLL’s DllMain will be executed in the address space of the target process. This means that it will be possible to simply read the memory of the target process, but also to call functions of this process.

Here is the commented code of a small DLL injector:


#include 
#include 
 
int main( int argc, char **argv) {
    
    // We get the path of the DLL to inject from the command line
    string DLLPath;
        
    if (argc > 1) {
        DLLPath = string(argv[1]);
    }
    else {   
        cerr << "Error: DLL path not defined" << endl; return EXIT_FAILURE; 
    } 

    // The second argument of the line is the ID of the process that will be injected 
    // If it is not present, we exit
    string ProcessIdStr; 
    if (argc > 2) {
        ProcessIdStr = argv[2];
    }  
    else {       
        cerr << "Error: PID not defined" << endl;   
        return EXIT_FAILURE; 
    }
        
    DWORD ID = std::stoi(ProcessIdStr);
    
    // Then we open it with the appropriate permissions
    HANDLE Process = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, ID);
        
    // We get the address of the LoadLibrary instruction in the target process 
    LPVOID LoadLibrary = (LPVOID) GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
    
    // We allocate a memory space in this process to store the name of the DLL that it will load
    LPVOID Memory = (LPVOID) VirtualAllocEx(Process, NULL, DLLPath.length() + 1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    
        
    // We write in this memory space the name of the DLL to inject
    WriteProcessMemory(Process, (LPVOID) Memory, DLLPath.c_str(), DLLPath.length() + 1, NULL);
        
    // And finally we create a thread running in the context of the target process 
        
    // This thread will only execute a LoadLibrary instruction with the path of the DLL to inject as argument.
        
    HANDLE RemoteThread = CreateRemoteThread(Process, NULL, NULL, (LPTHREAD_START_ROUTINE) LoadLibrary, (LPVOID) Memory, NULL, NULL);
        
    if(RemoteThread == NULL) {
        cerr << "Error: Failed to create remote thread (" << GetLastError() << ")" << endl;
    }
    else {
        cerr << "Info: Remote thread successfully created" << endl;    
    }
    
    // Finally we close access to the process and free the memory that had been allocated to it
        
    CloseHandle(Process);
    VirtualFreeEx(Process, (LPVOID) Memory, 0, MEM_RELEASE);
    
    return EXIT_SUCCESS;
}

This code takes as parameter the path of the DLL to inject, and the PID of the target process. Once launched the DllMain function, the injected DLL will be executed in the context of the target process, as if it were a thread of that process.
This means that he will be able to have access to all the variables of this process, but also to perform functions thereof.

In a future article, we’ll see how to inject code without going through DLL injection, then we’ll also see how to use DLL injection to manually load a C# assembly in a Mono Runtime environment.

Have a good day!