Calling a function in an injected DLL?
Asked Answered
S

2

6

Using C++, I have an application which creates a remote process and injects a DLL into it. Is there a way to get the remote application to execute a function exported from the DLL, from the application which created it? And is it possible to send parameters to that function? Please note that I am trying to stay away from doing anything within DllMain.

Salters answered 17/11, 2012 at 8:15 Comment(9)
Is not it is what CreateRemoteThread for?Sweaty
I believe you can put any code with VirtualAllocEx/WriteProcessMemory to the destination address space and then execute it with CreateRemoteThread, I'm sure someone will add a complete answer, anyway.Sweaty
That is the likely solution, but how would I retrieve the address of a particular export from the remote process?Salters
You can rely that kernel32 and user32 have the same base addreess in all processes - then, when your injected code calls LoadLibrary it will receive the base address of your DLL and can use it to calculate the offset, I believe. codeguru.com/cpp/w-p/system/processesmodules/article.php/c5767/… There are a lot articles in the net, you should google for the complete solution because generating/copying instructions ain't easy, I don't think it's worth it to wasting your time generating the asm code :-).Sweaty
To elaborate, ProcessA creates ProcessB with DllA. DllA contains one export named Initialize. What I need to do from here is use ProcessA to call Initialize as if ProcessB were the calling process, but how do I get the address of Initialize from ProcessA in order to call it?Salters
codeproject.com/Tips/139349/…Sweaty
Okay so, as it turns out... since the process is created in a suspended state, AFAIK no modules are loaded in the remote process. Therefore, module enumeration fails. I need to find a way to allow the target process to ONLY load kernel32/user32 before these methods will work.Salters
why don't just inject with VirtualAllocEx + WriteProcessMemory the code that calls in remote thread calls LoadLibrary + GetProcAddress + and then calls the respective function? I'm sure there must be examples in the internet - it's about 4 function calls in total to be injected.Sweaty
I think I've found what you are talking about, and it's simply ingenious: codeguru.com/cpp/w-p/dll/article.php/c3651/… If this works, I'll be back to answer this question.Salters
S
3

So after some elaborate testing, it would seem that my previous answer is anything but foolproof
(or even 100% functional, for that matter), and is prone to crashes. After giving it some thought, I've decided to take an entirely different approach to this... using Interprocess Communication.

Be aware... this method utilizes code in DllMain.
So don't go overboard, and be sure to follow safe practices when doing this, so that you don't end up in a deadlock...

Most notably, the Win32 API offers the following useful functions:

With the use of these, we can simply tell our Launcher process exactly where our remote init function resides, straight from the injected dll itself...


dllmain.cpp:

// Data struct to be shared between processes
struct TSharedData
{
    DWORD dwOffset = 0;
    HMODULE hModule = nullptr;
    LPDWORD lpInit = nullptr;
};
// Name of the exported function you wish to call from the Launcher process
#define DLL_REMOTEINIT_FUNCNAME "RemoteInit"
// Size (in bytes) of data to be shared
#define SHMEMSIZE sizeof(TSharedData)
// Name of the shared file map (NOTE: Global namespaces must have the SeCreateGlobalPrivilege privilege)
#define SHMEMNAME "Global\\InjectedDllName_SHMEM"
static HANDLE hMapFile;
static LPVOID lpMemFile;

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

    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(hModule);

            // Get a handle to our file map
            hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, SHMEMSIZE, SHMEMNAME);
            if (hMapFile == nullptr) {
                MessageBoxA(nullptr, "Failed to create file mapping!", "DLL_PROCESS_ATTACH", MB_OK | MB_ICONERROR);
                return FALSE;
            }

            // Get our shared memory pointer
            lpMemFile = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
            if (lpMemFile == nullptr) {
                MessageBoxA(nullptr, "Failed to map shared memory!", "DLL_PROCESS_ATTACH", MB_OK | MB_ICONERROR);
                return FALSE;
            }

            // Set shared memory to hold what our remote process needs
            memset(lpMemFile, 0, SHMEMSIZE);
            data.hModule = hModule;
            data.lpInit = LPDWORD(GetProcAddress(hModule, DLL_REMOTEINIT_FUNCNAME));
            data.dwOffset = DWORD(data.lpInit) - DWORD(data.hModule);
            memcpy(lpMemFile, &data, sizeof(TSharedData));
        case DLL_THREAD_ATTACH:
            break;
        case DLL_THREAD_DETACH:
            break;
        case DLL_PROCESS_DETACH:
            // Tie up any loose ends
            UnmapViewOfFile(lpMemFile);
            CloseHandle(hMapFile);
            break;
    }
    return TRUE;
    UNREFERENCED_PARAMETER(lpReserved);
}


