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.
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.
Share Improve this question edited Feb 8 at 0:47 JohnA asked Feb 7 at 23:38 JohnAJohnA 6361 gold badge8 silver badges23 bronze badges 6 | Show 1 more comment2 Answers
Reset to default 5CreateRemoteThread()
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 allocateremoteString
to hold the path to that DLL, and useCreateRemoteThread()
to invokeLoadLibrary()
withremoteString
as its input parameter.This is the easiest and most common way to inject remote code using
CreateRemoteThread()
, asLoadLibrary()
is compatible withCreateRemoteThread()
.create a DLL that implements a
SetWindowsHookEx()
callback, such asWH_CALLWNDPROC
orWH_GETMESSAGE
, and then you can do what you want inside of that callback. Then have your app useSetWindowsHookEx()
to install the DLL as the relevant hook type in the target process, and then useSendMessage()
orPost[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 blockPAGE_EXECUTE
rights withVirtualProtectEx()
. Then useCreateRemoteThread()
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.
After a quick search in the Win32 API documentation, it looks like you're using CreateRemoteThread
wrong.
As defined in section parameters, lpStartAddress
must exist in the remote process and lpParameter
is the parameter passed to the threaded function:
[in] lpStartAddress
A pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the starting address of the thread in the remote process. The function must exist in the remote process. For more information, see ThreadProc.
[in] lpParameter
A pointer to a variable to be passed to the thread function.
The ThreadProc
documentation is a good illustration:
An application-defined function that serves as the starting address for a thread. Specify this address when calling the CreateThread, CreateRemoteThread, or CreateRemoteThreadEx function.
The LPTHREAD_START_ROUTINE type defines a pointer to this callback function. ThreadProc is a placeholder for the application-defined function name.
This is the type of function you can point to with CreateRemoteThread
. This way, CreateRemoteThread
and your custom function agree on a function prototype for this mechanism to work.
Another hint of misuse of the mechanism would be the prototype of MessageBoxA
itself. This function appears to have only one non-optional parameter, and that is an unsigned integer ([in] UINT uType
) :
int MessageBoxA(
[in, optional] HWND hWnd,
[in, optional] LPCSTR lpText,
[in, optional] LPCSTR lpCaption,
[in] UINT uType
);
On the other hand, CreateRemoteThread
defines lpParameter
as a pointer to any type ([in] LPVOID lpParameter
):
HANDLE CreateRemoteThread(
[in] HANDLE hProcess,
[in] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
[in] LPVOID lpParameter,
[in] DWORD dwCreationFlags,
[out] LPDWORD lpThreadId
);
The last indication in this direction is the following part of the CreateRemoteThread
documentation (return values):
Note that
CreateRemoteThread
may succeed even iflpStartAddress
points to data, code, or is not accessible.
My understanding is that the thread is created even if lpStartAddress
is invalid, and then, when the thread executes, it throws an exception. Creation is successful, but nothing happens as a result of the exception. As suggested in the section on the return value, a call to GetLastError
would be a good idea to get more details on what's going on and get extended error information.
My suggestion would be to first check for the error with a call to GetLastError
and then try to define a function in ThreadProc
format which would interpret the void* parameter as LPCSTR and this function would call MessageBoxA
with all the parameters hardcoded except the character string (title -> lpCaption
or contents of the message box -> lpText
) which would be the newly converted LPCSTR. If you want to give your custom function more parameters, I suggest you use the same method as suggested with the LPCSTR, but this time with a custom object.
Hope this helps
stderr
viafprintf
. Many operating systems have different output "pipes" for regular and error outputs. (So you can write scripts that redirect error or standard output). – Thomas Matthews Commented Feb 7 at 23:55MessageBoxA("Hello World!")
. But MessageBox take not 1 parameter but 4 and first parameter must be parent window handle or 0. But not string. So your code and must not work. It wrong by design. You need write shellcode, which call messagebox in process – RbMm Commented Feb 8 at 2:45MessageBoxA(nullptr, "Hello, World!", "Title", MB_OK)
directly. – Raymond Chen Commented Feb 8 at 3:12