Module Enumeration
We have already discussed different ways to enumerate processes, and one additional enumeration crucial for offensive programming is the enumeration of modules inside processes. Modules are generally DLL files loaded for the process. Here, we will discuss the way to enumerate and load the DLL files (modules) associated with a specified process using the EnumProcessModules function.
The image below can describe the flow that we will cover in order to enumerate the modules. First, we need to enumerate all processes using either of the methods. Once we have the process ID, we pass that ID to open a handle to the process with OpenProcess. We will then use that handle with EnumProcessModules, which opens handles to the modules within that process. Both the process handle and module handles are passed to GetModuleFileNameExA, which enumerates the fully qualified path for the file containing the specified module.
Enumerate Process Ids
We have previously discussed the enumeration of processes and their entities such as process IDs and process names in previous blogs. You can refer to this link for more information on the ToolHelp32 function. Process enumeration can also be achieved using other methods like the Windows Terminal Services API, EnumProcess, or NtQuerySystemInformation.
Define the function for process Id enumeration and call it within the main function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void FindProcess() {
// Your Code here
}
int main(){
FindProcess();
// Provide the Proces Ids and take an input from the user for the process they want to enumerate the modules for.
DWORD pid;
cout << "\n\nEnter ProcessId: ";
cin >> pid;
// Pass the Process Id to FindProcessModule
FindProcessModule(pid);
}
OpenProcess
The next step involves opening a handle to the process using the OpenProcess function. This function requires three flags: dwDesiredAccess, bInheritHandle, and dwProcessId.
SYNTAX
1
2
3
4
5
HANDLE OpenProcess(
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwProcessId
);
- DWORD dwDesiredAccess: Contains the access rights to the process object. Since we are using the function GetModuleFileNameExA, it requires PROCESS_QUERY_INFORMATION and PROCESS_VM_READ attributes.
- BOOL bInheritHandle: Specifies whether the created process should inherit the handle of the original process. Since we do not need that now, we can set it to FALSE.
- DWORD dwProcessId: ID of the process that we want to open.
Create another function such as FindProcessModule
and open the handle for the process.
1
2
3
4
5
6
7
8
9
10
11
12
void FindProcessModule(DWORD pid) {
HANDLE hProcess;
BOOL bInHandle;
DWORD dwProcessId = pid;
// Opening the handle of the process. PROCESS_QUERY_INFORMATION and PROCESS_VM_READ is needed for GetModuleFileNameEx function
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessId);
if (hProcess == NULL){
cout << "\nUnable to Identify the Process ID\n";
return;
}
EnumProcessModules
Once we have opened the handle for the specified process, the next step is to retrieve handles for each module within that process using the handle we created. The return type is boolean and the function return with zero if fails.
SYNTAX
1
2
3
4
5
6
BOOL EnumProcessModules(
[in] HANDLE hProcess,
[out] HMODULE *lphModule,
[in] DWORD cb,
[out] LPDWORD lpcbNeeded
);
- HANDLE hProcess: This parameter requires a handle to the process.
- HMODULE *lpModule: An array where the module handles for the process are stored. HMODULE provides handle to the module (EXE or DLL) within the selected process. We can give it the size of 1024 or 2048 initially since we do not have an idea of the potentital number of the modules here.
- DWORD cb: Size of the
lpModule
array in bytes. - LPDWORD lpcbNeeded: Number of bytes required to store the list of module handles in the
lpModule
array.
1
2
3
4
5
6
7
8
// Output variable which points to an array of HMODULE values. HMODULE provides handle to the module (EXE or DLL) within the selected process.
HMODULE lphModule[1024];
// Size in bytes for lphModule
DWORD cbNeeded;
// Opens handle for each module (DLLs) within the selected process.
BOOL bProcessModule = EnumProcessModules(hProcess, lphModule, sizeof(lphModule), &cbNeeded);
GetModuleFileNameExA
Once we have the handle for each module in the process, we can use GetModuleFileNameExA
to retrieve the full path of the file for each module, i.e., the DLL file.
SYNTAX
1
2
3
4
5
6
DWORD GetModuleFileNameExA(
[in] HANDLE hProcess,
[in, optional] HMODULE hModule,
[out] LPSTR lpFilename,
[in] DWORD nSize
);
- HANDLE hProcess: It represents the handle to the process which contains the module.
- HMODULE hModule: It represents the handle to the modules within that process.
- LPSTR lpFilename: Provides the full path for the specified modules.
- DWORD nSize: It contains the buffer size for the path in bytes.
Before calling the GetModuleFileNameExA
function, we need to iterate through the list of modules in the array because lphModule
contains this list in an array format. To determine the number of entries in lphModule
, we divide lpcbNeeded
(the number of bytes required to store the list of module handles) by the size in bytes of each element in module handle (sizeof(HMODULE)
). This calculation, represented as int numberOfModules = cbNeeded / sizeof(HMODULE);
, gives us the count of modules based on the information returned by EnumProcessModules
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Identify the number of HMODULE values
int numberOfModules = cbNeeded / sizeof(HMODULE);
{
printf("\n%s \t\t%s\n", "Address", "DLLs");
// Iterate through each modlues
for (int i = 0; i < numberOfModules; i++)
{
TCHAR szModName[MAX_PATH];
DWORD szModNameSize = sizeof(szModName);
// Get the full path to the module's file.
// Process - Handle to the process, lphModule[i] - handle to the module, szModName - Filename (Output) that means it provides DLL, szModNameSize - size of the buffer
if (GetModuleFileNameExA(hProcess, lphModule[i], szModName,szModNameSize))
{
// Print the module name and handle value.
printf("%#llx \t\t%s\n", lphModule[i], szModName);
}
}
}
}
References
- https://learn.microsoft.com/en-us/windows/win32/psapi/enumerating-all-modules-for-a-process
- https://www.youtube.com/watch?v=kEIE91kpsGg
- https://medium.com/@stackzero/how-to-do-process-enumeration-an-alternative-way-stackzero-fad874477cda#:~:text=Process%20enumeration%20is%20a%20key,legitimate%20processes%20to%20evade%20detection.
- https://www.codereversing.com/archives/265
- https://giodicanio.com/2024/06/10/how-to-enumerate-the-modules-loaded-in-a-process/
- https://www.codeproject.com/Articles/2262/Process-Module-and-Thread-enumeration-classes