Then, from our Launcher application, we will do the usual CreateProcess + VirtualAllocEx + CreateRemoteThread trick to inject our Dll, making sure to pass in a pointer to a proper SECURITY_DESCRIPTOR as the 3rd parameter to CreateProcess, as well as passing the CREATE_SUSPENDED flag in the 6th parameter.

This is to help ensure that your child process will have the proper privileges to read and write to a global shared memory namespace, though there are also other ways to achieve this (or you could test without the global path altogether).

The CREATE_SUSPENDED flag will ensure that the dllmain entry point function would have finished writing to our shared memory before other libraries are loaded, which allows easier local hooking later on...


Injector.cpp:

SECURITY_ATTRIBUTES SecAttr, *pSec = nullptr;
SECURITY_DESCRIPTOR SecDesc;

if (InitializeSecurityDescriptor(&SecDesc, SECURITY_DESCRIPTOR_REVISION) &&
    SetSecurityDescriptorDacl(&SecDesc, TRUE, PACL(nullptr), FALSE))
{
    SecAttr.nLength = sizeof(SecAttr);
    SecAttr.lpSecurityDescriptor = &SecDesc;
    SecAttr.bInheritHandle = TRUE;
    pSec = &SecAttr;
}

CreateProcessA(szTargetExe, nullptr, pSec, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi);


After injecting the DLL into the target process, all you need to do is use the same (more or less) file mapping code from your DLL project into your Launcher project (except for the part where you set the shared memory's contents, of course).

Then, calling your remote function is just a simple matter of:

// Copy from shared memory
TSharedData data;
memcpy(&data, lpMemFile, SHMEMSIZE);
// Clean up
UnmapViewOfFile(lpMemFile);
CloseHandle(hMapFile);
// Call the remote function
DWORD dwThreadId = 0;
auto hThread = CreateRemoteThread(hProcess, nullptr, 0, LPTHREAD_START_ROUTINE(data.lpInit), nullptr, 0, &dwThreadId);

Then you can ResumeThread on the target process's main thread, or from your remote function.


As an added bonus... Using this form of communication can also open up several doors for our Launcher process, as it can now directly communicate with the target process.
But again, be sure that you don't do too much in DllMain and, if at all possible, simply use your remote init function (where it is also safe to use named mutexes, for example) to create a separate shared memory map and continue communication from there.

Hope this helps someone! =)

Salters answered 27/4, 2015 at 13:26 Comment(0)
S
7

Note:
For a much better answer, please see my update posted below!


Okay so here's how I was able to accomplish this:

