Tutorials

Enumerating processes in Windows XP kernel mode drivers

Every time the operation system executes a program, it in fact creates a new process for this program and starts it as this process. From that point there are no filenames exist for the system, only the process IDs. Sometimes we need to obtain the filename a process belongs to. There are several ways to implement that from the user mode, but in the kernel the best approach is to use undocumented functions of the system, just like the Task Manage does when displaying currently running processes. The basic idea behind this approach is to use the undocumented ZwQuerySystemInformation function. It has the following structure:

ZwQuerySystemInformation(
	IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
	OUT PVOID SystemInformation,
	IN ULONG SystemInformationLength,
	OUT PULONG ReturnLength OPTIONAL
);

Among many other quite useful things, this function can return detailed information about the processes, currently running by the system. The information class selector SystemInformationClass is used to set the function to output various kinds of information. In order to output information about processes and threads, SystemInformationClass has to be set to “5”.

There are also several additional structures to declare, they might be put in a separate header file.

SYSTEM_INFORMATION_CLASS contains many control parameters, but we are only interested in SystemProcessAndThreadInformation:

// enumproc.h
#ifndef _ENUMPROC_
#define _ENUMPROC_

typedef enum _SYSTEM_INFORMATION_CLASS { 
	SystemBasicInformation, 				// 0 
	SystemProcessorInformation, 			// 1 
	SystemPerformanceInformation, 			// 2
	SystemTimeOfDayInformation, 			// 3
	SystemNotImplemented1, 				// 4
	SystemProcessesAndThreadsInformation, 		// 5
	SystemCallCounts, 					// 6
	SystemConfigurationInformation, 			// 7
	SystemProcessorTimes, 				// 8
	SystemGlobalFlag, 					// 9
	SystemNotImplemented2, 				// 10
	SystemModuleInformation, 				// 11
	SystemLockInformation, 				// 12
	SystemNotImplemented3, 				// 13
	SystemNotImplemented4, 				// 14
	SystemNotImplemented5, 				// 15
	SystemHandleInformation, 				// 16
	SystemObjectInformation, 				// 17
	SystemPagefileInformation, 				// 18
	SystemInstructionEmulationCounts, 			// 19
	SystemInvalidInfoClass1, 				// 20
	SystemCacheInformation, 				// 21
	SystemPoolTagInformation, 				// 22
	SystemProcessorStatistics, 				// 23
	SystemDpcInformation, 				// 24
	SystemNotImplemented6, 				// 25
	SystemLoadImage, 					// 26
	SystemUnloadImage, 				// 27
	SystemTimeAdjustment, 				// 28
	SystemNotImplemented7, 				// 29
	SystemNotImplemented8, 				// 30
	SystemNotImplemented9, 				// 31
	SystemCrashDumpInformation, 			// 32
	SystemExceptionInformation, 			// 33
	SystemCrashDumpStateInformation, 			// 34
	SystemKernelDebuggerInformation, 			// 35
	SystemContextSwitchInformation, 			// 36
	SystemRegistryQuotaInformation, 			// 37
	SystemLoadAndCallImage, 				// 38
	SystemPrioritySeparation, 				// 39
	SystemNotImplemented10, 				// 40
	SystemNotImplemented11, 				// 41
	SystemInvalidInfoClass2, 				// 42
	SystemInvalidInfoClass3, 				// 43
	SystemTimeZoneInformation, 				// 44
	SystemLookasideInformation, 			// 45
	SystemSetTimeSlipEvent, 				// 46
	SystemCreateSession, 				// 47
	SystemDeleteSession, 				// 48
	SystemInvalidInfoClass4, 				// 49
	SystemRangeStartInformation, 			// 50
	SystemVerifierInformation, 				// 51
	SystemAddVerifier, 				// 52
	SystemSessionProcessesInformation 			// 53
} SYSTEM_INFORMATION_CLASS;

