简单的回环筛选器驱动程序





4.00/5 (4投票s)
本文档面向首次接触 Windows 内核过滤驱动程序并希望通过一个简单的示例(附带源代码)进行试验的开发人员。
引言
本文档为 Windows 内核模式过滤驱动程序开发提供了一个快速入门指南。
本文介绍的示例过滤驱动程序使用直通机制,即将 I/O 请求转发到更低级别的驱动程序,而无需进行任何处理。
背景
本文档面向首次编写 Windows 内核过滤驱动程序并希望试验此驱动程序的开发人员,该驱动程序基于已编写的“Simple WDM Loopback Driver”。
Using the Code
与其他驱动程序一样,过滤驱动程序也有一个初始化入口点 DriverEntry。
1 DriverEntry 例程
我们不会过多讨论函数参数,因为在“Simple WDM Loopback Driver”一文中已经讨论过。请参考“loopback.aspx”。
1.1 设置分派例程
实际上,为特定功能/类驱动程序开发的过滤驱动程序不是由我们编写的。大多数情况下是第三方驱动程序。考虑到这一点,我们需要为所有操作指定直通功能,然后覆盖我们感兴趣的那个。
/*
 * Set Dispatch entry points to pass through
 */
for (ulIndex = 0; ulIndex <= IRP_MJ_MAXIMUM_FUNCTION;  ulIndex++) {
	pDriverObject->MajorFunction[ulIndex] = LBKFltPassThrough;
}
/*
 * Override entry points we are interested in	
 */
pDriverObject->MajorFunction[IRP_MJ_CREATE]	= LBKFltCreate;
pDriverObject->MajorFunction[IRP_MJ_CLOSE]	= LBKFltClose;
pDriverObject->MajorFunction[IRP_MJ_WRITE]	= LBKFltWrite;
pDriverObject->MajorFunction[IRP_MJ_READ]	= LBKFltRead;
然后,我们需要实际构建设备堆栈。
1.2 创建过滤设备
首先,我们需要在 DriverEntry 中创建一个设备对象,例如 LOOPBACKFLT。创建无名设备也可以。然后,我们需要使用以下替代方法将我们的设备堆叠在 LOOPBACK 设备之上。
以下代码是下面描述的所有 3 种替代方法的通用代码
/*
 * Form the Device Name and symbolic name
 */
RtlInitUnicodeString(&devNameFlt, L"\\Device\\LOOPBACKFLT");
RtlInitUnicodeString(&symLinkNameFlt, L\\DosDevices\\LBKFLT);
 
RtlInitUnicodeString(&devName, L"\\Device\\LOOPBACK");
/*
 * Now create the device
 */
status = IoCreateDevice( pDriverObject,
			 sizeof(DEVICE_EXTENSION),
			 &devNameFlt,
			 FILE_DEVICE_UNKNOWN,
			 0, FALSE,
			 &pDevObj );
if (!NT_SUCCESS(status)){
	goto cleanup_failure;
}
1.3 附加到 Loopback 设备
如前所述,有 3 种替代方法,如下所示……
1.3.1 替代方法 1
IoAttachDevice 易于使用,但有一个限制,即要过滤的设备必须命名。
/*
 * Attach device to loopback device stack 
 */
/*---------------------- Alternative Start----------------------------------------------*/
/*---------------------- Alternative 1--------------------------------------------------*/
status = IoAttachDevice(pDevObj, &devName, &(pDevExt->pTargetDeviceObject));
if(status != STATUS_SUCCESS){
	DbgPrint("IoAttachDevice failed with error = 0x%0x\n", status);
	goto cleanup_failure;
}
1.3.2 替代方法 2
通过此替代方法,我们需要通过指定设备名称给 IoGetDeviceObjectPointer 来获取设备指针。如果请求的对象访问权限可以获得,IoGetDeviceObjectPointer 例程会返回指向命名设备对象堆栈顶部的对象以及指向相应文件对象的指针。在这种情况下,由于 LOOPBACK 是设备堆栈中最顶层的设备,它将返回指向 LOOPBACK 设备的指针。
然后,我们需要使用 IoAttachDeviceToDeviceStack 将我们的设备附加到设备堆栈。
/*---------------------- Alternative 2--------------------------------------------------*/
/*
 * Get Device Pointer
 */	
