Malware development trick – part 37: Enumerate process modules via VirtualQueryEx. Simple C++ example.

Hello, cybersecurity enthusiasts and white hackers!

Malware development trick - part 37: Enumerate process modules via VirtualQueryEx. Simple C++ example.

Today, this post is the result of my own research on another popular malware development trick: get list of modules of target process.

It’s similar to my previous post about enum list of modules, but in this case I used VirtualQueryEx

practical examplePermalink

First of all, we just use one of the methods to find target process PID. For example I used this one:

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
  _In_ HANDLE ph,
  _In_ ACCESS_MASK DesiredAccess,
  _In_ ULONG HandleAttributes,
  _In_ ULONG Flags,
  _Out_ PHANDLE Newph
);

int findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function address
  fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
      pid = GetProcessId(current);
      break;
    }
  }

  return pid;
}

Then, create function which opens a specified process, iterates through its memory regions using VirtualQueryEx, and retrieves information about the loaded modules, including their names and base addresses:

// function to list modules loaded by a specified process
int listModulesOfProcess(int pid) {
  HANDLE ph;
  MEMORY_BASIC_INFORMATION mbi;
  char * base = NULL;

  ph = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
  if (ph == NULL)
    return -1;

  printf("modules found:\n");
  printf("name\t\t\t base address\n");
  printf("=================================================================================\n");

  while (VirtualQueryEx(ph, base, &mbi, sizeof(mbi)) == sizeof(MEMORY_BASIC_INFORMATION)) {
    char szModName[MAX_PATH];

    // only focus on the base address regions
    if ((mbi.AllocationBase == mbi.BaseAddress) && (mbi.AllocationBase != NULL)) {
      if (GetModuleFileNameEx(ph, (HMODULE) mbi.AllocationBase, (LPSTR) szModName, sizeof(szModName) / sizeof(TCHAR)))
        printf("%#25s\t\t%#10llx\n", szModName, (unsigned long long)mbi.AllocationBase);
      }
      // check the next region
      base += mbi.RegionSize;
    }
  
  CloseHandle(ph);
  return 0;
}

As you can see, the code enters a while loop that continues as long as the VirtualQueryEx function successfully retrieves memory information. This loop iterates through memory regions within the target process.

Then checks whether the AllocationBase of the current memory region matches the BaseAddress. This condition ensures that it only focuses on the base address regions. If the conditions are met, it proceeds to retrieve the module name.

if (GetModuleFileNameEx(ph, (HMODULE) mbi.AllocationBase, (LPSTR) szModName, sizeof(szModName) / sizeof(TCHAR))) – The GetModuleFileNameEx function is called to retrieve the module filename associated with the current memory region’s base address. If successful, it stores the filename in szModName.

If the module name retrieval was successful, the code prints the module name and base address in a formatted manner.

After processing the current region, the base pointer is incremented by the size of the region to check the next region in the subsequent iteration of the loop.

That’s all.

So, the full source code is looks like this (hack.c):

/*
 * hack.c - get the list of 
 * modules of the process via VirtualQueryEx. C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2023/11/07/malware-tricks-37.html
*/
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
#include <tlhelp32.h>
#include <shlwapi.h>
#include <psapi.h>

#pragma comment(lib, "ntdll.lib")
#pragma comment(lib, "shlwapi.lib")

typedef NTSTATUS (NTAPI * fNtGetNextProcess)(
  _In_ HANDLE ph,
  _In_ ACCESS_MASK DesiredAccess,
  _In_ ULONG HandleAttributes,
  _In_ ULONG Flags,
  _Out_ PHANDLE Newph
);

int findMyProc(const char * procname) {
  int pid = 0;
  HANDLE current = NULL;
  char procName[MAX_PATH];

  // resolve function address
  fNtGetNextProcess myNtGetNextProcess = (fNtGetNextProcess) GetProcAddress(GetModuleHandle("ntdll.dll"), "NtGetNextProcess");

  // loop through all processes
  while (!myNtGetNextProcess(current, MAXIMUM_ALLOWED, 0, 0, &current)) {
    GetProcessImageFileNameA(current, procName, MAX_PATH);
    if (lstrcmpiA(procname, PathFindFileName((LPCSTR) procName)) == 0) {
      pid = GetProcessId(current);
      break;
    }
  }

  return pid;
}

// function to list modules loaded by a specified process
int listModulesOfProcess(int pid) {
  HANDLE ph;
  MEMORY_BASIC_INFORMATION mbi;
  char * base = NULL;

  ph = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
  if (ph == NULL)
    return -1;

  printf("modules found:\n");
  printf("name\t\t\t base address\n");
  printf("=================================================================================\n");

  while (VirtualQueryEx(ph, base, &mbi, sizeof(mbi)) == sizeof(MEMORY_BASIC_INFORMATION)) {
    char szModName[MAX_PATH];

    // only focus on the base address regions
    if ((mbi.AllocationBase == mbi.BaseAddress) && (mbi.AllocationBase != NULL)) {
      if (GetModuleFileNameEx(ph, (HMODULE) mbi.AllocationBase, (LPSTR) szModName, sizeof(szModName) / sizeof(TCHAR)))
        printf("%#25s\t\t%#10llx\n", szModName, (unsigned long long)mbi.AllocationBase);
      }
      // check the next region
      base += mbi.RegionSize;
    }
  
  CloseHandle(ph);
  return 0;
}

int main(int argc, char* argv[]) {
  int pid = 0; // process ID
  pid = findMyProc(argv[1]);
  printf("%s%d\n", pid > 0 ? "process found at pid = " : "process not found. pid = ", pid);
  if (pid != 0)
    listModulesOfProcess(pid);
  return 0;
}

demoPermalink

Let’s go to see this logic in action.

Compile it:

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 -lpsapi -lshlwapi

Malware development trick - part 37: Enumerate process modules via VirtualQueryEx. Simple C++ example.

Then, open target process in the victim’s machine:

Malware development trick - part 37: Enumerate process modules via VirtualQueryEx. Simple C++ example.

And just run our hack.exe:
只需运行我们的 hack.exe :

.\hack.exe mspaint.exe

Malware development trick - part 37: Enumerate process modules via VirtualQueryEx. Simple C++ example.

Malware development trick - part 37: Enumerate process modules via VirtualQueryEx. Simple C++ example.

As you can see, everything is worked perfectly! =^..^=
如您所见,一切都很完美!=^..^=

Keep in mind that this code may have limitations and dependencies on specific Windows APIs. Additionally, it relies on the process name for identification, which may not be unique.
请记住,此代码可能对特定的 Windows API 有限制和依赖关系。此外,它还依赖于进程名称进行标识,该名称可能不是唯一的。

This code can also help you develop your own script to work with process memory, for example for forensics or other tasks on blue team practical cases.
此代码还可以帮助您开发自己的脚本来使用进程内存,例如用于取证或蓝队实际案例中的其他任务。

I hope this post spreads awareness to the blue teamers of this interesting malware dev technique, and adds a weapon to the red teamers arsenal.
我希望这篇文章能让蓝队员了解这种有趣的恶意软件开发技术,并为红队员的武器库增添武器。

VirtualQueryEx 虚拟查询Ex
GetModuleFileNameEx
Find process ID by name and inject to it
按名称查找进程 ID 并注入其中

Find PID via NtGetNextProcess
通过 NtGetNextProcess 查找 PID

source code in github
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
所有图纸和截图都是我的

 

原文始发于cocomelonc:Malware development trick – part 37: Enumerate process modules via VirtualQueryEx. Simple C++ example.

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...