Post

EnumProcesses

Introduction

Process enumeration can be used alongside process injection, to identify a target process and perform injection by utilising CreateToolHelp32Snapshot. We can perform process enumeration using EnumProcesses.

EnumProcesses

We start by reviewing Microsoft’s documentation on EnumProcesses and notice that the function returns a Process Ids (PIDs) as an array, without the associated process name. The problem is that only having PIDs without the associated process name makes it difficult to identify the process from a human perspective.

The solution is to use the OpenProcessGetModuleBaseName and EnumProcessModules WinAPIs.

  1. OpenProcess will be used to open a handle to a PID with PROCESS_QUERY_INFORMATION and PROCESS_VM_READ access rights.
  2. EnumProcessModules will be used to enumerate all the modules within the opened process.
  3. GetModuleBaseName will determine the name of the process, given the enumerated process modules.

EnumProcesses Advantage

Using the CreateToolhelp32Snapshot process enumeration method, a snapshot is created and a string comparison is performed to determine whether the process name matches the intended target process. The issue with that method is when there are multiple instances of a process running at different privilege levels, there’s no way to differentiate them during the string comparison. For example, some svchost.exe processes run with normal user privileges whereas others run with elevated privileges. There is no way to determine the privilege level of svchost.exe during the string comparison. Therefore the only indicator as to whether it’s privileged is if the OpenProcess call fails (assuming that the implementation is running with normal user privileges).

On the other hand, using the EnumProcesses process enumeration method provides the PID and handle to the process, and the objective is to obtain the process name. This method is guaranteed to be successful since a handle to the process already exists.

PrintProcesses Function

PrintProcesses is a custom function that prints the process name and PID of the enumerated processes. Only processes running with the same privileges as the implementation can have their information retrieved. Information about elevated processes cannot be retrieved, again, assuming the implementation is running with normal user privileges. Attempts to open a handle to high-privileged processes using OpenProcess will result in ERROR_ACCESS_DENIED error.