status = IoGetDeviceObjectPointer( &devName, FILE_ALL_ACCESS, &pFileObject, &(pLBKdev));	
if(status != STATUS_SUCCESS){
	DbgPrint("IoGetDeviceObjectPointer failed with error = 0x%0x\n", status);
	goto cleanup_failure;
}
ASSERT(pLBKdev);	
DbgPrint("%wZ Device Object Pointer 0x%0x\n", &devName, pLBKdev);
ObReferenceObject(pLBKdev);
pDevExt->pTargetDeviceObject = IoAttachDeviceToDeviceStack( pDevObj, pLBKdev);
if(pDevExt->pTargetDeviceObject){
	DbgPrint("Attached to loopback device successfully\n");
	status = STATUS_UNSUCCESSFUL;
	goto cleanup_failure;
}else{
	 DbgPrint("Attaching to loopback device failed\n");
}
1.3.3 替代方法 3
此方法与替代方法 2 相同,只是它使用 IoAttachDeviceToDeviceStackSafe 而不是 IoAttachDeviceToDeviceStack。
与 IoAttachDeviceToDeviceStack 不同,IoAttachDeviceToDeviceStackSafe 有一个额外的参数 AttachedToDeviceObject,过滤驱动程序使用它来传递 *SourceDevice* 对象 AttachedToDeviceObject 字段的地址。IoAttachDeviceToDeviceStackSafe 在持有 I/O 系统数据库锁的同时更新此字段。由于它持有此锁,IoAttachDeviceToDeviceStackSafe 避免了可能发生的竞态条件,即如果 *SourceDevice* 对象在 AttachedToDeviceObject 字段更新之前收到了 IRP。
我倾向于使用替代方法 3。
/*---------------------- Alternative 3------------------------------------------------*/
/*
 * Get Device Pointer
 */	
status = IoGetDeviceObjectPointer
	( &devName,  FILE_ALL_ACCESS,  &pFileObject,  &(pLBKdev)  );	
if(status != STATUS_SUCCESS){
	DbgPrint("IoGetDeviceObjectPointer failed with error = 0x%0x\n", status);
	goto cleanup_failure;
}
ASSERT(pLBKdev);	
DbgPrint("%wZ Device Object Pointer 0x%0x\n", &devName, pLBKdev);
ObReferenceObject(pLBKdev);
	
status = IoAttachDeviceToDeviceStackSafe(pDevObj, pLBKdev, &pDevExt->pTargetDeviceObject);
if(status != STATUS_SUCCESS){
	DbgPrint("IoGetDeviceObjectPointer failed with error = 0x%0x\n", status);
	goto cleanup_failure;
}
IoAttachDeviceToDeviceStack、IoAttachDeviceToDeviceStackSafe 在即插即用驱动程序中使用,其中从 AddDevice 入口点获取的是较低的设备对象而不是设备名称。
1.4 DeviceTree 视图
加载 Loopback 驱动程序和 Loopback 过滤驱动程序后,您可以使用 DeviceTree 查看设备堆栈,如下所示。
 
 
1.5 WinObj 视图
为 LOOPBACKFLT 创建符号链接是可选的。但是在示例代码中,我创建了它。如果您的用户模式需要与过滤驱动程序通信,则需要它。
加载 Loopback 驱动程序和 Loopback 过滤驱动程序后,您可以使用 WinObj 查看符号链接,如下所示。
 
 
2 通用直通分派实现
它将 IO 请求传递给堆栈中的较低设备,而无需任何修改。
IoSkipCurrentIrpStackLocation 使较低的驱动程序使用过滤驱动程序收到的相同堆栈位置。
NTSTATUS LBKFltPassThrough( IN PDEVICE_OBJECT  pDevObj, IN PIRP  pIrp)
{
	PDEVICE_EXTENSION   pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension;
	DbgPrint("LBFlltPassThrough was called \n");
	/*
	 * Let below driver use our stack location
	 */
	IoSkipCurrentIrpStackLocation(pIrp);
	/*
	 * send IRP to lower target device
	 */
	return IoCallDriver(pDevExt->pTargetDeviceObject, pIrp);
}
Create 分派例程实现为直通,除了打印一些调试信息。
以下将介绍过滤驱动程序中处理 IRP 的另外几种方式。
3 Write 直通分派实现
Write 分派实现再次类似于直通,除了从 IO_STACK_LOCATION 捕获一些信息。但是,在此代码中,我们没有使用捕获的信息。
我们使用 NULL 终止字符串来测试读取和写入。如果要测试写入二进制数据,请删除 STRING_TESET_MODE 定义。否则,它可能会导致系统崩溃。
NTSTATUS LBKFltWrite(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp) 
{	
	PDEVICE_EXTENSION   pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension;
	PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp );
	PREADWRITE_IO_CONTEXT Context = NULL;
	DbgPrint("LBKFltWrite was called \n");
	Context = ExAllocatePoolWithTag(NonPagedPool, 
		sizeof(READWRITE_IO_CONTEXT), LBKFLT_TAG_NPAGED);
	if(NULL == Context){
		DbgPrint("Memory allocation failure for read io context \n");
		pIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
		pIrp->IoStatus.Information = 0;
		IoCompleteRequest(pIrp, IO_NO_INCREMENT);
		return STATUS_INSUFFICIENT_RESOURCES;
	}
	Context->liOffset = pIrpStack->Parameters.Write.ByteOffset;
	Context->ulLength = pIrpStack->Parameters.Write.Length;
#ifdef STRING_TESET_MODE
	/*
	 * Only DO_BUFFERED_IO is supported
	 */
	DbgPrint("Data = %s\n", pIrp->AssociatedIrp.SystemBuffer);
