CreateRemoteThread is almost identical to CreateThread, and it is not surprising that CreateRemoteThread requires the thread function it will execute to be in the process the thread gets created in. While you could inject some assembly to load the DLL using VirtualAllocEx, there is an easier way, in this case. It just so happens that the prototype of the thread function CreateRemoteThread will execute exactly matches that of LoadLibrary (either the ASCII or Unicode version will do, so long as you use the appropriate string). The new thread will thus call LoadLibrary, loading the DLL and executing DllMain, then set the return value of LoadLibrary (and indirectly that of DllMain) as the thread exit code, which your program can retrieve with GetExitCodeThread.
Of course, this only loads the DLL and executes DllMain, which, as previously mentioned, does not permit a great deal of activity. This is where creating an initialization thread from DllMain comes in handy, as mentioned last post. However, there's one more thing to be mentioned. From DllMain you cannot tell whether you're in the patcher process or the target process; at least not by any methods inherent to the process. One simple solution to this is to check if there's a memory mapped file corresponding to the current process. If such a file mapping exists, then the process is a target process, and initialization should be performed; otherwise, you're in the patcher process. The use of a file mapping is particularly convenient, because you can pass data to and from the target process in the very same file mapping.
Oh, and one last thing to mention: getting the address of LoadLibrary. This may be accomplished simply by using GetModuleHandle and GetProcAddress. While it's true that almost all the time you can't be sure that a DLL will be loaded in exactly the same place in two different processes, Kernel32.dll and NTDLL.dll are the exceptions to this rule. Windows has some built-in checks to ensure that Kernel32 and NTDLL will always get loaded at their preferred address, guaranteeing that their base addresses will be the same for all processes.
DWORD APIENTRY InitializationFunction(void *lpParam)
{
MessageBox(NULL, "Hello from the inside!", "InitializationFunction", MB_OK | MB_ICONEXCLAMATION);
return 0;
}
BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
g_hDLL = (HINSTANCE)hModule; // Save the HINSTANCE
// If we're a target process, execute the initialization thread
HANDLE hMapping = OpenProcessSection("InjectIntoProcessNT");
if (hMapping)
{
// Close the indicator mapping. If there was any actual data in the mapping, we would need to pass the file mapping HANDLE to the initialization function instead of closing it.
CloseHandle(hMapping);
// Create the initialization thread
HANDLE hThread = CreateThread(NULL, 0, InitializationFunction, 0, 0, NULL);
if (!hThread)
return FALSE;
// Close the thread (it'll keep on running)
CloseHandle(hThread);
}
}
return TRUE;
}
_declspec(dllexport) bool __stdcall InjectIntoProcessNT(DWORD nProcessID, DWORD nTimeoutMS)
{
// Get this DLL's path
char szDLLPath[MAX_PATH + 1];
GetModuleFileName((HMODULE)g_hDLL, szDLLPath, MAX_PATH);
// Get the address of LoadLibrary(A)
HMODULE hKernel32 = GetModuleHandle("Kernel32");
FARPROC lpfnLoadLibraryA = GetProcAddress(hKernel32, "LoadLibraryA");
// Open a HANDLE to the process. We'll need access to create the loader thread, as well as allocate memory for and write the DLL path.
HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, nProcessID);
if (!hProcess)
return false;
bool bSuccess = false, bTimedOut = false; // You know the drill
// Create the "you are a target process" file mapping
HANDLE hMapping = CreateProcessSection(1, "InjectIntoProcessNT", nProcessID);
if (hMapping)
{
// Allocate memory for the DLL path
void *lpDLLPathMemory = VirtualAllocEx(hProcess, NULL, MAX_PATH + 1, MEM_COMMIT, PAGE_READWRITE);
if (lpDLLPathMemory)
{
// Write the path
SIZE_T nBytesWritten;
WriteProcessMemory(hProcess, lpDLLPathMemory, szDLLPath, MAX_PATH + 1, &nBytesWritten);
// Create the loader thread
DWORD nThreadID;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpfnLoadLibraryA, lpDLLPathMemory, 0, &nThreadID);
// Wait for the loader thread to terminate (it will terminate when DllMain returns). If it's necessary to verify that the initialization thread has successfully completed, an alternate waiting method, such as the one we used for windows hooks, will be necessary, here.
bTimedOut = (WaitForSingleObject(hThread, nTimeoutMS) != WAIT_OBJECT_0);
if (!bTimedOut)
{
// Get the thread's return value to check if DllMain executed successfully
DWORD nExitCode;
GetExitCodeThread(hThread, &nExitCode);
if (nExitCode != NULL)
bSuccess = true; // DllMain executed successfully
}
// Close the thread. It will continue to run if it hasn't terminated.
CloseHandle(hThread);
// Free the memory for the DLL path
if (!bTimedOut)
VirtualFreeEx(hProcess, lpDLLPathMemory, 0, MEM_RELEASE);
}
// Close the file mapping
if (!bTimedOut)
CloseHandle(hMapping);
}
// Close the target process
CloseHandle(hProcess);
return bSuccess;
}
{
MessageBox(NULL, "Hello from the inside!", "InitializationFunction", MB_OK | MB_ICONEXCLAMATION);
return 0;
}
BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)
{
g_hDLL = (HINSTANCE)hModule; // Save the HINSTANCE
// If we're a target process, execute the initialization thread
HANDLE hMapping = OpenProcessSection("InjectIntoProcessNT");
if (hMapping)
{
// Close the indicator mapping. If there was any actual data in the mapping, we would need to pass the file mapping HANDLE to the initialization function instead of closing it.
CloseHandle(hMapping);
// Create the initialization thread
HANDLE hThread = CreateThread(NULL, 0, InitializationFunction, 0, 0, NULL);
if (!hThread)
return FALSE;
// Close the thread (it'll keep on running)
CloseHandle(hThread);
}
}
return TRUE;
}
_declspec(dllexport) bool __stdcall InjectIntoProcessNT(DWORD nProcessID, DWORD nTimeoutMS)
{
// Get this DLL's path
char szDLLPath[MAX_PATH + 1];
GetModuleFileName((HMODULE)g_hDLL, szDLLPath, MAX_PATH);
// Get the address of LoadLibrary(A)
HMODULE hKernel32 = GetModuleHandle("Kernel32");
FARPROC lpfnLoadLibraryA = GetProcAddress(hKernel32, "LoadLibraryA");
// Open a HANDLE to the process. We'll need access to create the loader thread, as well as allocate memory for and write the DLL path.
HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, nProcessID);
if (!hProcess)
return false;
bool bSuccess = false, bTimedOut = false; // You know the drill
// Create the "you are a target process" file mapping
HANDLE hMapping = CreateProcessSection(1, "InjectIntoProcessNT", nProcessID);
if (hMapping)
{
// Allocate memory for the DLL path
void *lpDLLPathMemory = VirtualAllocEx(hProcess, NULL, MAX_PATH + 1, MEM_COMMIT, PAGE_READWRITE);
if (lpDLLPathMemory)
{
// Write the path
SIZE_T nBytesWritten;
WriteProcessMemory(hProcess, lpDLLPathMemory, szDLLPath, MAX_PATH + 1, &nBytesWritten);
// Create the loader thread
DWORD nThreadID;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpfnLoadLibraryA, lpDLLPathMemory, 0, &nThreadID);
// Wait for the loader thread to terminate (it will terminate when DllMain returns). If it's necessary to verify that the initialization thread has successfully completed, an alternate waiting method, such as the one we used for windows hooks, will be necessary, here.
bTimedOut = (WaitForSingleObject(hThread, nTimeoutMS) != WAIT_OBJECT_0);
if (!bTimedOut)
{
// Get the thread's return value to check if DllMain executed successfully
DWORD nExitCode;
GetExitCodeThread(hThread, &nExitCode);
if (nExitCode != NULL)
bSuccess = true; // DllMain executed successfully
}
// Close the thread. It will continue to run if it hasn't terminated.
CloseHandle(hThread);
// Free the memory for the DLL path
if (!bTimedOut)
VirtualFreeEx(hProcess, lpDLLPathMemory, 0, MEM_RELEASE);
}
// Close the file mapping
if (!bTimedOut)
CloseHandle(hMapping);
}
// Close the target process
CloseHandle(hProcess);
return bSuccess;
}
Update:
Note that despite the theoretical possibility of creating a thread in a process during startup (while the main thread is suspended with CREATE_SUSPENDED) so that the patcher can execute synchronously, this does not work in practice, because CSRSS (the Win32 subsystem process) freaks out if the first thread to execute isn't the first thread to be created, and kills the process.
No comments:
Post a Comment