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:
#ifndef _ENUMPROC_
#define _ENUMPROC_
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation,
SystemProcessorInformation,
SystemPerformanceInformation,
SystemTimeOfDayInformation,
SystemNotImplemented1,
SystemProcessesAndThreadsInformation,
SystemCallCounts,
SystemConfigurationInformation,
SystemProcessorTimes,
SystemGlobalFlag,
SystemNotImplemented2,
SystemModuleInformation,
SystemLockInformation,
SystemNotImplemented3,
SystemNotImplemented4,
SystemNotImplemented5,
SystemHandleInformation,
SystemObjectInformation,
SystemPagefileInformation,
SystemInstructionEmulationCounts,
SystemInvalidInfoClass1,
SystemCacheInformation,
SystemPoolTagInformation,
SystemProcessorStatistics,
SystemDpcInformation,
SystemNotImplemented6,
SystemLoadImage,
SystemUnloadImage,
SystemTimeAdjustment,
SystemNotImplemented7,
SystemNotImplemented8,
SystemNotImplemented9,
SystemCrashDumpInformation,
SystemExceptionInformation,
SystemCrashDumpStateInformation,
SystemKernelDebuggerInformation,
SystemContextSwitchInformation,
SystemRegistryQuotaInformation,
SystemLoadAndCallImage,
SystemPrioritySeparation,
SystemNotImplemented10,
SystemNotImplemented11,
SystemInvalidInfoClass2,
SystemInvalidInfoClass3,
SystemTimeZoneInformation,
SystemLookasideInformation,
SystemSetTimeSlipEvent,
SystemCreateSession,
SystemDeleteSession,
SystemInvalidInfoClass4,
SystemRangeStartInformation,
SystemVerifierInformation,
SystemAddVerifier,
SystemSessionProcessesInformation
} 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;
LPVOID pBuffer = NULL;
NTSTATUS Status;
do
{
pBuffer = ExAllocatePool (NonPagedPool, cbBuffer);
if (pBuffer == NULL)
{
return 1;
}
Status = ZwQuerySystemInformation(
SystemProcessesAndThreadsInformation, pBuffer, cbBuffer, NULL);
if (Status == STATUS_INFO_LENGTH_MISMATCH)
{
ExFreePool(pBuffer);
cbBuffer *= 2;
}
else if (!NT_SUCCESS(Status))
{
ExFreePool(pBuffer);
return 1;
}
}
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 (;;)
{
LPWSTR pszProcessName = pInfo->ProcessName.Buffer;
if (pszProcessName == NULL)
pszProcessName = L"NULL";
if (pInfo->ProcessId == PID)
{
wcstombs(ProcessName,pszProcessName,256);
break;
}
if (pInfo->NextEntryDelta == 0)
break;
pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo)+ pInfo->NextEntryDelta);
}
The complete code for process enumeration is in the following file presented below:
#include <ntddk.h>
#include <stdlib.h>
#include "enumproc.h"
char ProcessName[256];
typedef ULONG DWORD;
typedef VOID* LPVOID;
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#define SystemProcessesAndThreadsInformation 5
void GetProcessName(int PID, char* pProcessName[256])
{
ULONG cbBuffer = 0x8000;
LPVOID pBuffer = NULL;
NTSTATUS Status;
PSYSTEM_PROCESS_INFORMATION pInfo;
do
{
pBuffer = ExAllocatePool (NonPagedPool, cbBuffer);
if (pBuffer == NULL)
{
return 1;
}
Status = ZwQuerySystemInformation(
SystemProcessesAndThreadsInformation, pBuffer, cbBuffer, NULL);
if (Status == STATUS_INFO_LENGTH_MISMATCH)
{
ExFreePool(pBuffer);
cbBuffer *= 2;
}
else if (!NT_SUCCESS(Status))
{
ExFreePool(pBuffer);
return 1;
}
}
while (Status == STATUS_INFO_LENGTH_MISMATCH);
PSYSTEM_PROCESS_INFORMATION pInfo;
pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuffer;
for (;;)
{
LPWSTR pszProcessName = pInfo->ProcessName.Buffer;
if (pszProcessName == NULL)
pszProcessName = L"NULL";
if (pInfo->ProcessId == PID)
{
wcstombs(ProcessName,pszProcessName,256);
break;
}
if (pInfo->NextEntryDelta == 0)
break;
pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo)+ pInfo->NextEntryDelta);
}
ExFreePool(pBuffer);
return 0;
}
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.