So I'm learning C coming from years of C# and I figured that a good starting project would be something a bit more low level.
I'm trying to invoke a MessageBox in a blank WPF App and it seems like everything would work but for some reason I'm not getting a MessageBox.
Both the WpfApp and the App I built are both compiled into x64 versions so I don't see why there would be any architectural issues.
Debug Output looks fine too.
I've tried running both applications with administrator privileges. I've tried invoking into different apps such as Notepad (both 32 and 64 bit version), as well as the Calculator.
Process ID for WpfApp1.exe found: 948
Target process found. PID: 948
Opened handle to process.
Handle to user32.dll loaded successfully.
Address of MessageBoxA: 00007ffc67fc8b70
Allocated memory at remote address: 00000262a8500000
Successfully wrote message to remote memory.
Remote thread created successfully.
Remote thread completed.
But I guess there's something wrong here?
#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
#include <unistd.h>
DWORD GetPID(const char *processName);
int main(void) {
DWORD processId = GetPID("WpfApp1.exe");
if (!processId) {
printf("Process not found\n");
fflush(stdout);
return 1;
}
printf("Target process found. PID: %lu\n", processId);
fflush(stdout);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
if (!hProcess) {
printf("Failed to open process. Error code: %lu\n", GetLastError());
fflush(stdout);
return 1;
}
printf("Opened handle to process.\n");
fflush(stdout);
// Explicitly load user32.dll
HMODULE hUser32 = LoadLibrary("user32.dll");
if (!hUser32) {
printf("Failed to load user32.dll. Error code: %lu\n", GetLastError());
fflush(stdout);
CloseHandle(hProcess);
return 1;
}
printf("Handle to user32.dll loaded successfully.\n");
fflush(stdout);
LPVOID msgBoxAddress = (LPVOID)GetProcAddress(hUser32, "MessageBoxA");
if (!msgBoxAddress) {
printf("Failed to find address of MessageBoxA. Error code: %lu\n", GetLastError());
fflush(stdout);
FreeLibrary(hUser32);
CloseHandle(hProcess);
return 1;
}
printf("Address of MessageBoxA: %p\n", msgBoxAddress);
fflush(stdout);
LPVOID remoteString = VirtualAllocEx(hProcess, NULL, 256, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!remoteString) {
printf("Failed to allocate memory in target process. Error code: %lu\n", GetLastError());
fflush(stdout);
FreeLibrary(hUser32);
CloseHandle(hProcess);
return 1;
}
printf("Allocated memory at remote address: %p\n", remoteString);
fflush(stdout);
if (!WriteProcessMemory(hProcess, remoteString, "Hello World!", 13, NULL)) {
printf("Failed to write to remote memory. Error code: %lu\n", GetLastError());
fflush(stdout);
VirtualFreeEx(hProcess, remoteString, 0, MEM_RELEASE);
FreeLibrary(hUser32);
CloseHandle(hProcess);
return 1;
}
printf("Successfully wrote message to remote memory.\n");
fflush(stdout);
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)msgBoxAddress, remoteString, 0, NULL);
if (!hThread) {
printf("Failed to create remote thread. Error code: %lu\n", GetLastError());
fflush(stdout);
VirtualFreeEx(hProcess, remoteString, 0, MEM_RELEASE);
FreeLibrary(hUser32);
CloseHandle(hProcess);
return 1;
}
printf("Remote thread created successfully.\n");
fflush(stdout);
WaitForSingleObject(hThread, INFINITE);
printf("Remote thread completed.\n");
fflush(stdout);
VirtualFreeEx(hProcess, remoteString, 0, MEM_RELEASE);
FreeLibrary(hUser32);
CloseHandle(hThread);
CloseHandle(hProcess);
return 0;
}
DWORD GetPID(const char *processName) {
PROCESSENTRY32 processEntry;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
printf("Failed to create snapshot. Error code: %lu\n", GetLastError());
fflush(stdout);
return 0;
}
processEntry.dwSize = sizeof(processEntry);
if (Process32First(hSnapshot, &processEntry)) {
do {
if (strcmp(processEntry.szExeFile, processName) == 0) {
DWORD pid = processEntry.th32ProcessID;
CloseHandle(hSnapshot);
printf("Process ID for %s found: %lu\n", processName, pid);
fflush(stdout);
return pid;
}
} while (Process32Next(hSnapshot, &processEntry));
}
printf("Failed to find process: %s\n", processName);
fflush(stdout);
CloseHandle(hSnapshot);
return 0;
}
This also seems like quite a lot of code compared to various articles I've read online.
CreateRemoteThread()
simply cannot call MessagBoxA()
directly, as MessageBoxA()
does not have the correct function signature that CreateRemoteThread()
is expecting:
int WINAPI MessageBoxA(
[in, optional] HWND hWnd,
[in, optional] LPCSTR lpText,
[in, optional] LPCSTR lpCaption,
[in] UINT uType
);
DWORD WINAPI ThreadProc(
_In_ LPVOID lpParameter
);
CreateRemoteThread()
can pass only 1 parameter to the target function, but MessageBoxA()
takes 4 parameters.
So, in this situation, you will have to change your approach. You can either:
create a DLL whose DllMain
creates a local thread, and then you can have that thread do whatever you want. Then have your app allocate remoteString
to hold the path to that DLL, and use CreateRemoteThread()
to invoke LoadLibrary()
with remoteString
as its input parameter.
This is the easiest and most common way to inject remote code using CreateRemoteThread()
, as LoadLibrary()
is compatible with CreateRemoteThread()
.
create a DLL that implements a SetWindowsHookEx()
callback, such as WH_CALLWNDPROC
or WH_GETMESSAGE
, and then you can do what you want inside of that callback. Then have your app use SetWindowsHookEx()
to install the DLL as the relevant hook type in the target process, and then use SendMessage()
or Post[Thread]Message()
to the target process to trigger the callback.
Have your app allocate a remote memory block that holds CPU instructions that call MessageBoxA()
directly. Give that memory block PAGE_EXECUTE
rights with VirtualProtectEx()
. Then use CreateRemoteThread()
to invoke that code block as-if it were a normal function.
This is a difficult technique if you are not familiar with how CPU instructions work in memory.