#endif /* STRING_TESET_MODE */
	/*
	 * We are not setting completion routine here to check the io completion status
	 * There are chances that lower drivers fail the request
	 */
	IoSkipCurrentIrpStackLocation(pIrp);
	ExFreePoolWithTag(Context, LBKFLT_TAG_NPAGED);
	return IoCallDriver(pDevExt->pTargetDeviceObject, pIrp);
}
4 Read 直通分派实现
Read 分派实现再次类似于直通,因为我们不修改 IO 请求,除了从 IO_STACK_LOCATION 捕获一些信息。但是,在此代码中,我们没有使用捕获的信息。
这是读取操作,我们无法在分派例程中捕获读取的数据。当较低的驱动程序完成请求处理时,应该捕获它。为此,我们使用 IoSetCompletionRoutine 设置了一个完成例程。当较低的驱动程序完成请求时,将调用完成例程。
通过此示例代码,我解释了 IO 上下文如何从分派例程传递到完成例程。
该代码基本上是为了理解目的而编写的,它不处理 IRP 挂起和其他一些情况。
NTSTATUS LBKFltRead(IN PDEVICE_OBJECT	pDevObj, IN PIRP pIrp) 
{
	PDEVICE_EXTENSION   pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension;
	PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp );
	PREADWRITE_IO_CONTEXT Context = NULL;
	DbgPrint("LBKFltRead was called \n");
	Context = ExAllocatePoolWithTag
		(NonPagedPool, sizeof(READWRITE_IO_CONTEXT), LBKFLT_TAG_NPAGED);
	if(NULL == Context){
		DbgPrint("Memory allocation failure for read io context \n");
		pIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
		pIrp->IoStatus.Information = 0;
		IoCompleteRequest(pIrp, IO_NO_INCREMENT);
		return STATUS_INSUFFICIENT_RESOURCES;
	}
	Context->liOffset = pIrpStack->Parameters.Read.ByteOffset;
	Context->ulLength = pIrpStack->Parameters.Read.Length;
	IoCopyCurrentIrpStackLocationToNext(pIrp);
	IoSetCompletionRoutine( pIrp,
							ReadIoCompletion,
							Context,
							TRUE,//InvokeOnSuccess
							TRUE, // InvokeOnError
							TRUE // InvokeOnCancel
							);
	return IoCallDriver(pDevExt->pTargetDeviceObject, pIrp);
}
NTSTATUS ReadIoCompletion( IN PDEVICE_OBJECT  pDevObj, IN PIRP  pIrp, IN PVOID  Context )
{
	PREADWRITE_IO_CONTEXT pCtx = (PREADWRITE_IO_CONTEXT)Context;
	UNREFERENCED_PARAMETER(pDevObj);
	if(NT_SUCCESS(pIrp->IoStatus.Status)){
		
		/*
		 * Log the IO context or do processing if required for the context
		 * We are not doing any processing here
		 */
		ExFreePoolWithTag(pCtx, LBKFLT_TAG_NPAGED);
#ifdef STRING_TESET_MODE
		/*
		 * Only DO_BUFFERED_IO is supported
		 */
		DbgPrint("Data = %s\n", pIrp->AssociatedIrp.SystemBuffer);
#endif /* STRING_TESET_MODE */
	}
	return STATUS_SUCCESS;
}
如何安装和使用示例过滤器
提供的示例代码是使用 DDK 控制台构建的。如果安装了 DDK,请打开 DDK 构建控制台,然后导航到项目HOME目录。使用 build –cEZ 构建驱动程序。
同时,构建 Simple WDM Loopback Driver 和测试应用程序。
驱动程序构建完成后,应将其复制到 $WINDOWS$\system32\drivers 文件夹。
按照“Simple WDM Loopback Driver”文章 "loopback.aspx" 中的说明安装 Loopback 驱动程序。
或者
如果您不想在测试驱动程序时重新启动计算机,请运行以下命令。
- 要安装 loopback 驱动程序,请运行sc create loopback binPath= %systemroot%\system32\drivers\loopback.sys type= kernel 
- 要启动 loopback 驱动程序,请运行sc start loopback 
- 要安装 loopback 过滤驱动程序,请运行sc create lbkflt binPath= %systemroot%\system32\drivers\lbkflt.sys type= kernel 
- 要启动 loopback 驱动程序sc start lbkflt 
- 现在驱动程序已准备好与用户应用程序通信。运行用户应用程序 Testor.exe。它将在“Simple WDM Loopback Driver”示例源代码的 Testor 可执行文件夹中。您将在 DebugView中看到以下输出。DbgView 快照 
- 要停止 LBKFLT 驱动程序sc stop lbkflt 
- 要停止 loopback 驱动程序sc stop loopback 
使用的工具
- DeviceTree- http://www.osronline.com/article.cfm?article=97
- DbgView- http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx
- WinObj- http://technet.microsoft.com/en-us/sysinternals/bb896657.aspx
历史
这是第一个版本。我将在未来发布更多 Windows 示例。