typedef struct _SYSTEM_THREAD_INFORMATION {
	LARGE_INTEGER KernelTime;
	LARGE_INTEGER UserTime;
	LARGE_INTEGER CreateTime;
	ULONG WaitTime;
	PVOID StartAddress;
	CLIENT_ID ClientId;
	KPRIORITY Priority;
	KPRIORITY BasePriority;
	ULONG ContextSwitchCount;
	LONG State;
	LONG WaitReason;
} SYSTEM_THREAD_INFORMATION, * PSYSTEM_THREAD_INFORMATION;

typedef struct _SYSTEM_PROCESS_INFORMATION {
	ULONG NextEntryDelta;
	ULONG ThreadCount;
	ULONG Reserved1[6];
	LARGE_INTEGER CreateTime;
	LARGE_INTEGER UserTime;
	LARGE_INTEGER KernelTime;
	UNICODE_STRING ProcessName;
	KPRIORITY BasePriority;
	ULONG ProcessId;
	ULONG InheritedFromProcessId;
	ULONG HandleCount;
	ULONG Reserved2[2];
	VM_COUNTERS VmCounters;
	IO_COUNTERS IoCounters;
	SYSTEM_THREAD_INFORMATION Threads[1];
} SYSTEM_PROCESS_INFORMATION, * PSYSTEM_PROCESS_INFORMATION;

NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySystemInformation(
	IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
	OUT PVOID SystemInformation,
	IN ULONG SystemInformationLength,
	OUT PULONG ReturnLength OPTIONAL
);

#endif

Before operating with ZwQuerySystemInformation, System information class option has to be defined in the module of the driver, responsible for process enumeration. It can easily be done with the preprocessor:

#define SystemProcessesAndThreadsInformation 5

The enumerating procedure itself is straight forward, but it differs from the one for the user mode. First of all, ZwQuerySystemInformation fills in the buffer with the information about all the processes currently running by the system. Since there is no way to predict how many processes are there and therefore, how big the buffer should be, we need to make sure that its size is big enough to hold the information. A nice way to figure out if the buffer size is big enough is to check the status of ZwQuerySystemInformation when it is called. This status is called NTSTATUS and it has a special flag STATUS_INFO_LENGTH_MISMATCH. If that flag is true, the buffer size is too small to hold the information being passed to it, and it has to be increased.
Flag STATUS_INFO_LENGTH_MISMATCH may also be defined with the preprocessor as follows:

#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)

A simple do…while loop can be used to implement the information acquisition:

ULONG cbBuffer = 0x8000; // declare initial size of buffer - 32kb
LPVOID pBuffer = NULL; // declare pointer to a buffer
NTSTATUS Status;
do
{
	pBuffer = ExAllocatePool (NonPagedPool, cbBuffer); //allocate memory for the buffer of size cbBuffer
	if (pBuffer == NULL) // if memory allocation failed, exit
	{
		return 1;
	}
	// try to obtain system information into the buffer
	Status = ZwQuerySystemInformation(
			SystemProcessesAndThreadsInformation, pBuffer, cbBuffer, NULL);
	// if the size of the information is larger than the size of the buffer
	if (Status == STATUS_INFO_LENGTH_MISMATCH)
	{
		ExFreePool(pBuffer); // free the memory associated with the buffer 
		cbBuffer *= 2; // and increase buffer size twice its original size
	}
	else if (!NT_SUCCESS(Status)) // if operation is not succeeded by any other reason
	{
		ExFreePool(pBuffer); // free the memory
		return 1; //and exit
	}
}
while (Status == STATUS_INFO_LENGTH_MISMATCH); 

If everything went fine so far, pBuffer is pointed to a memory location where the correct system information is stored. Now we have to decode this information to SYSTEM_PROCESS_INFORMATION class format:

PSYSTEM_PROCESS_INFORMATION pInfo;
pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuffer;

