WDM driver 和 user-mode 沟通:最佳实践和回调问题
WDM driver and user-mode communication: best practices and callback questions
我的 driver 的目的是将我收到的每个回调通知 user-mode 应用程序,并将我从这些已注册例程中获得的数据传递给它。然后 user-mode 应用程序将在屏幕上打印它从内核接收到的所有信息(这是一个简单的 Win32 控制台应用程序)。我目前注册了三个回调:PsSetCreateProcessNotifyRoutineEx
、PsSetCreateProcessNotifyRoutineEx2
和 PsSetLoadImageNotifyRoutine
。我想知道:
1)kernel-mode和[=50=之间通信的"best"方法是什么], 考虑到可以同时加载很多进程和很多图片?
2) 是应该每次调用都实现这样的方法还是应该我存储一些信息并每隔 0.5 秒将它们推送到 user-mode?
我实际上使用以下代码在 driver 中定义 IOCTL:
#define IOCTL_RECEIVE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_READ_DATA)
这是我的 DispatchDeviceControl 函数的代码:
NTSTATUS DispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
KIRQL CurrentIRQL = KeGetCurrentIrql();
DbgPrint("DispatchDeviceControl called at IRQL level: %d", CurrentIRQL);
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status = STATUS_SUCCESS;
PVOID buffer = Irp->AssociatedIrp.SystemBuffer;
ULONG inLength = irpsp->Parameters.DeviceIoControl.InputBufferLength;
ULONG outLength = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
ULONG returnLength = 0;
pMsg PMSG = NULL;
switch (irpsp->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_RECEIVE:
DbgPrint("IOCTL_RECEIVE message sent\n");
KeWaitForSingleObject(&kEvent, Executive, KernelMode, 0, NULL);
PMSG = (pMsg)ExInterlockedRemoveHeadList(&listhead, &spinlock);
// Copy data to the buffer
RtlCopyMemory((PCHAR)buffer, (PCHAR)PMSG, sizeof(Msg));
// Release the structure
ExFreePool(PMSG);
// Set returnLength
returnLength = sizeof(Msg);
break;
default:
status = STATUS_INVALID_PARAMETER;
}
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = returnLength;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
以及我的回调例程之一的代码:
_Use_decl_annotations_
VOID prcmPsCreateProcessNotifyRoutineEx2(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
{
// If process is exiting, just return immediately
if (CreateInfo == NULL)
return;
pMsg PMSG = (pMsg)ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(Msg), 'prcm');
if (PMSG == NULL)
{
DbgPrint("PsCreateProcessNotifyRoutineEx2: ERROR allocating Pool space for data to be sent to user mode\n");
return;
}
// Fill data
PMSG->ParentId = NULL;
PMSG->ProcessId = ProcessId;
PMSG->FullImageName = NULL;
ExInterlockedInsertHeadList(&listhead, (PLIST_ENTRY)PMSG, &spinlock);
KeSetEvent(&kEvent, 0, FALSE);
return;
}
最后是结构定义(我包含了一些示例值,但第一个元素显然是 LIST_ENTRY
:
typedef struct {
LIST_ENTRY listhead;
HANDLE ParentId;
HANDLE ProcessId;
PUNICODE_STRING FullImageName;
} Msg, *pMsg;
供您参考,我正确地调用了我的 DriverEntry
函数:
KeInitializeEvent(&kEvent, SynchronizationEvent, FALSE);
KeInitializeSpinLock(&spinlock);
InitializeListHead(&listhead);
在我的 user-mode 控制台应用程序中,我在主例程中创建了一个线程,并在其相关线程函数中不断检查 DeviceIoControl
的 return 值以便打印我从 driver.
3) 获得的实时信息有点 off-topic 但我认为是相关的:使用此代码,我错过 user-mode 中的某些通知是否正常?有人知道为什么吗?
OSR 写了一篇很棒的文章,The Inverted Call Model。这种系统的基本设计包括服务、驱动程序和两者之间的一些协议。它适用于单片驱动程序和分层驱动程序,只要求驱动程序能够接收设备控制操作。
我还想提一下,您同时使用了 PsSetCreateProcessNotifyRoutineEx 和 PsSetCreateProcessNotifyRoutineEx2。你应该使用一个或另一个。从 Windows 10, version 1703 Windows Server 2016 开始支持 PsSetCreateProcessNotifyRoutineEx2。它提供了更好的功能,所以我建议尽可能使用它。
您可以通过 MmGetSystemRoutinAddress 动态使用 PsSetCreateProcessNotifyRoutineEx2 来确定使用哪个,如果它不可用,则使用 PsSetCreateProcessNotifyRoutineEx。
我的 driver 的目的是将我收到的每个回调通知 user-mode 应用程序,并将我从这些已注册例程中获得的数据传递给它。然后 user-mode 应用程序将在屏幕上打印它从内核接收到的所有信息(这是一个简单的 Win32 控制台应用程序)。我目前注册了三个回调:PsSetCreateProcessNotifyRoutineEx
、PsSetCreateProcessNotifyRoutineEx2
和 PsSetLoadImageNotifyRoutine
。我想知道:
1)kernel-mode和[=50=之间通信的"best"方法是什么], 考虑到可以同时加载很多进程和很多图片?
2) 是应该每次调用都实现这样的方法还是应该我存储一些信息并每隔 0.5 秒将它们推送到 user-mode?
我实际上使用以下代码在 driver 中定义 IOCTL:
#define IOCTL_RECEIVE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_READ_DATA)
这是我的 DispatchDeviceControl 函数的代码:
NTSTATUS DispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
KIRQL CurrentIRQL = KeGetCurrentIrql();
DbgPrint("DispatchDeviceControl called at IRQL level: %d", CurrentIRQL);
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status = STATUS_SUCCESS;
PVOID buffer = Irp->AssociatedIrp.SystemBuffer;
ULONG inLength = irpsp->Parameters.DeviceIoControl.InputBufferLength;
ULONG outLength = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
ULONG returnLength = 0;
pMsg PMSG = NULL;
switch (irpsp->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_RECEIVE:
DbgPrint("IOCTL_RECEIVE message sent\n");
KeWaitForSingleObject(&kEvent, Executive, KernelMode, 0, NULL);
PMSG = (pMsg)ExInterlockedRemoveHeadList(&listhead, &spinlock);
// Copy data to the buffer
RtlCopyMemory((PCHAR)buffer, (PCHAR)PMSG, sizeof(Msg));
// Release the structure
ExFreePool(PMSG);
// Set returnLength
returnLength = sizeof(Msg);
break;
default:
status = STATUS_INVALID_PARAMETER;
}
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = returnLength;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
以及我的回调例程之一的代码:
_Use_decl_annotations_
VOID prcmPsCreateProcessNotifyRoutineEx2(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
{
// If process is exiting, just return immediately
if (CreateInfo == NULL)
return;
pMsg PMSG = (pMsg)ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(Msg), 'prcm');
if (PMSG == NULL)
{
DbgPrint("PsCreateProcessNotifyRoutineEx2: ERROR allocating Pool space for data to be sent to user mode\n");
return;
}
// Fill data
PMSG->ParentId = NULL;
PMSG->ProcessId = ProcessId;
PMSG->FullImageName = NULL;
ExInterlockedInsertHeadList(&listhead, (PLIST_ENTRY)PMSG, &spinlock);
KeSetEvent(&kEvent, 0, FALSE);
return;
}
最后是结构定义(我包含了一些示例值,但第一个元素显然是 LIST_ENTRY
:
typedef struct {
LIST_ENTRY listhead;
HANDLE ParentId;
HANDLE ProcessId;
PUNICODE_STRING FullImageName;
} Msg, *pMsg;
供您参考,我正确地调用了我的 DriverEntry
函数:
KeInitializeEvent(&kEvent, SynchronizationEvent, FALSE);
KeInitializeSpinLock(&spinlock);
InitializeListHead(&listhead);
在我的 user-mode 控制台应用程序中,我在主例程中创建了一个线程,并在其相关线程函数中不断检查 DeviceIoControl
的 return 值以便打印我从 driver.
3) 获得的实时信息有点 off-topic 但我认为是相关的:使用此代码,我错过 user-mode 中的某些通知是否正常?有人知道为什么吗?
OSR 写了一篇很棒的文章,The Inverted Call Model。这种系统的基本设计包括服务、驱动程序和两者之间的一些协议。它适用于单片驱动程序和分层驱动程序,只要求驱动程序能够接收设备控制操作。
我还想提一下,您同时使用了 PsSetCreateProcessNotifyRoutineEx 和 PsSetCreateProcessNotifyRoutineEx2。你应该使用一个或另一个。从 Windows 10, version 1703 Windows Server 2016 开始支持 PsSetCreateProcessNotifyRoutineEx2。它提供了更好的功能,所以我建议尽可能使用它。
您可以通过 MmGetSystemRoutinAddress 动态使用 PsSetCreateProcessNotifyRoutineEx2 来确定使用哪个,如果它不可用,则使用 PsSetCreateProcessNotifyRoutineEx。