BOOL RemoteLibraryFunction( HANDLE hProcess, LPCSTR lpModuleName, LPCSTR lpProcName, LPVOID lpParameters, SIZE_T dwParamSize, PVOID *ppReturn )
{
    LPVOID lpRemoteParams = NULL;

    LPVOID lpFunctionAddress = GetProcAddress(GetModuleHandleA(lpModuleName), lpProcName);
    if( !lpFunctionAddress ) lpFunctionAddress = GetProcAddress(LoadLibraryA(lpModuleName), lpProcName);
    if( !lpFunctionAddress ) goto ErrorHandler;

    if( lpParameters )
    {
        lpRemoteParams = VirtualAllocEx( hProcess, NULL, dwParamSize, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );
        if( !lpRemoteParams ) goto ErrorHandler;

        SIZE_T dwBytesWritten = 0;
        BOOL result = WriteProcessMemory( hProcess, lpRemoteParams, lpParameters, dwParamSize, &dwBytesWritten);
        if( !result || dwBytesWritten < 1 ) goto ErrorHandler;
    }

    HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpFunctionAddress, lpRemoteParams, NULL, NULL );
    if( !hThread ) goto ErrorHandler;

    DWORD dwOut = 0;
    while(GetExitCodeThread(hThread, &dwOut)) {
        if(dwOut != STILL_ACTIVE) {
            *ppReturn = (PVOID)dwOut;
            break;
        }
    }

    return TRUE;

ErrorHandler:
    if( lpRemoteParams ) VirtualFreeEx( hProcess, lpRemoteParams, dwParamSize, MEM_RELEASE );
    return FALSE;
}

//...
CStringA targetDll = "injected.dll"

    // Inject the target library into the remote process
PVOID lpReturn = NULL;
RemoteLibraryFunction( hProcess, "kernel32.dll", "LoadLibraryA", targetDll.GetBuffer(MAX_PATH), targetDll.GetLength(), &lpReturn );
HMODULE hInjected = reinterpret_cast<HMODULE>( lpReturn );

    // Call our exported function
lpReturn = NULL;
RemoteLibraryFunction( hProcess, targetDll, "Initialize", NULL, 0, &lpReturn );
BOOL RemoteInitialize = reinterpret_cast<BOOL>( lpReturn );


This can also be used to send parameters to a remote function via a pointer to a struct or union, and gets around having to write anything in DllMain.

Salters answered 19/11, 2012 at 3:19 Comment(3)
Thanks for this; it got me started. However I found shortly afterwards that no matter what I did, using this code always crashed the target process. The issue is that the function to be called within the DLL injected to the "hooked" process doesn't reside at the same address as which it does in the current process. For that reason, the post at https://mcmap.net/q/1773220/-calling-function-in-injected-dll should be followed which describes how to avoid crashes.Nirvana
@Nirvana I've updated with a new answer, which far surpasses this one.Salters
@RectangleEquals: I know that this answer is kind of "deprecated" but I like the simplicity of it. However, I'm running into trouble with supplying parameters to a function. In particular, I want to supply a DWORD. The following code does not work and the passed value ends up being gibberish like -1733099520: lpReturn = NULL; DWORD speed_up = 1; RemoteLibraryFunction(h_target_proc_handle, targetDll, "set_speedup", &speed_up, sizeof speed_up, &lpReturn); How can it be fixed?Authenticity
S
3

So after some elaborate testing, it would seem that my previous answer is anything but foolproof
(or even 100% functional, for that matter), and is prone to crashes. After giving it some thought, I've decided to take an entirely different approach to this... using Interprocess Communication.

Be aware... this method utilizes code in DllMain.
So don't go overboard, and be sure to follow safe practices when doing this, so that you don't end up in a deadlock...

Most notably, the Win32 API offers the following useful functions:

With the use of these, we can simply tell our Launcher process exactly where our remote init function resides, straight from the injected dll itself...


dllmain.cpp:

