Lets assume the remote thread procedure look like this:
DWORD __stdcall ThreadProc (void *pData) {
ThreadData *p = (ThreadData*)pData; // Contains function references and strings
p->MessageBoxW(NULL, p->Message, p->Title, MB_OK);
}
Then everything works fine and p->MessageBoxW(...)
shows a message box as expected. But I don't want to call GetProcAddress
for every function I use in the remote thread, so I thought I could create a function export within my module (EXE file creating the remote thread), so that the remote thread just calls LoadLibraryW
to load my EXE file as module into the target process's address space and GetProcAddress
to get the exported function's address in order to call it.
typedef void (__stdcall *_Test) ();
extern "C" void __stdcall Test () {
return;
}
DWORD __stdcall ThreadProc (void *pData) {
ThreadData *p = (ThreadData*)pData; // Contains function references and strings
HMODULE hLib = p->LoadLibraryW(p->LibPath);
_Test pTest = (_Test)p->GetProcAddress(hLib, p->ProcName);
pTest();
p->FreeLibrary(hLib);
return NULL;
}
This still works fine. But as soon as I change the exported function to
extern "C" void __stdcall Test () {
MessageBoxW(NULL, L"Message", L"Title", MB_OK);
return;
}
the target process suddenly crashes. Doesn't LoadLibrary
resolve intermodular references? Is it possible to load my module into the target process's address space so that the exported function can be coded without passing all function addresses to it?
Additional information: For everyone copying the code, I had to disable incremental linking, build as release and add a module definition file to ensure that Test
is exported as Test
and not as _Test@SoMeJuNk
. Just prepending __declspec(dllexport)
didn't work for some reason. The module definition file looks like this
EXPORTS
Test@0
The ThreadData
structure looks like this
typedef struct tagThreadData {
typedef BOOL (__stdcall *_FreeLibrary) (HMODULE);
typedef FARPROC (__stdcall *_GetProcAddress) (HMODULE, PSTR);
typedef HMODULE (__stdcall *_LoadLibraryW) (LPWSTR);
typedef DWORD (__stdcall *_MessageBoxW) (HWND, LPWSTR, LPWSTR, DWORD);
_FreeLibrary FreeLibrary;
_GetProcAddress GetProcAddress;
_LoadLibraryW LoadLibraryW;
_MessageBoxW MessageBoxW;
WCHAR LibPath[100];
WCHAR Message[30];
CHAR ProcName[10];
WCHAR Title[30];
} ThreadData, *PThreadData;
I came up with a temporary solution: Putting all remote code into an actual DLL. But putting the code into a DLL isn't my target, so if someone comes up with a clever solution, where the EXE file is the injector as well as the module being injected, I will mark the new answer as right.
Even though there are many tutorials on how to inject an actual DLL into another process's address space, I still give away my solution. I wrote my original solution only for UNICODE and 64-Bit, but I tried my best to make it work for both ASCII and UNICODE and 32-bit and 64-bit. But lets get started...
First of all, an explanation of the basic steps
Obtain handle to the target process with at least the following access rights
PROCESS_CREATE_THREAD
PROCESS_QUERY_INFORMATION
PROCESS_VM_OPERATION
PROCESS_VM_WRITE
PROCESS_VM_READ
Allocate memory for the remote thread procedure and the data and function pointers needed for loading the target dll and its "entrypoint" (I don't mean the actual entrypoint DllMain, but a function designed to be called from within the remote thread)
PVOID pThread = VirtualAllocEx(hProc, NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
Copy remote thread procedure and important data over to the target process
WriteProcessMemory(hProc, pThread, ThreadProc, ThreadProcLen, NULL);
WriteProcessMemory(hProc, pParam, &data, sizeof(ThreadData), NULL);
Create remote thread. This thread will load the target dll into the target process's address space and calls its "entrypoint"
HANDLE hThread = CreateRemoteThread(hProc, NULL, 0, (PTHREAD_START_ROUTINE)pThread, pParam, NULL, NULL);
Optional: Wait until the thread returns
WaitForSingleObject(hThread, INFINITE);
DWORD threadExitCode;
GetExitCodeThread(hThread, &threadExitCode);
Close thread handle, release memory, Close process handle
CloseHandle(hThread);
VirtualFreeEx(hProc, pThread, 4096, MEM_RELEASE);
CloseHandle(hProc);
So here's my ThreadProc
and ThreadData
structure. ThreadProc
is the remote thread procedure being called by CreateRemoteThread
and should LoadLibrary
the target dll, so it can call the target dll's "entrypoint". The ThreadData
structure contains the addresses of LoadLibrary
, GetProcAddress
and FreeLibrary
, the target dll's path TargetDll
and the name of the "entrypoint" DllEntry
.
typedef struct {
typedef BOOL (__stdcall *_FreeLibrary) (HMODULE);
typedef FARPROC (__stdcall *_GetProcAddress) (HMODULE, LPCH);
typedef HMODULE (__stdcall *_LoadLibrary) (LPTSTR);
typedef void (__stdcall *_DllEntry) ();
_LoadLibrary LoadLibrary;
TCHAR TargetDll[MAX_PATH];
_GetProcAddress GetProcAddress;
CHAR DllEntry[50]; // Some entrypoint designed to be
// called from the remote thread
_FreeLibrary FreeLibrary;
} ThreadData, *PThreadData;
// ThreadProcLen should be smaller than 3400, because ThreadData can
// take up to 644 bytes unless you change the length of TargetDll or
// DllEntry
#define ThreadProcLen (ULONG_PTR)2048
#define SPY_ERROR_OK (DWORD)0
#define SPY_ERROR_LOAD_LIB (DWORD)1
#define SPY_ERROR_GET_PROC (DWORD)2
DWORD ThreadProc (PVOID pParam) {
DWORD err = SPY_ERROR_OK;
PThreadData p = (PThreadData)pParam;
// Load dll to be injected
HMODULE hLib = p->LoadLibrary(p->TargetDll);
if (hLib == NULL)
return SPY_ERROR_LOAD_LIB;
// Obtain "entrypoint" of dll (not DllMain)
ThreadData::_DllEntry pDllEntry = (ThreadData::_DllEntry)p->GetProcAddress(hLib, p->DllEntry);
if (pDllEntry != NULL)
// Call dll's "entrypoint"
pDllEntry();
else
err = SPY_ERROR_GET_PROC;
// Free dll
p->FreeLibrary(hLib);
return err;
}
Then there's the actual code injecting the remote thread procedure into the target process's address space
int main(int argc, char* argv[]) {
// DWORD pid = atoi(argv[1]);
// Open process
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hProc != NULL) {
// Allocate memory in the target process's address space
PVOID pThread = VirtualAllocEx(hProc, NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pThread != NULL) {
PVOID pParam = (PVOID)((ULONG_PTR)pThread + ThreadProcLen);
// Initialize data to be passed to the remote thread
ThreadData data;
HMODULE hLib = LoadLibrary(TEXT("KERNEL32.DLL"));
data.LoadLibrary = (ThreadData::_LoadLibrary)GetProcAddress(hLib, "LoadLibrary");
data.GetProcAddress = (ThreadData::_GetProcAddress)GetProcAddress(hLib, "GetProcAddress");
data.FreeLibrary = (ThreadData::_FreeLibrary)GetProcAddress(hLib, "FreeLibrary");
FreeLibrary(hLib);
_tcscpy_s(data.TargetDll, TEXT("...")); // Insert path of target dll
strcpy_s(data.DllEntry, "NameOfTheDllEntry"); // Insert name of dll's "entrypoint"
// Write procedure and data into the target process's address space
WriteProcessMemory(hProc, pThread, ThreadProc, ThreadProcLen, NULL);
WriteProcessMemory(hProc, pParam, &data, sizeof(ThreadData), NULL);
// Create remote thread (ThreadProc)
HANDLE hThread = CreateRemoteThread(hProc, NULL, 0, (PTHREAD_START_ROUTINE)pThread, pParam, NULL, NULL);
if (hThread != NULL) {
// Wait until remote thread has finished
if (WaitForSingleObject(hThread, INFINITE) == WAIT_OBJECT_0) {
DWORD threadExitCode;
// Evaluate exit code
if (GetExitCodeThread(hThread, &threadExitCode) != FALSE) {
// Evaluate exit code
} else {
// The thread's exit code couldn't be obtained
}
} else {
// Thread didn't finish for some unknown reason
}
// Close thread handle
CloseHandle(hThread);
}
// Deallocate memory
VirtualFreeEx(hProc, pThread, 4096, MEM_RELEASE);
} else {
// Couldn't allocate memory in the target process's address space
}
// Close process handle
CloseHandle(hProc);
}
return 0;
}
The dll being injected has a real entrypoint DllMain
that is called, when LoadLibrary
loads the target dll into the target process's address space, and another "entrypoint" NameOfTheDllEntry
called by the remote thread procedure (if it can be located in the first place)
// Module.def:
// LIBRARY NameOfDllWithoutExtension
// EXPORTS
// NameOfTheDllEntry
__declspec(dllexport) void __stdcall NameOfTheDllEntry () {
// Because the library is actually loaded in the target process's address
// space, there's no need for obtaining pointers to every function.
// I didn't try libraries other than kernel32.dll and user32.dll, but they
// should be working as well as long as the dll itself references them
// Do stuff
return;
}
BOOL APIENTRY DllMain (HMODULE hLib, DWORD reason, PVOID) {
if (reason == DLL_PROCESS_ATTACH)
DisableThreadLibraryCalls(hLib); // Optional
return TRUE;
}