/*
CheezMan.c - (c) 1998, Microsoft Corporation. All Rights Reserved.
1998 James M. Finnegan - Microsoft Systems Journal
Cheezy Process (and 16-bit task) manager for Windows 2000, to demonstrate
the use of PSAPI and VDMDBG in a manner that is similar to TASKMAN.EXE
*/
#include <windows.h>
#include <vdmdbg.h>
#include "psapi.h"
#include "resource.h"
VOID CenterWindow(HWND hWnd);
BOOL CALLBACK DlgProc(HWND hWnd,UINT uMessage,WPARAM wParam,LPARAM lParam);
void EnumerateProcesses();
HWND ghWnd;
int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine,
int nCmdShow)
{
static char szAppName[]="CheezMan";
DialogBox(hInstance,szAppName,0,DlgProc);
return 0;
}
VOID CenterWindow(HWND hWnd)
{
RECT rect;
WORD wWidth,
wHeight;
GetWindowRect(hWnd,&rect);
wWidth =GetSystemMetrics(SM_CXSCREEN);
wHeight=GetSystemMetrics(SM_CYSCREEN);
MoveWindow(hWnd,(wWidth/2) - ((rect.right - rect.left)/2),
(wHeight/2) - ((rect.bottom - rect.top) /2),
rect.right - rect.left,
rect.bottom - rect.top,
FALSE);
}
BOOL CALLBACK DlgProc(HWND hWnd,UINT uMessage,WPARAM wParam,LPARAM lParam)
{
static WORD wTimer;
switch(uMessage)
{
case WM_INITDIALOG:
ghWnd = hWnd;
// Create a timer to refresh the dialog's contents every
// 1000 milliseconds.
wTimer = SetTimer(hWnd, 1, 1000, NULL);
CenterWindow(hWnd);
PostMessage(hWnd,WM_TIMER, 0, 0L);
return FALSE;
break;
case WM_COMMAND:
switch(wParam)
{
case IDB_CLOSE:
PostMessage(hWnd,WM_CLOSE,0,0L);
break;
}
break;
case WM_TIMER:
// Clear the listbox
SendDlgItemMessage(hWnd,IDC_LIST1,LB_RESETCONTENT,0,0L);
// Refresh the process list
EnumerateProcesses();
break;
case WM_CLOSE:
KillTimer(hWnd,wTimer);
EndDialog(hWnd, FALSE);
break;
default:
return FALSE;
}
return TRUE;
}
/*
EnumerateWin16Processes - This is a callback function (referenced by
VDMEnumTaskWOWEx below) that is called in turn
for each 16-bit Windows task that is executing in
a Windows 2000 VDM.
*/
BOOL WINAPI EnumerateWin16Processes(DWORD dwThreadId, WORD hMod16, WORD hTask16,
PSZ pszModName, PSZ pszFileName, LPARAM lpUserDefined)
{
char szTemp[80];
// Indent the task name, to visually show that it is a "child" of the
// process listed above it
wsprintf(szTemp," %s",pszModName);
SendDlgItemMessage(ghWnd,IDC_LIST1,LB_ADDSTRING,0,(LONG)(LPSTR)szTemp);
// Return FALSE to continue enumeration
return FALSE;
}
void PrintProcessName(DWORD processID)
{
char szProcessName[MAX_PATH] = "unknown";
char szTemp[80];
// Get a handle to the passed-in process ID
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE, processID );
// Get the process name
if(hProcess)
{
HMODULE hMod;
DWORD cbNeeded;
if(EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded))
{
GetModuleBaseName(hProcess, hMod, szProcessName,
sizeof(szProcessName));
}
}
// Print the process name and ID
wsprintf(szTemp, "%s (PID: %u)", szProcessName, processID );
SendDlgItemMessage(ghWnd,IDC_LIST1,LB_ADDSTRING,0,(LONG)(LPSTR)szTemp);
// If the process name is NTVDM.EXE, try walking it to see if it is
// a WOW box. The callback will not be called if NTVDM is not WOW
if(!stricmp(szProcessName,"ntvdm.exe"))
{
// VDMEnumTaskWOWEx() is NOT documented in the VDMDBG.HLP
// file -- but it is in VDMDBG.H. Explanations there make its use
// clear (and documented!) Basically, you need modname and filename
// as additional parameters in the callback.
VDMEnumTaskWOWEx(processID, EnumerateWin16Processes, 0);
}
CloseHandle(hProcess);
}
/*
EnumerateProcesses - Enumerates all process IDs currently running within
a system. The print function (above) will deal with 16-bit
apps.
*/
void EnumerateProcesses()
{
DWORD aProcesses[1024], // Array of process IDs
cbNeeded, // Byte count returned...
cProcesses; // The number of 'em obtained
unsigned int i;
// Get a list of all current PIDs running
if(!EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded))
return;
// Get the count of PIDs in the array
cProcesses = cbNeeded / sizeof(DWORD);
// Print the name and process identifier for each process
for(i = 0; i < cProcesses; i++)
PrintProcessName(aProcesses[i]);
}
Figure 4 Process Structure APIs
API
|
Documented in Windows NT 4.0
|
Documented in Windows 2000
|
Examined in the Nerditorium
|
PsCreateSystemThread
|
|
|
|
PsGetCurrentProcess
|
|
|
|
PsGetCurrentProcessId
|
|
|
|
PsGetCurrentThread
|
|
|
|
PsGetCurrentThreadId
|
|
|
|
PsGetVersion
|
|
|
|
PsSetCreateProcessNotifyRoutine
|
|
|
|
PsSetCreateThreadNotifyRoutine
|
|
|
|
PsSetLoadImageNotifyRoutine
|
|
|
|
PsTerminateSystemThread
|
|
|
|
Figure 5 Callback Function Prototypes
typedef VOID (*PCREATE_PROCESS_NOTIFY_ROUTINE)(
IN HANDLE ParentId,
IN HANDLE ProcessId,
IN BOOLEAN Create
);
typedef VOID (*PCREATE_THREAD_NOTIFY_ROUTINE)(
IN HANDLE ProcessId,
IN HANDLE ThreadId,
IN BOOLEAN Create
);
typedef VOID (*PLOAD_IMAGE_NOTIFY_ROUTINE)(
IN PUNICODE_STRING FullImageName,
IN HANDLE ProcessId, // pid into which image is being mapped
IN PIMAGE_INFO ImageInfo
);
Figure 6 ProcView.c
/*
ProcView.C - 1998 James M. Finnegan - Microsoft Systems Journal
Copyright (c)1998 Microsoft Corporation. All Rights Reserved.
This module implements the kernel calls required to utilize the
Process Structure APIs.
** Danger ** Don't try to unload this driver, since the Process Structure
callbacks cannot be unregistered. If the callbacks disappear, evil things
will happen!
*/
#include "ntddk.h"
#include <stdio.h>
#define FILE_DEVICE_UNKNOWN 0x00000022
#define IOCTL_UNKNOWN_BASE FILE_DEVICE_UNKNOWN
#define IOCTL_PROCVIEW_GET_PROCINFO CTL_CODE(IOCTL_UNKNOWN_BASE, 0x0800,
METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define IOCTL_PROCVIEW_GET_THREADINFO CTL_CODE(IOCTL_UNKNOWN_BASE, 0x0801,
METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define IOCTL_PROCVIEW_GET_IMAGEINFO CTL_CODE(IOCTL_UNKNOWN_BASE, 0x0802,
METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
// Boilerplate KM device functions...
void ProcViewUnloadDriver(PDRIVER_OBJECT DriverObject);
NTSTATUS ProcViewDispatchCreateClose(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp);
NTSTATUS ProcViewDispatchIoctl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
// Our Process Structure callbacks!
VOID ProcViewProcessCallback(IN HANDLE ParentId, IN HANDLE ProcessId,
IN BOOLEAN Create);
VOID ProcViewThreadCallback(IN HANDLE ParentId, IN HANDLE ProcessId,
IN BOOLEAN Create);
VOID ProcViewImageCallback(IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId,
IN PIMAGE_INFO ImageInfo);
// Structure for Process callback information
typedef struct _CallbackInfo
{
HANDLE ParentId;
HANDLE ProcessId;
BOOLEAN Create;
}CALLBACK_INFO, *PCALLBACK_INFO;
// Structure for Thread callback information
typedef struct _CallbackThreadInfo
{
HANDLE ProcessId;
HANDLE ThreadId;
BOOLEAN Create;
}CALLBACK_THREAD_INFO, *PCALLBACK_THREAD_INFO;
// Structure for image loading information
typedef struct _CallbackImageInfo
{
UCHAR ImageNameA[255];
HANDLE IProcessId;
IMAGE_INFO ImageInfo;
}CALLBACK_IMAGE_INFO, *PCALLBACK_IMAGE_INFO;
// Private storage for us to squirrel things away...
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT DeviceObject;
HANDLE ProcessHandle;
PKEVENT ProcessEvent;
HANDLE ThreadHandle;
PKEVENT ThreadEvent;
HANDLE ImageHandle;
PKEVENT ImageEvent;
HANDLE PParentId;
HANDLE PProcessId;
BOOLEAN PCreate;
HANDLE TProcessId;
HANDLE TThreadId;
BOOLEAN TCreate;
UCHAR ImageNameA[255];
HANDLE IProcessId;
IMAGE_INFO ImageInfo;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
PDEVICE_OBJECT gpDeviceObject;
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
NTSTATUS ntStatus;
UNICODE_STRING uszDriverString;
UNICODE_STRING uszDeviceString;
UNICODE_STRING uszProcessEventString,
uszThreadEventString,
uszImageEventString;
PDEVICE_OBJECT pDeviceObject;
PDEVICE_EXTENSION extension;
// Point uszDriverString at the driver name
RtlInitUnicodeString(&uszDriverString, L"\\Device\\ProcView");
// Create and initialize device object
ntStatus = IoCreateDevice(DriverObject,
sizeof(DEVICE_EXTENSION),
&uszDriverString,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&pDeviceObject);
if(ntStatus != STATUS_SUCCESS)
return ntStatus;
// Assign extension variable...
extension = pDeviceObject->DeviceExtension;
// Point uszDeviceString at the device name
RtlInitUnicodeString(&uszDeviceString, L"\\DosDevices\\ProcView");
// Create symbolic link to the user-visible name
ntStatus = IoCreateSymbolicLink(&uszDeviceString, &uszDriverString);
if(ntStatus != STATUS_SUCCESS)
{
// Delete device object if not successful
IoDeleteDevice(pDeviceObject);
return ntStatus;
}
// Assign global pointer to the device object for use by the callback
// functions...
gpDeviceObject = pDeviceObject;
// Load structure to point to IRP handlers...
DriverObject->DriverUnload = ProcViewUnloadDriver;
DriverObject->MajorFunction[IRP_MJ_CREATE] = ProcViewDispatchCreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = ProcViewDispatchCreateClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ProcViewDispatchIoctl;
// Create events for user-mode processes to monitor
RtlInitUnicodeString(&uszProcessEventString,
L"\\BaseNamedObjects\\ProcViewProcessEvent");
extension->ProcessEvent = IoCreateNotificationEvent (&uszProcessEventString,
&extension->ProcessHandle);
RtlInitUnicodeString(&uszThreadEventString,
L"\\BaseNamedObjects\\ProcViewThreadEvent");
extension->ThreadEvent = IoCreateNotificationEvent (&uszThreadEventString,
&extension->ThreadHandle);
RtlInitUnicodeString(&uszImageEventString,
L"\\BaseNamedObjects\\ProcViewImageEvent");
extension->ImageEvent = IoCreateNotificationEvent (&uszImageEventString,
&extension->ImageHandle);
// Clear 'em out!
KeClearEvent(extension->ProcessEvent);
KeClearEvent(extension->ThreadEvent);
KeClearEvent(extension->ImageEvent);
// Set up callback routines...
ntStatus = PsSetCreateProcessNotifyRoutine(ProcViewProcessCallback, 0);
ntStatus = PsSetCreateThreadNotifyRoutine(ProcViewThreadCallback);
ntStatus = PsSetLoadImageNotifyRoutine(ProcViewImageCallback);
// Return success
return ntStatus;
}
NTSTATUS ProcViewDispatchCreateClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information=0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return(STATUS_SUCCESS);
}
VOID ProcViewProcessCallback(IN HANDLE ParentId, IN HANDLE ProcessId,
IN BOOLEAN Create)
{
PDEVICE_EXTENSION extension;
// Assign extension variable...
extension = gpDeviceObject->DeviceExtension;
// Slam current values into device extension. User-mode apps will pick it
// up using DeviceIoControl calls...
extension->PParentId = ParentId;
extension->PProcessId = ProcessId;
extension->PCreate = Create;
// Pulse the event so any user-mode apps listening will know something
// interesting has happened. Sadly, user-mode apps can't reset a KM
// event, which is why we're pulsing it here...
KeSetEvent(extension->ProcessEvent, 0, FALSE);
KeClearEvent(extension->ProcessEvent);
}
VOID ProcViewThreadCallback(IN HANDLE ProcessId, IN HANDLE ThreadId,
IN BOOLEAN Create)
{
PDEVICE_EXTENSION extension;
// Assign extension variable...
extension = gpDeviceObject->DeviceExtension;
// Slam current values into device extension. User-mode apps will pick it
// up using DeviceIoControl calls...
extension->TProcessId = ProcessId;
extension->TThreadId = ThreadId;
extension->TCreate = Create;
// Pulse the event so any user-mode apps listening will know something
// interesting has happened. Sadly, user-mode apps can't reset a KM
// event, which is why we're pulsing it here...
KeSetEvent(extension->ThreadEvent, 0, FALSE);
KeClearEvent(extension->ThreadEvent);
}
VOID ProcViewImageCallback(IN PUNICODE_STRING FullImageName, IN HANDLE ProcessId,
IN PIMAGE_INFO ImageInfo)
{
PDEVICE_EXTENSION extension;
// Assign extension variable...
extension = gpDeviceObject->DeviceExtension;
// Slam current values into device extension. User-mode apps will pick it
// up using DeviceIoControl calls...
extension->IProcessId = ProcessId;
RtlCopyMemory(&extension->ImageInfo, ImageInfo, sizeof(IMAGE_INFO));
// Hokey method for converting a KM Unicode string into something
// easily printable by our user-mode app :-)
sprintf(extension->ImageNameA,"%S",FullImageName->Buffer);
// Pulse the event so any user-mode apps listening will know something
// interesting has happened. Sadly, user-mode apps can't reset a KM
// event, which is why we're pulsing it here...
KeSetEvent(extension->ImageEvent, 0, FALSE);
KeClearEvent(extension->ImageEvent);
}
NTSTATUS ProcViewDispatchIoctl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
PDEVICE_EXTENSION extension = DeviceObject->DeviceExtension;
PCALLBACK_INFO pCallbackInfo;
PCALLBACK_THREAD_INFO pCallbackThreadInfo;
PCALLBACK_IMAGE_INFO pCallbackImageInfo;
// These IOCTL handlers simply rip the current data out of the device
// extension structure. I assume that the user-mode app is calling this
// because the KM event was signaled in the callbacks above. No provisions
// are made here to ensure that the same data isn't picked up twice, or
// that data isn't missed. A more full-fledged impelmentation should build
// a queue or list of events as they occur, so user-mode gets 'em all.
switch(irpStack->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_PROCVIEW_GET_PROCINFO:
if(irpStack->Parameters.DeviceIoControl.OutputBufferLength >=
sizeof(CALLBACK_INFO))
{
pCallbackInfo = Irp->AssociatedIrp.SystemBuffer;
pCallbackInfo->ParentId = extension->PParentId;
pCallbackInfo->ProcessId = extension->PProcessId;
pCallbackInfo->Create = extension->PCreate;
ntStatus = STATUS_SUCCESS;
}
break;
case IOCTL_PROCVIEW_GET_THREADINFO:
if(irpStack->Parameters.DeviceIoControl.OutputBufferLength >=
sizeof(CALLBACK_INFO))
{
pCallbackThreadInfo = Irp->AssociatedIrp.SystemBuffer;
pCallbackThreadInfo->ProcessId = extension->TProcessId;
pCallbackThreadInfo->ThreadId = extension->TThreadId;
pCallbackThreadInfo->Create = extension->TCreate;
ntStatus = STATUS_SUCCESS;
}
break;
case IOCTL_PROCVIEW_GET_IMAGEINFO:
if(irpStack->Parameters.DeviceIoControl.OutputBufferLength >=
sizeof(CALLBACK_IMAGE_INFO))
{
pCallbackImageInfo = Irp->AssociatedIrp.SystemBuffer;
strcpy(pCallbackImageInfo->ImageNameA, extension->ImageNameA);
pCallbackImageInfo->IProcessId = extension->IProcessId;
RtlCopyMemory(&pCallbackImageInfo->ImageInfo,
&extension->ImageInfo, sizeof(IMAGE_INFO));
ntStatus = STATUS_SUCCESS;
}
break;
default:
break;
}
Irp->IoStatus.Status = ntStatus;
// Set # of bytes to copy back to user-mode...
if(ntStatus == STATUS_SUCCESS)
Irp->IoStatus.Information =
irpStack->Parameters.DeviceIoControl.OutputBufferLength;
else
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return ntStatus;
}
void ProcViewUnloadDriver(IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING uszDeviceString;
IoDeleteDevice(DriverObject->DeviceObject);
RtlInitUnicodeString(&uszDeviceString, L"\\DosDevices\\ProcView");
IoDeleteSymbolicLink(&uszDeviceString);
}