Malware development trick - part 36: Enumerate process modules. Simple C++ example

Hello, cybersecurity enthusiasts and white hackers!

hack

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

Let’s say we created successfully DLL injection to process. How to check if DLL in list of modules of our process?

hack

practical example

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, just use Module32First and Module32Next functions from Windows API.

// function to list modules loaded by a specified process
int listModulesOfProcess(int pid) {

HANDLE mod;
MODULEENTRY32 me32;

mod = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
if (mod == INVALID_HANDLE_VALUE) {
printf(“CreateToolhelp32Snapshot error :(\n”);
return -1;
}

me32.dwSize = sizeof(MODULEENTRY32);
if (!Module32First(mod, &me32)) {
CloseHandle(mod);
return -1;
}

printf(“modules found:\n”);
printf(“name\t\t\t base address\t\t\tsize\n”);
printf(“=================================================================================\n”);
do {
printf(“%#25s\t\t%#10llx\t\t%#10d\n”, me32.szModule, me32.modBaseAddr, me32.modBaseSize);
} while (Module32Next(mod, &me32));
CloseHandle(mod);
return 0;
}

As you can see, the code is a bit similar to the PID search logic with CreateToolHelp32Snapshot, Process32First and Process32Next.

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

/*
 * hack.c - get the list of modules of the process. C++ implementation
 * @cocomelonc
 * https://cocomelonc.github.io/malware/2023/09/25/malware-tricks-36.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 mod;
MODULEENTRY32 me32;

mod = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
if (mod == INVALID_HANDLE_VALUE) {
printf(“CreateToolhelp32Snapshot error :(\n”);
return -1;
}

me32.dwSize = sizeof(MODULEENTRY32);
if (!Module32First(mod, &me32)) {
CloseHandle(mod);
return -1;
}

printf(“modules found:\n”);
printf(“name\t\t\t base address\t\t\tsize\n”);
printf(“=================================================================================\n”);
do {
printf(“%#25s\t\t%#10llx\t\t%#10d\n”, me32.szModule, me32.modBaseAddr, me32.modBaseSize);
} while (Module32Next(mod, &me32));
CloseHandle(mod);
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;
}

You can use this code to check if a DLL is in the list of modules of the target process.

demo

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

hack

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

hack

hack

And just run our hack.exe:

.\hack.exe mspaint.exe

hack

hack

hack

Also, check with DLL injection logic:

hack

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.

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.

Find process ID by name and inject to it
Find PID via NtGetNextProcess
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

Article Link: Malware development trick - part 36: Enumerate process modules. Simple C++ example. - cocomelonc