It’s possible to use OpenProcess’s response as an indicator to determine if the process can be targeted. Processes that cannot have a handle open to them cannot be targeted whereas the ones with a handle successfully opened can be targeted.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
BOOL PrintProcesses() {

	DWORD		adwProcesses	[1024 * 2],
			    dwReturnLen1		= NULL,
			    dwReturnLen2		= NULL,
			    dwNmbrOfPids		= NULL;

	HANDLE		hProcess		= NULL;
	HMODULE		hModule			= NULL;

	WCHAR		szProc			[MAX_PATH];

	// Get the array of PIDs
	if (!EnumProcesses(adwProcesses, sizeof(adwProcesses), &dwReturnLen1)) {
		printf("[!] EnumProcesses Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	// Calculating the number of elements in the array 
	dwNmbrOfPids = dwReturnLen1 / sizeof(DWORD);

	printf("[i] Number Of Processes Detected : %d \n", dwNmbrOfPids);

	for (int i = 0; i < dwNmbrOfPids; i++) {

		// If process is not NULL
		if (adwProcesses[i] != NULL) {

			// Open a process handle 
			if ((hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, adwProcesses[i])) != NULL) {

				// If handle is valid
				// Get a handle of a module in the process 'hProcess'
				// The module handle is needed for 'GetModuleBaseName'
				if (!EnumProcessModules(hProcess, &hModule, sizeof(HMODULE), &dwReturnLen2)) {
					printf("[!] EnumProcessModules Failed [ At Pid: %d ] With Error : %d \n", adwProcesses[i], GetLastError());
				}
				else {
					// If EnumProcessModules succeeded
					// Get the name of 'hProcess' and save it in the 'szProc' variable 
					if (!GetModuleBaseName(hProcess, hModule, szProc, sizeof(szProc) / sizeof(WCHAR))) {
						printf("[!] GetModuleBaseName Failed [ At Pid: %d ] With Error : %d \n", adwProcesses[i], GetLastError());
					}
					else {
						// Printing the process name & its PID
						wprintf(L"[%0.3d] Process \"%s\" - Of Pid : %d \n", i, szProc, adwProcesses[i]);
					}
				}

				// Close process handle 
				CloseHandle(hProcess);
			}
		}

		// Iterate through the PIDs array  
	}

	return TRUE;
}

GetRemoteProcessHandle Function

The code snippet below is an update to the previous PrintProcesses function. GetRemoteProcessHandle will perform the same tasks as PrintProcesses except it will return a handle to the specified process.

The updated function uses wcscmp to verify the target process. Furthermore, OpenProcess’s access control is changed from PROCESS_QUERY_INFORMATION | PROCESS_VM_READ to PROCESS_ALL_ACCESS to provide more access to the returned process object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
BOOL GetRemoteProcessHandle(LPCWSTR szProcName, DWORD* pdwPid, HANDLE* phProcess) {

	DWORD		adwProcesses	[1024 * 2],
			    dwReturnLen1		= NULL,
			    dwReturnLen2		= NULL,
			    dwNmbrOfPids		= NULL;

	HANDLE		hProcess		= NULL;
	HMODULE		hModule			= NULL;

	WCHAR		szProc			[MAX_PATH];
	
	// Get the array of PIDs
	if (!EnumProcesses(adwProcesses, sizeof(adwProcesses), &dwReturnLen1)) {
		printf("[!] EnumProcesses Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	// Calculating the number of elements in the array 
	dwNmbrOfPids = dwReturnLen1 / sizeof(DWORD);

	printf("[i] Number Of Processes Detected : %d \n", dwNmbrOfPids);

	for (int i = 0; i < dwNmbrOfPids; i++) {

		// If process is not NULL
		if (adwProcesses[i] != NULL) {

			// Open a process handle 
			if ((hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, adwProcesses[i])) != NULL) {

				// If handle is valid
				// Get a handle of a module in the process 'hProcess'.
				// The module handle is needed for 'GetModuleBaseName'
				if (!EnumProcessModules(hProcess, &hModule, sizeof(HMODULE), &dwReturnLen2)) {
					printf("[!] EnumProcessModules Failed [ At Pid: %d ] With Error : %d \n", adwProcesses[i], GetLastError());
				}
				else {
					// If EnumProcessModules succeeded
					// Get the name of 'hProcess' and save it in the 'szProc' variable 
					if (!GetModuleBaseName(hProcess, hModule, szProc, sizeof(szProc) / sizeof(WCHAR))) {
						printf("[!] GetModuleBaseName Failed [ At Pid: %d ] With Error : %d \n", adwProcesses[i], GetLastError());
					}
					else {
						// Perform the comparison logic
						if (wcscmp(szProcName, szProc) == 0) {
							wprintf(L"[+] FOUND \"%s\" - Of Pid : %d \n", szProc, adwProcesses[i]);
							// Return by reference
							*pdwPid		= adwProcesses[i];
							*phProcess	= hProcess;
							break;	
						}
					}
				}

				CloseHandle(hProcess);
			}
		}
	}

	// Check if pdwPid or phProcess are NULL
	if (*pdwPid == NULL || *phProcess == NULL)
		return FALSE;
	else
		return TRUE;
}

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Defining target process to enumerate by its name
#define TARGET_PROCESS L"svchost.exe"

int EnumProcesses_example1_basic() {
	DWORD		Pid = NULL;
	HANDLE		hProcess = NULL;

	if (!GetRemoteProcessHandle(TARGET_PROCESS, &Pid, &hProcess)) {
		return -1;
	}

	wprintf(L"[+] FOUND \"%s\" - Of Pid : %d \n", TARGET_PROCESS, Pid);

	//PrintProcesses();

	printf("[#] Press <Enter> To Quit ... ");
	(void)getchar();

	return 0;
}

EnumProcesses Function

This post is licensed under CC BY 4.0 by the author.