As I have mentioned before, pInfo contains information about all the processes acquired by ZwQuerySystemInformation at some moment in time. In order to obtain a process name having its ID we have to check all the PIDs stored in pInfo against the given ID. For loop is used for this purpose:

for (;;) // forever do
{
	LPWSTR pszProcessName = pInfo->ProcessName.Buffer; // assign a process name to a new variable
	if (pszProcessName == NULL) // if no name available
	pszProcessName = L"NULL"; // set it to something
	if (pInfo->ProcessId == PID) // check its process ID against the PID we are looking for
	{ // if they matched
		wcstombs(ProcessName,pszProcessName,256); // convert wide character string “pszProcessName” 
								// to character string “path”
		break; // exit the loop
	}
	if (pInfo->NextEntryDelta == 0) // if there are no other entries in pInfo
		break; // exit the loop

	// if we are still in the loop, current entry does not contain 
	// the process we are looking for, but there is at least one more entry in pInfo
	pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo)+ pInfo->NextEntryDelta); // obtain that new entry
}

The complete code for process enumeration is in the following file presented below:

// enumproc.c
#include <ntddk.h>
#include <stdlib.h>
#include "enumproc.h"
char ProcessName[256]; // define process name container

typedef ULONG DWORD;
typedef VOID* LPVOID;
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#define SystemProcessesAndThreadsInformation 5 // define control parameter for system information class

void GetProcessName(int PID, char* pProcessName[256])
{
	ULONG cbBuffer = 0x8000; // declare initial size of buffer - 32kb
	LPVOID pBuffer = NULL; // declare pointer to a buffer
	NTSTATUS Status;
	PSYSTEM_PROCESS_INFORMATION pInfo;
	do
	{
		pBuffer = ExAllocatePool (NonPagedPool, cbBuffer); //allocate memory for the buffer of size cbBuffer
		if (pBuffer == NULL) // if memory allocation failed, exit
		{
			return 1;
		}
		// try to obtain system information into the buffer
		Status = ZwQuerySystemInformation(
								SystemProcessesAndThreadsInformation, pBuffer, cbBuffer, NULL);
		// if the size of the information is larger than the size of the buffer
		if (Status == STATUS_INFO_LENGTH_MISMATCH)
		{
			ExFreePool(pBuffer); // free the memory associated with the buffer 
			cbBuffer *= 2; // and increase buffer size twice its original size 
		}
		else if (!NT_SUCCESS(Status)) // if operation is not succeeded by any other reason
		{
			ExFreePool(pBuffer); // free the memory
			return 1; //and exit
		}
	}
	while (Status == STATUS_INFO_LENGTH_MISMATCH);

	PSYSTEM_PROCESS_INFORMATION pInfo;
	pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuffer;

	for (;;) // forever do
	{
		LPWSTR pszProcessName = pInfo->ProcessName.Buffer; // assign a process name to a new variable
		if (pszProcessName == NULL) // if no name available
		pszProcessName = L"NULL"; // set it to something

		if (pInfo->ProcessId == PID) // check its process ID against the PID we are looking for
		{ // if they matched
			wcstombs(ProcessName,pszProcessName,256); // convert wide character string “pszProcessName” 
									// to character string “path”
			break; // exit the loop
		}

		if (pInfo->NextEntryDelta == 0) // if there are no other entries in pInfo
			break; // exit the loop

		// if we are still in the loop, current entry does not contain 
		// the process we are looking for, but there is at least one more entry in pInfo

		pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo)+ pInfo->NextEntryDelta); // obtain that new entry
	}
	ExFreePool(pBuffer); // on exit free the memory
	return 0; // and exit
}

The process enumeration procedure in this example takes one integer parameter PID (Process ID) and outputs the corresponded name as a character string of size 256 defined outside the function:

GetProcessName(PID);

As always, all that can only be done with Administrator rights and should work only in ring 0.

 
Alexander Volynkin ICQ #: 490622
Last update: 04.21.2004