// Data struct to be shared between processes
struct TSharedData
{
    DWORD dwOffset = 0;
    HMODULE hModule = nullptr;
    LPDWORD lpInit = nullptr;
};
// Name of the exported function you wish to call from the Launcher process
#define DLL_REMOTEINIT_FUNCNAME "RemoteInit"
// Size (in bytes) of data to be shared
#define SHMEMSIZE sizeof(TSharedData)
// Name of the shared file map (NOTE: Global namespaces must have the SeCreateGlobalPrivilege privilege)
#define SHMEMNAME "Global\\InjectedDllName_SHMEM"
static HANDLE hMapFile;
static LPVOID lpMemFile;

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

    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(hModule);

            // Get a handle to our file map
            hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, SHMEMSIZE, SHMEMNAME);
            if (hMapFile == nullptr) {
                MessageBoxA(nullptr, "Failed to create file mapping!", "DLL_PROCESS_ATTACH", MB_OK | MB_ICONERROR);
                return FALSE;
            }

            // Get our shared memory pointer
            lpMemFile = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0);
            if (lpMemFile == nullptr) {
                MessageBoxA(nullptr, "Failed to map shared memory!", "DLL_PROCESS_ATTACH", MB_OK | MB_ICONERROR);
                return FALSE;
            }

            // Set shared memory to hold what our remote process needs
            memset(lpMemFile, 0, SHMEMSIZE);
            data.hModule = hModule;
            data.lpInit = LPDWORD(GetProcAddress(hModule, DLL_REMOTEINIT_FUNCNAME));
            data.dwOffset = DWORD(data.lpInit) - DWORD(data.hModule);
            memcpy(lpMemFile, &data, sizeof(TSharedData));
        case DLL_THREAD_ATTACH:
            break;
        case DLL_THREAD_DETACH:
            break;
        case DLL_PROCESS_DETACH:
            // Tie up any loose ends
            UnmapViewOfFile(lpMemFile);
            CloseHandle(hMapFile);
            break;
    }
    return TRUE;
    UNREFERENCED_PARAMETER(lpReserved);
}


Then, from our Launcher application, we will do the usual CreateProcess + VirtualAllocEx + CreateRemoteThread trick to inject our Dll, making sure to pass in a pointer to a proper SECURITY_DESCRIPTOR as the 3rd parameter to CreateProcess, as well as passing the CREATE_SUSPENDED flag in the 6th parameter.

This is to help ensure that your child process will have the proper privileges to read and write to a global shared memory namespace, though there are also other ways to achieve this (or you could test without the global path altogether).

The CREATE_SUSPENDED flag will ensure that the dllmain entry point function would have finished writing to our shared memory before other libraries are loaded, which allows easier local hooking later on...


Injector.cpp:

SECURITY_ATTRIBUTES SecAttr, *pSec = nullptr;
SECURITY_DESCRIPTOR SecDesc;

if (InitializeSecurityDescriptor(&SecDesc, SECURITY_DESCRIPTOR_REVISION) &&
    SetSecurityDescriptorDacl(&SecDesc, TRUE, PACL(nullptr), FALSE))
{
    SecAttr.nLength = sizeof(SecAttr);
    SecAttr.lpSecurityDescriptor = &SecDesc;
    SecAttr.bInheritHandle = TRUE;
    pSec = &SecAttr;
}

CreateProcessA(szTargetExe, nullptr, pSec, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi);


After injecting the DLL into the target process, all you need to do is use the same (more or less) file mapping code from your DLL project into your Launcher project (except for the part where you set the shared memory's contents, of course).

Then, calling your remote function is just a simple matter of:

// Copy from shared memory
TSharedData data;
memcpy(&data, lpMemFile, SHMEMSIZE);
// Clean up
UnmapViewOfFile(lpMemFile);
CloseHandle(hMapFile);
// Call the remote function
DWORD dwThreadId = 0;
auto hThread = CreateRemoteThread(hProcess, nullptr, 0, LPTHREAD_START_ROUTINE(data.lpInit), nullptr, 0, &dwThreadId);

Then you can ResumeThread on the target process's main thread, or from your remote function.


As an added bonus... Using this form of communication can also open up several doors for our Launcher process, as it can now directly communicate with the target process.
But again, be sure that you don't do too much in DllMain and, if at all possible, simply use your remote init function (where it is also safe to use named mutexes, for example) to create a separate shared memory map and continue communication from there.

Hope this helps someone! =)

Salters answered 27/4, 2015 at 13:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.