I'm trying to learn how to inject a DLL into a process. To do so, I've set up a new project with:
HelloWorld.exe
which displays a message box.hook_msg.dll
which implements the hook using (correctly?) minhookinjector.exe
which is supposed to inject hook_msg.dll into HelloWorld.exe.When I run injector.exe
, I only get the HelloWorld
message box and nothing from the injected DLL (I expect another message box).
To give you more context, walking through the process with x64dbg showed that the last error was ERROR_MOD_NOT_FOUND.
Below is more information on my approach trying to solve the problem.
And here is the code and my configuration to compile it:
Injector:
#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <stdio.h>
STARTUPINFO startupInfo;
PROCESS_INFORMATION processInformation;
BOOL InjectDLL(HANDLE hProcess, LPCTSTR dllPath)
{
// I read on some other stack question that size matter so i used +1 for the null terminator
SIZE_T dllPathSize = (_tcslen(dllPath) + 1) * sizeof(TCHAR);
// Allocating memory in the remote process
LPVOID pRemoteDllPath = VirtualAllocEx(hProcess, NULL, dllPathSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pRemoteDllPath == NULL)
{
return FALSE;
}
else
{
//Put some info so i can see the memory address and confirm in x64dbg
_tprintf(TEXT("Memory allocated at: %p\n"), pRemoteDllPath);
}
//Writing the DLL path to the remote process
if (!WriteProcessMemory(hProcess, pRemoteDllPath, (LPVOID)dllPath, dllPathSize, NULL))
{
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
return FALSE;
}
else
{
//Put some info so i can see the memory address and confirm in x64dbg
_tprintf(TEXT("DLL path written to memory at: %p\n"), pRemoteDllPath);
}
// Reading the memory back to verify
TCHAR* localBuffer = (TCHAR*)malloc(dllPathSize);
if (localBuffer == NULL)
{
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
return FALSE;
}
if (!ReadProcessMemory(hProcess, pRemoteDllPath, localBuffer, dllPathSize, NULL))
{
free(localBuffer);
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
return FALSE;
}
else
{
//Here i wanted to check if the memory was read correctly
_tprintf(TEXT("DLL path read from memory: %s\n"), localBuffer);
}
// Even displaying the raw bytes
for (SIZE_T i = 0; i < dllPathSize; i++)
{
_tprintf(TEXT("%02X "), ((BYTE*)localBuffer)[i]);
if ((i + 1) % 16 == 0) _tprintf(TEXT("\n"));
}
_tprintf(TEXT("\n"));
free(localBuffer);
//Getting the address of LoadLibraryW in kernel32.dll
LPVOID pLoadLibraryW = (LPVOID)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryW");
if (pLoadLibraryW == NULL)
{
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
return FALSE;
}
//Creating a remote thread to load the DLL
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryW, pRemoteDllPath, 0, NULL);
if (hRemoteThread == NULL)
{
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
return FALSE;
}
else
{
// Print the handle of the remote thread
_tprintf(TEXT("Handle du thread: %p\n"), hRemoteThread);
// Get the ID of the remote thread
DWORD threadId = GetThreadId(hRemoteThread);
_tprintf(TEXT("ID du thread: %lu\n"), threadId);
}
// Wait for the remote thread to complete
WaitForSingleObject(hRemoteThread, INFINITE);
// Free the allocated memory and close the thread handle
VirtualFreeEx(hProcess, pRemoteDllPath, 0, MEM_RELEASE);
CloseHandle(hRemoteThread);
return TRUE;
}
int _tmain(int argc, TCHAR *argv[])
{
TCHAR targetPath[MAX_PATH];
TCHAR hookdllpath[MAX_PATH];
TCHAR modulePath[MAX_PATH];
TCHAR *lastSlash;
// Get the path of the current executable
if (GetModuleFileName(NULL, modulePath, MAX_PATH) == 0)
{
return FALSE;
}
// Find the last backslash in the path
lastSlash = _tcsrchr(modulePath, '\\');
if (lastSlash == NULL)
{
return FALSE;
}
// Replace the executable name with "helloworld.exe"
_tcscpy(lastSlash + 1, TEXT("helloworld.exe"));
// Initialize the STARTUPINFO structure
ZeroMemory(&startupInfo, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);
// Initialize the PROCESS_INFORMATION structure
ZeroMemory(&processInformation, sizeof(processInformation));
// Starting a new process
if (!CreateProcess(modulePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startupInfo, &processInformation))
{
return FALSE;
}
// Get the path of the DLL to inject
if (GetFullPathName(TEXT("hooking_msg.dll"), MAX_PATH, targetPath, NULL) == 0)
{
return FALSE;
}
// Inject the DLL into the process
if (!InjectDLL(processInformation.hProcess, targetPath))
{
return FALSE;
}
// Resume the suspended process
ResumeThread(processInformation.hThread);
// Wait for the process to complete
WaitForSingleObject(processInformation.hProcess, INFINITE);
// Close process and thread handles
CloseHandle(processInformation.hProcess);
CloseHandle(processInformation.hThread);
return TRUE;
}
Compiled with:
{
"type": "cppbuild",
"label": "Build injector",
"command": "cl.exe",
"args": [
"/Zi",
"/EHsc",
"/nologo",
"/Fe:",
"${workspaceFolder}/injector.exe",
"${workspaceFolder}/injector.cpp",
"user32.lib",
"/link",
"/MACHINE:X86"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$msCompile"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
hook_msg.dll :
#include <Windows.h>
#include "MinHook.h"
typedef int (WINAPI *MESSAGEBOXW)(HWND, LPCWSTR, LPCWSTR, UINT);
// Pointer for calling original MessageBoxW.
MESSAGEBOXW fpMessageBoxW = NULL;
// Detour function which overrides MessageBoxW.
int WINAPI DetourMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)
{
return fpMessageBoxW(hWnd, L"Hooked!", lpCaption, uType);
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
// Initialize MinHook.
if (MH_Initialize() != MH_OK)
{
return FALSE;
}
// Get the address of MessageBoxW from user32.dll.
HMODULE hUser32 = GetModuleHandleW(L"user32.dll");
if (hUser32 == NULL)
{
return FALSE;
}
FARPROC pMessageBoxW = GetProcAddress(hUser32, "MessageBoxW");
if (pMessageBoxW == NULL)
{
return FALSE;
}
// Create a hook for MessageBoxW, in disabled state.
if (MH_CreateHook(pMessageBoxW, &DetourMessageBoxW, reinterpret_cast<LPVOID*>(&fpMessageBoxW)) != MH_OK)
{
return FALSE;
}
// Enable the hook for MessageBoxW.
if (MH_EnableHook(pMessageBoxW) != MH_OK)
{
return FALSE;
}
break;
}
case DLL_PROCESS_DETACH:
{
// Disable the hook for MessageBoxW.
if (MH_DisableHook(&MessageBoxW) != MH_OK)
{
return FALSE;
}
// Uninitialize MinHook.
if (MH_Uninitialize() != MH_OK)
{
return FALSE;
}
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
compiled with:
{
"label": "build DLL",
"type": "shell",
"command": "cl.exe",
"args": [
"/LD",
"/EHsc",
"/MTd",
"/Fe:",
"${workspaceFolder}/hooking_msg.dll",
"${workspaceFolder}/hooking_msg.cpp",
"user32.lib",
"/I", "G:\\01_Doc\\05_Projects\\MinHook_133_lib\\include",
"/link",
"/MACHINE:X86",
"/LIBPATH:G:\\01_Doc\\05_Projects\\MinHook_133_lib\\lib",
"G:\\01_Doc\\05_Projects\\MinHook_133_lib\\lib\\libMinHook-x86-v141-mtd.lib"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$msCompile"]
}
Hello world:
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
MessageBoxW(NULL, L"HelloWorld", L"Message", MB_OK);
return 0;
}
compiled with:
{
"type": "cppbuild",
"label": "C/C++: cl.exe build active file",
"command": "cl.exe",
"args": [
"/Zi",
"/EHsc",
"/nologo",
"/Fe:",
"${workspaceFolder}/helloworld.exe",
"${workspaceFolder}/helloworld.cpp",
"user32.lib",
"/link",
"/SUBSYSTEM:WINDOWS",
"/MACHINE:X86"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$msCompile"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
I first read this post on stack CreateRemoteThread() succeeds yet doesn't do anything
As it wasn't solving my issue I tryed to log some info to be sure that the path is well written in the target process, the log are ok:
I also used a dependency walker to see if there was unsolved dependency but it seems to be ok.
Then i used x64dbg to walk throught the code of both injector and target process, first i run injector from x64dbg, I break just before the creation of the target thread. Then i attach an other instance of x64dbg to helloworld.exe to watch the call of LoadLibraryW
.
The dll path is correctly given to the target process with full path, yet the function LoadLibraryW
end with LastError
ERROR_MOD_NOT_FOUND and LastStatus
STATUS_DLL_NOT_FOUND.
Maybe my hook implementation is not good? Or something else... I don't know what to check now to troubleshot.
Thank you for your help.
You are injecting the DLL into a new process that is created with CREATE_SUSPENDED
. The process's main thread is not running yet, and so other DLLs that the process uses are not loaded until the main thread begins running. That includes kernel32.dll
, which your hook is trying to use.
If you want your hook to run when the new process begins running, you will have to use a different technique to load the DLL, such as queuing an APC request to the process's main thread, or even patching the process's entry point (MS Detours can handle that for you), etc.