过滤驱动程序总结

来源:互联网 发布:淘宝店铺橱窗位在哪 编辑:程序博客网 时间:2024/06/16 21:22

过滤驱动程序



    过滤驱动程序可以修改已有驱动的功能,也可以对数据进行过滤加密。WDM驱动需要通过注册表记录


指定加裁的过滤驱动,OS会读取这些值完成加载,其可以是高层过滤,也可以是低层过滤。而NT较为灵


活,不用设置注册表,直接在内存中寻找设备对象,然后自行创建过滤驱动并将自己附加在这个驱动之


上。
    过滤驱动的入口函数需要将所有的IRP都设置派遣例程,因为过滤驱动要保证所有上层传递下来的


IRP都能够接收到。如果想修改某个IRP的处理,例如键盘读取输入的键盘扫描码,可以在派遣例程中设


置完成例程,在完成例程中修改IRP的处理。
    一般,将介于FDO与PDO间的过滤驱动程序称为低层过滤驱动程序,而将附加在FDO上面的称为高层过


滤驱动程序。
1、WDM式过滤示例
    文件过滤驱动,它将挂载在磁盘驱动上,它将所有发往磁盘驱动的IRP全部拦截。一般都是将上层驱


动程序传进来的IRP进行拦截并不做处理,而转发给底层驱动程序,称为Pass-Through。特殊IRP,特殊


处理。将需要过滤的操作放在完成例程中。
wps_clip_image-8752
图示 过滤驱动程序 P507
 代码


示例代码 P507
wps_clip_image-1252
图示U盘设备驱动拓扑 P509
    对于过滤驱动程序的AddDevice例程,需要创建一个过滤设备对象,然后把这个对象附加在设备栈上


,并用设备扩展记录仅低于本层的驱动对象地址。对于磁盘过滤的关键在于IRP_MJ_SCSI这个IRP的捕获


,DISK.sys与USBSTOR.sys间传递的就是标准SCSI指令。如果想让设备变成只读,只要将SCSI命令中的


SCSIOP_WRITE请求设置为失败;还有另外一种方法是在SCSIOP_MODE_SENSE请求时,设置 


MODE_DSP_WRITE_PROTECT位。
2、NT式过滤驱动
如将键盘中的CapsLock改成Ctrl,示例如下。
示例代码(Link)
========

Windows驱动开发WDM



之前用的驱动例子是一个功能型驱动,只不过它操作的是一个虚拟设备。这个驱动创建的FDO(功能设备


对象)是附在虚拟总线驱动创建的虚拟PDO之上的。这次来介绍一下不同于功能型驱动的过滤驱动。过滤


驱动可以在功能型驱动的上面,称之为上层过滤驱动,或者高层,反正就这个意思。过滤驱动在功能型


驱动下面,称之为下层过滤驱动。看示意图:


从名字上就可以知道过滤驱动是干啥用的。就是起过滤作用。比如:


1. 可以对写入硬盘的数据做加密,然后读取的时候解密,这样对于用户来说,根本不知道有加密解密的


过程,然后存在硬盘上的数据是加密的。


2. 可以对已有驱动做一些扩展,或者改变已有驱动的功能。比如已有驱动一次只能写1024字节的数据,


那么过滤驱动可以扩展到任何长度,然后分段调用已有驱动就是了。


我们来尝试给之前用的驱动加个过滤驱动。这个过滤驱动代码,我是从前面的驱动copy过来的,然后修


改一下。


其实过滤驱动跟功能型驱动从代码上看也没有太多不同,需要注意的是:


1. 因为过滤驱动是附在FDO上的,那么通常过滤驱动都不需要一个名字,因为caller不会直接调用过滤


驱动;


2. 因为不知道下层驱动是缓冲模式还是直接模式操作内存,那么通常过滤驱动会同时支持缓冲和直接方


式。


看一下AddDevice里面创建过滤驱动设备对象的代码:


01.NTSTATUS CreateFDO(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT pdo)  
02.{  
03.    NTSTATUS status;  
04.    PDEVICE_OBJECT fido;  
05.  
06.    //创建FDO(Function Device Object)   
07.    status = IoCreateDevice(  
08.        DriverObject,  
09.        sizeof(DEVICE_EXTENSION),  
10.        NULL,//过滤驱动,无需名字   
11.        FILE_DEVICE_UNKNOWN,  
12.        0,  
13.        FALSE,  
14.        &fido);  
15.    if( !NT_SUCCESS(status))  
16.        return status;  
17.  
18.    KdPrint(("Create filter device\n"));  
19.  
20.    PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fido->DeviceExtension;  
21.    pdx->fdo = fido;  
22.    //将FDO附加在PDO上面,并且将Extension中的NextStackDevice指向FDO的下层设备。如果PDO上


面有过滤驱动的话,NextStackDevice就是过滤驱动,如果没有就是PDO。   
23.    pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fido, pdo);  
24.  
25.    if (pdx->NextStackDevice == NULL)  
26.    {  
27.        KdPrint(("attach failed\n"));  
28.        IoDeleteDevice(fido);  
29.        return STATUS_DEVICE_REMOVED;  
30.    }  
31.      
32.  
33.    fido->Flags |= DO_BUFFERED_IO | DO_DIRECT_IO| DO_POWER_PAGABLE;//   
34.    fido->Flags &= ~DO_DEVICE_INITIALIZING;//将Flag上的DO_DEVICE_INITIALIZING位清零,保


证设备初始化完毕,必须的。   
35.  
36.    return STATUS_SUCCESS;  
37.}  
NTSTATUS CreateFDO(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT pdo)
{
NTSTATUS status;
PDEVICE_OBJECT fido;


//创建FDO(Function Device Object)
status = IoCreateDevice(
DriverObject,
sizeof(DEVICE_EXTENSION),
NULL,//过滤驱动,无需名字
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&fido);
if( !NT_SUCCESS(status))
return status;


KdPrint(("Create filter device\n"));


PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fido->DeviceExtension;
pdx->fdo = fido;
//将FDO附加在PDO上面,并且将Extension中的NextStackDevice指向FDO的下层设备。如果PDO


上面有过滤驱动的话,NextStackDevice就是过滤驱动,如果没有就是PDO。
pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fido, pdo);


if (pdx->NextStackDevice == NULL)
{
KdPrint(("attach failed\n"));
IoDeleteDevice(fido);
return STATUS_DEVICE_REMOVED;
}



fido->Flags |= DO_BUFFERED_IO | DO_DIRECT_IO| DO_POWER_PAGABLE;//
fido->Flags &= ~DO_DEVICE_INITIALIZING;//将Flag上的DO_DEVICE_INITIALIZING位清零,保


证设备初始化完毕,必须的。


return STATUS_SUCCESS;
}看上去代码很简单,这就够了。主要就是创建一个设备,然后附在传进来的pdo之上。


我们在过滤驱动里面只做一件事情,把传进来的buffer的第一个字节改成x。


看code:


[cpp] view plain copy print?
01.NTSTATUS HelloWDMIOControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp)  
02.{  
03.    KdPrint(("EX Enter HelloWDMIOControl\n"));  
04.  
05.    PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;  
06.    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);  
07.  
08.  
09.    //得到IOCTRL码   
10.    ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;  
11.  
12.    NTSTATUS status;  
13.    ULONG info = 0;  
14.    switch (code)  
15.    {  
16.    case IOCTL_ENCODE:  
17.        {              
18.            PDEVICE_OBJECT pFdo = fdo->DriverObject->DeviceObject;  
19.            do   
20.            {  
21.                pdx = (PDEVICE_EXTENSION)pFdo->DeviceExtension;  
22.                KdPrint(("EX Device: %x, PDX::NextStackDevice: %x\n", pFdo, pdx-


>NextStackDevice));  
23.                pFdo = pFdo->NextDevice;  
24.            } while (pFdo != NULL);  
25.  
26.            //过滤驱动里面修改一个输入缓冲的数据。   
27.            char* inBuf = (char*)Irp->AssociatedIrp.SystemBuffer;  
28.            inBuf[0] = 'x';  
29.  
30.            KdPrint(("Try to call lower driver Irp: %x\n", Irp));  
31.            pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;  
32.            IoCopyCurrentIrpStackLocationToNext(Irp);  
33.            status = IoCallDriver(pdx->NextStackDevice, Irp);  
34.  
35.            KdPrint(("Finished calling lower driver, Irp: %x", Irp));  
36.        }  
37.        break;  
38.    default:  
39.        IoSkipCurrentIrpStackLocation(Irp);  
40.        status = IoCallDriver(pdx->NextStackDevice, Irp);  
41.        break;  
42.    }  
43.  
44.    KdPrint(("EX Leave HelloWDMIOControl\n"));  
45.    return status;  
46.}  
NTSTATUS HelloWDMIOControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp)
{
KdPrint(("EX Enter HelloWDMIOControl\n"));


PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);




//得到IOCTRL码
ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;


NTSTATUS status;
ULONG info = 0;
switch (code)
{
case IOCTL_ENCODE:
{  
PDEVICE_OBJECT pFdo = fdo->DriverObject->DeviceObject;
do 
{
pdx = (PDEVICE_EXTENSION)pFdo->DeviceExtension;
KdPrint(("EX Device: %x, PDX::NextStackDevice: %x\n", pFdo, 


pdx->NextStackDevice));
pFdo = pFdo->NextDevice;
} while (pFdo != NULL);


//过滤驱动里面修改一个输入缓冲的数据。
char* inBuf = (char*)Irp->AssociatedIrp.SystemBuffer;
inBuf[0] = 'x';


KdPrint(("Try to call lower driver Irp: %x\n", Irp));
pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
IoCopyCurrentIrpStackLocationToNext(Irp);
status = IoCallDriver(pdx->NextStackDevice, Irp);


KdPrint(("Finished calling lower driver, Irp: %x", Irp));
}
break;
default:
IoSkipCurrentIrpStackLocation(Irp);
status = IoCallDriver(pdx->NextStackDevice, Irp);
break;
}


KdPrint(("EX Leave HelloWDMIOControl\n"));
return status;
}
如果是IOCTL_ENCODE的irp,那么把input buffer的第一个字节改成x,然后继续调用下层驱动。如果是


其他irp直接调用下层驱动。


代码就这么简单,现在来看看inf文件:


01.[Version]  
02.Signature=$CHICAGO$  
03.Provider=%MFGNAME%  
04.  
05.[DestinationDirs]  
06.DefaultDestDir=10,system32\drivers  
07.FiltJectCopyFiles=11  
08.  
09.[SourceDisksFiles]  
10.HelloWDM_Ex.sys=1  
11.  
12.[SourceDisksNames]  
13.1=%INSTDISK%,,,MyFilter_Check  
14.  
15.;------------------------------------------------------------------------------  
16.;  Windows 2000 Sections  
17.;------------------------------------------------------------------------------  
18.  
19.[DefaultInstall.ntx86]  
20.CopyFiles=DriverCopyFiles,FiltJectCopyFiles  
21.  
22.[DriverCopyFiles]  
23.HelloWDM_Ex.sys,,,0x60      ; replace newer, suppress dialog  
24.  
25.[DefaultInstall.ntx86.services]  
26.AddService=HelloWDM_Filter,,FilterService  
27.  
28.[FilterService]  
29.ServiceType=1  
30.StartType=3  
31.ErrorControl=1  
32.ServiceBinary=%10%\system32\drivers\HelloWDM_Ex.sys  
33.  
34.;------------------------------------------------------------------------------  
35.;  String Definitions  
36.;------------------------------------------------------------------------------  
37.  
38.[Strings]  
39.MFGNAME="Kevin filter"  
40.INSTDISK="Kevin disk"  
41.DESCRIPTION="Kevin - Sample Filter Driver"  
[Version]
Signature=$CHICAGO$
Provider=%MFGNAME%


[DestinationDirs]
DefaultDestDir=10,system32\drivers
FiltJectCopyFiles=11


[SourceDisksFiles]
HelloWDM_Ex.sys=1


[SourceDisksNames]
1=%INSTDISK%,,,MyFilter_Check


;------------------------------------------------------------------------------
;  Windows 2000 Sections
;------------------------------------------------------------------------------


[DefaultInstall.ntx86]
CopyFiles=DriverCopyFiles,FiltJectCopyFiles


[DriverCopyFiles]
HelloWDM_Ex.sys,,,0x60 ; replace newer, suppress dialog


[DefaultInstall.ntx86.services]
AddService=HelloWDM_Filter,,FilterService


[FilterService]
ServiceType=1
StartType=3
ErrorControl=1
ServiceBinary=%10%\system32\drivers\HelloWDM_Ex.sys


;------------------------------------------------------------------------------
;  String Definitions
;------------------------------------------------------------------------------


[Strings]
MFGNAME="Kevin filter"
INSTDISK="Kevin disk"
DESCRIPTION="Kevin - Sample Filter Driver"
比功能型驱动简单很多,没什么花头,就是要注意一下AddService=HelloWDM_Filter,,FilterService,


这里的HelloWDM_Filter后面会被用到。


这个inf文件,可以直接右键点击,然后安装。


安装这个还不够,我们还需要改一下注册表,来指明过滤驱动应该对哪些设备起作用。之前那个驱动的


ID是ClassGUID={EF2962F0-0D55-4bff-B8AA-2221EE8A79B0},那么我们可以在注册表里面找到


HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{EF2962F0-0D55-4BFF-B8AA-


2221EE8A79B0},给它加个子键,比如:




UpperFilters指上层过滤驱动(反之,下层驱动是LowerFilters),HelloWDM_Filter来自于inf文件的


AddService=HelloWDM_Filter,,FilterService


一旦我们创建的这个子键后,我们可以从设备管理器上看到:


重启电脑,用devicetree可以看到:


因为功能驱动HelloWDM创建了3个设备对象,那么过滤驱动将会附在再上层的FDO上面。从上图可以看到


附在MyWDMDevice3的上面。这个时候设备栈类似于:


filter device -> MyWDMDevice3 -> MyWDMDevice2 -> MyWDMDevice -> PDO(虚拟总线驱动创建的)


ok,测试一下。测试代码很简单,就是打开设备\\.\HelloWDM(MyWDMDevice),然后发一个IOCTL_ENCODE


请求(DeviceIoControl)。


01.// TestWDMDriver.cpp : Defines the entry point for the console application.   
02.//   
03.  
04.#include "stdafx.h"   
05.#include <windows.h>   
06.#include <process.h>   
07.  
08.#define DEVICE_NAME L"\\\\.\\HelloWDM"   
09.#define DEVICE_NAME2 L"\\\\.\\HelloWDM2"   
10.#define DEVICE_EX L"\\\\.\\HelloWDM_EX"   
11.  
12.void Test(void* pParam)  
13.{  
14.    int index = (int)pParam;  
15.    //设置overlapped标志,表示异步打开   
16.    HANDLE hDevice;  
17.      
18.    hDevice = CreateFile(DEVICE_NAME,GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | 


FILE_SHARE_WRITE,NULL,OPEN_EXISTING,  
19.            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL);  
20.  
21.    wprintf(L"CreateFile, name: %s, ret: %x\n", DEVICE_NAME, hDevice);  
22.      
23.      
24.  
25.    if (hDevice != INVALID_HANDLE_VALUE)  
26.    {  
27.        char inbuf[100] = {0};  
28.          
29.        sprintf(inbuf, "hello world %d", index);  
30.        char outbuf[100] = {0};  
31.        DWORD dwBytes = 0;  
32.  
33.        DWORD dwStart = GetTickCount();  
34.  
35.        printf("input buffer: %s\n", inbuf);  
36.          
37.        OVERLAPPED ol = {0};  
38.        ol.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);  
39.  
40.        BOOL b = DeviceIoControl(hDevice, CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, 


METHOD_IN_DIRECT, FILE_ANY_ACCESS),   
41.            inbuf, strlen(inbuf), outbuf, 100, &dwBytes, &ol);  
42.  
43.        printf("DeviceIoControl thread %d, returns %d, last error: %d, used: %d ms, 


input: %s\n", index, b, GetLastError(), GetTickCount() - dwStart, inbuf);  
44.  
45.  
46.        WaitForSingleObject(ol.hEvent, INFINITE);  
47.  
48.        DWORD dwEnd = GetTickCount();  
49.        //将输出buffer的数据和'm'亦或,看看是否能够得到初始的字符串。   
50.        for (int i = 0; i < strlen(inbuf); i++)  
51.        {  
52.            outbuf[i] = outbuf[i] ^ 'm';  
53.        }  
54.  
55.        printf("Verify thread %d, outbuf: %s, used: %d ms\n", index, outbuf, dwEnd - 


dwStart);  
56.  
57.        CloseHandle(hDevice);  
58.  
59.    }  
60.    else  
61.        printf("CreateFile failed, err: %x\n", GetLastError());  
62.}  
63.  
64.  
65.int _tmain(int argc, _TCHAR* argv[])  
66.{  
67.    HANDLE t1 = (HANDLE)_beginthread(Test, 0, (void*)0);  
68.  
69.    WaitForSingleObject(t1, INFINITE);  
70.  
71.    printf("Test ends\n");  
72.    return 0;  
73.}  
// TestWDMDriver.cpp : Defines the entry point for the console application.
//


#include "stdafx.h"
#include <windows.h>
#include <process.h>


#define DEVICE_NAME L"\\\\.\\HelloWDM"
#define DEVICE_NAME2 L"\\\\.\\HelloWDM2"
#define DEVICE_EX L"\\\\.\\HelloWDM_EX"


void Test(void* pParam)
{
int index = (int)pParam;
//设置overlapped标志,表示异步打开
HANDLE hDevice;

hDevice = CreateFile(DEVICE_NAME,GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | 


FILE_SHARE_WRITE,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,NULL);


wprintf(L"CreateFile, name: %s, ret: %x\n", DEVICE_NAME, hDevice);




if (hDevice != INVALID_HANDLE_VALUE)
{
char inbuf[100] = {0};

sprintf(inbuf, "hello world %d", index);
char outbuf[100] = {0};
DWORD dwBytes = 0;


DWORD dwStart = GetTickCount();


printf("input buffer: %s\n", inbuf);

OVERLAPPED ol = {0};
ol.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);


BOOL b = DeviceIoControl(hDevice, CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, 


METHOD_IN_DIRECT, FILE_ANY_ACCESS), 
inbuf, strlen(inbuf), outbuf, 100, &dwBytes, &ol);


printf("DeviceIoControl thread %d, returns %d, last error: %d, used: %d ms, 


input: %s\n", index, b, GetLastError(), GetTickCount() - dwStart, inbuf);




WaitForSingleObject(ol.hEvent, INFINITE);


DWORD dwEnd = GetTickCount();
//将输出buffer的数据和'm'亦或,看看是否能够得到初始的字符串。
for (int i = 0; i < strlen(inbuf); i++)
{
outbuf[i] = outbuf[i] ^ 'm';
}


printf("Verify thread %d, outbuf: %s, used: %d ms\n", index, outbuf, dwEnd 


- dwStart);


CloseHandle(hDevice);


}
else
printf("CreateFile failed, err: %x\n", GetLastError());
}




int _tmain(int argc, _TCHAR* argv[])
{
HANDLE t1 = (HANDLE)_beginthread(Test, 0, (void*)0);


WaitForSingleObject(t1, INFINITE);


printf("Test ends\n");
return 0;
}


看输出:
输进去hello world 0, 输出变成了xello world 0,说明过滤驱动起作用了。看看debugview的输出:
嘿嘿,确实有过滤驱动的输出,成功。
总结,看起来过滤驱动还是蛮简单的,当然这只是一个极其简单的例子,实际情况怕是没那么简单了。


要点:
1. 代码和功能驱动没什么大分别,如果无需过滤,则直接调用下层驱动。
2. INF文件需要相应修改,看上面的例子。
3. 需要改一下注册表,找到相应的ID, 如HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control


\Class\{EF2962F0-0D55-4BFF-B8AA-2221EE8A79B0},然后增加子键UpperFilters或者LowerFilters,看


上面的例子。(其实INF文件可以增加这个子键,在INF文件里面改下就好)
有关这个例子,如果我们设置为LowerFilters,那么过滤驱动就不会被调用到,为什么?很简单,因为这


个例子的功能驱动直接把IRP完成了,也就不会往下走了。
代码:http://download.csdn.net/detail/zj510/4906910
WDK(7600) 编译驱动, VS2008编译测试例子。过滤驱动可以直接右键inf文件安装。
========

过滤驱动的概念

有的公司采用技术手段禁止员工使用U盘,是为了防止员工通过U盘将敏感数据带出公司,本质上是禁止


敏感数据通过USB接口流出。USB接口比较复杂。本章讨论一个类似但是简单得多的设备:串口。要禁止


使用串口非常容易(给串口贴上封条,或者写一个简单的程序始终占用串口),但是要区别处理,允许


非机密数据流出,而禁止机密数据;或者要记录串口上流过的数据,然而又不影响其他的程序使用串口


,就有一定难度了。这一章中,我们通过给串口设备增加一个过滤层来实现这个功能。 `|:'f'2_  
而下一章中,我们则把目标转移到键盘,除了过滤之外,我们将进一步探讨是否有方法保证从键盘输入


的密码不被隐藏的黑客软件通过类似的过滤方式截取。 o4WoyzX-"  
在Windows系统上与安全软件相关的驱动开发过程中,“过滤”(filter)是极其重要的一个概念。过滤


是在不影响上层和下层接口的情况下,在Windows系统内核中加入新的层,从而不需要修改上层的软件或


者下层的真实驱动程序,就加入了新的功能。 U KKV{i5  
举一个比较容易理解的例子:实时监控的反病毒程序。任何高层软件或者Windows的文件系统都没有考虑


过应该什么时候去检查文件中是否含有某个病毒的特征码,实际上也不应该要求某个软件或者Windows的


文件系统去考虑这些。反病毒程序需要在不改变文件系统的上层和下层接口的情况下,在中间加入一个


过滤层,这样就可以在上层软件读取文件、下层驱动提供数据时,对这些数据进行扫描,看其中是否含


有某个病毒的特征码。这是一个很典型的过滤过程。 G/Prs|ug<  
本章之所以从串口过滤出发,是因为串口在Windows中是非常简单的设备。对一个安全软件来说,串口过


滤似乎没有什么意义。不过有一些特殊场合的计算机,要求防止“信息外泄”,或者需要知道“哪些信


息外泄”了。除了网络和可移动存储设备之外,有时串口也在考虑的范围之内。 5�dpS151  
此外,使用这种方法也可以绑定键盘,从而截获用户的击键。 &m/bx"Oe�  
3.1.1  设备绑定的内核API之一 $YBJ:GM{/  
进行过滤的最主要的方法是对一个设备对象(Device Object)进行绑定。读者可以想象,Windows系统


之所以可以运作,是因为Windows中已经存在许多提供了各种功能的设备对象。这些设备对象接收请求,


并完成实际硬件的功能。 E aS.d-  
我们可以首先认为:一个真实的设备对应一个设备对象(虽然实际对应关系可能复杂得多)。通过编程


可以生成一个虚拟的设备对象,并“绑定”(Attach)在一个真实的设备上。一旦绑定,则本来操作系


统发送给真实设备的请求,就会首先发送到这个虚拟设备。 YBk90/+  
下面结合代码进行讲解。读者可能希望编译执行这些代码,驱动的初学者请先阅读本书第1章,以便学会


如何安装开发环境、编译代码和调试程序。 la2d"?R*  
在WDK中,有多个内核API能实现绑定功能。下面是其中一个函数的原型: tO+`tj  
NTSTATUS aR OLvu545  
    IoAttachDevice( -nHLc?:N2  
            IN PDEVICE_OBJECT  SourceDevice, .&!�5<Rx  
            IN PUNICODE_STRING  TargetDevice, orp)Nyy  
            OUT PDEVICE_OBJECT  *AttachedDevice J2=N?>S>  
); P z45h  
IoAttachDevice参数如下: !jae"LuOm  
SourceDevice 是调用者生成的用来过滤的虚拟设备;而TargetDevice是要被绑定的目标设备。请注意这


里的TargetDevice并不是一个 PDEVICE_OBJECT(DEVICE_OBJECT是设备对象的数据结构,以P开头的是其


指针),而是一个字符串(在驱动开发中字符串用 UNICODE_STRING来表示)。实际上,这个字符串是要


被绑定的设备的名字。 *~ZlG8jD  
Windows中许多设备对象是有名字的,但是并不是所有的设备对象都有名字。必须是有名字的设备,才能


用这个内核API进行绑定。 M/n/=wbq2  
这里有一个疑问:假设这个函数绑定一个名字所对应的设备,那么如果这个设备已经被其他的设备绑定


了,会怎么样呢? YzLPTvc  
如果一个设备被其他设备绑定,它们在一起的一组设备,被称为设备栈(之所以称为栈,是由于和请求


的传递方式有关)。实际上,IoAttachDevice总是会绑定设备栈上最顶层的那个设备。 7_-(.nFjWh  
AttachedDevice是一个用来返回的指针的指针。绑定成功后,被绑定的设备指针被返回到这个地址。 K 


g!_9  
下面这个例子绑定串口1。之所以这里绑定很方便,是因为在Windows中,串口设备是有固定名字的。第


一个串口名字为“/Device/Serial0”,第二个为“/Device/Serial1”,依次类推。请注意实际编码时C


语言中的“/”要写成“//”。 F"} U)  
UNICODE_STRING com_name = RLT_CONSTANT_STRING(L"//Device//Serial0"); ?s+@tKAe  
NTSTATUS status = IoAttachDevice( kc=*o=d(  
    com_filter_device,      // 生成的过滤设备 "pf R  
    &com_device_name,      // 串口的设备名 }fS~z  
    &attached_device);      // 被绑定的设备指针返回到这里 [!j bz{(  
当然,试图执行这段代码的读者可能会发现,这里没有提供如何生成一个过滤设备的代码。在接下来的


第二个API介绍之后,读者会看到完整的例子。 CfEw a@v  
3.1.2  设备绑定的内核API之二 /&`m  
前面已经提到了并不是所有的设备都有设备名字,所以依靠IoAttachDevice无法绑定没有名字的设备。


另外还有两个API:一个是 IoAttachDeviceToDeviceStack,另一个是


IoAttachDeviceToDeviceStackSafe。这两个函数功能一样,都是根据设备对象的指针(而不是名字)进


行绑定;区别是IoAttachDeviceToDeviceStackSafe更加安全,而且只有在 Windows 2000SP4和Windows 


XP以上的系统中才有。一般都使用IoAttachDeviceToDeviceStackSafe,但当试图兼容较低版本的


Windows 2000时,应该使用IoAttachDeviceToDeviceStack。 gh/NI;6`  
NTSTATUS ]!@9[|  
IoAttachDeviceToDeviceStackSafe( s53M~(  
    IN PDEVICE_OBJECT  SourceDevice,    // 过滤设备 A^ pE#A%  
    IN PDEVICE_OBJECT  TargetDevice,    // 要被绑定的设备栈中的设备 FN@W]ZI,K  
    IN OUT PDEVICE_OBJECT  *AttachedToDeviceObject// 返回最终被绑定的设备 WKQ4(o0L  
    ); sd_"}kbX  
和第一个API是类似的,只是TargetDevice换成了一个指针。另外,AttachedToDevice Object同样也是


返回最终被绑定的设备,实际上也就是绑定之前设备栈上最顶端的那个设备。 >J86  -  
在Window 2000下应该使用另外一个函数IoAttachDeviceToDeviceStack,这个函数除了缺少最后一个参


数之外(实际上放到返回值里了),其他的和IoAttachDeviceTo DeviceStackSafe函数相同。 


K21aV6/$  
PDEVICE_OBJECT ]db=oR9I  
    IoAttachDeviceToDeviceStack( khP- o8|(�  
    IN PDEVICE_OBJECT  SourceDevice, jB/nN9j6  
    IN PDEVICE_OBJECT  TargetDevice 58H|$dM{  
); >gA@'/  
这个函数返回了最终被绑定的设备指针,这也就导致了它不能返回一个明确的错误码。但是如果为NULL


,则表示绑定失败了。 8�b>6T  
读到这里,读者一定迫不及待地想试试如何绑定一个串口了,但问题是,这里还没有介绍如何打开串口


设备(从名字获得设备对象指针)和如何生成一个串口。下面就给出绑定的完整例子。 ]^DGZ(]  
3.1.3  生成过滤设备并绑定 POz)IQAy  
在绑定一个设备之前,先要知道如何生成一个用于过滤的过滤设备。函数IoCreateDevice被用于生成设


备: [i@;/Uo[y  
NTSTATUS NBw- 7hK<z  
IoCreateDevice( B!1F*I*  
        IN PDRIVER_OBJECT  DriverObject,    R6H'?qq9R  
        IN ULONG  DeviceExtensionSize, �MVG1TTd  
        IN PUNICODE_STRING  DeviceName  OPTIONAL, T~/-DQLU  
        IN DEVICE_TYPE  DeviceType, M) bp{7  
        IN ULONG  DeviceCharacteristics, e]{gf  
        IN BOOLEAN  Exclusive, %4 Z1lOwU  
        OUT PDEVICE_OBJECT  *DeviceObject :iC'`* S  
        ); :O%zvb9P  
这个函数看上去很复杂,但是目前使用时,还无须了解太多。DriverObject是本驱动的驱动对象。这个


指针是系统提供,从DriverEntry中传入,在最后完整的例子中再解释。DeviceExtensionSize是设备扩


展,读者请先简单地传入0。DeviceName是设备名称。一个规则是:过滤设备一般不需要名称,所以传入


NULL即可。DeviceType是设备类型,保持和被绑定的设备类型一致即可。 DeviceCharacteristics是设


备特征,在生成设备对象时笔者总是凭经验直接填0,然后看是否排斥,选择FALSE。 -Y1Z,Gb  
值得注意的是,在绑定一个设备之前,应该把这个设备对象的多个子域设置成和要绑定的目标对象一致


,包括标志和特征。下面是一个示例的函数,这个函数可以生成一个设备,然后绑定在另一个设备上。 


&'yUXl)9Vm  
NTSTATUS I-w=JGT/D  
ccpAttachDevice( 7iyDBrWSI0  
            PDRIVER_OBJECT driver, /A6|6dzM  
            PDEVICE_OBJECT oldobj, Tt7kLOR  
            PDEVICE_OBJECT *fltobj, P< q�w %_  
            PDEVICE_OBJECT *next) PRR]=EoYu  
{ T)[sh &  
    NTSTATUS status; 28i5 gK!  
    PDEVICE_OBJECT topdev = NULL; Fu4Q3D}%  
    // 生成设备,然后绑定 =yU!xz.Sr7  
    status = IoCreateDevice(driver, B[79%),  
                        0, EGoEfNk0S  
                        NULL, )zX7FE   
                        oldobj->DeviceType, VEs.(o]|}  
                        0, }s2E}&SO  
                        FALSE, Jzehq7 %  
                        fltobj); v, Nrn%  
    if (status != STATUS_SUCCESS) L?aemV/  
        return status; uIy! .<LF  
    // 拷贝重要标志位 y W}):Al?  
    if(oldobj->Flags & DO_BUFFERED_IO) }HVLLmQ  
        (*fltobj)->Flags |= DO_BUFFERED_IO; e fnQM6  
    if(oldobj->Flags & DO_DIRECT_IO) yVCz9*ybj#  
        (*fltobj)->Flags |= DO_DIRECT_IO; f<s<h> Z/R  
    if(oldobj->Flags & DO_BUFFERED_IO) dm-iX;Q!  
        (*fltobj)->Flags |= DO_BUFFERED_IO; //)o!0 @  
    if(oldobj->Characteristics & FILE_DEVICE_SECURE_OPEN) yG]A _s'  
        (*fltobj)->Characteristics |= FILE_DEVICE_SECURE_OPEN; Ico1X>U  
    (*fltobj)->Flags |=  DO_POWER_PAGABLE; {ca@Yi:B  
    // 将一个设备绑定到另一个设备上 >yxeH! 5  
    topdev = IoAttachDeviceToDeviceStack(*fltobj,oldobj); C sol ;.F  
    if (topdev == NULL) c:~,WC~PF  
    { yY,*iEk  
        // 如果绑定失败了,销毁设备,返回错误。 1LaOYu  
        IoDeleteDevice(*fltobj);  ;at:Z&  
        *fltobj = NULL; ;Z. Va2d  
        status = STATUS_UNSUCCESSFUL; `VHoCU+  
        return status; _.^OKm*  
    } ) >|/p(  
    *next = topdev; tamw4h{n  
    // 设置这个设备已经启动 ,K- Ne  
    (*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING; G7].p4E  
    return STATUS_SUCCESS; h5^w.;8/  
}  fDoeR&&Tv  
3.1.4  从名字获得设备对象 V6/iu"+g&  
在知道一个设备名字的情况下,使用函数IoGetDeviceObjectPointer可以获得这个设备对象的指针。这


个函数的原型如下: 6,!/x"d~8  
NTSTATUS e1EH* D  
IoGetDeviceObjectPointer( ipz/dJ.`m  
    IN PUNICODE_STRING  ObjectName, i3*$VG^=  
    IN ACCESS_MASK  DesiredAccess, qnI 7@P  
    OUT PFILE_OBJECT  *FileObject, yOO3/NtI�V  
    OUT PDEVICE_OBJECT  *DeviceObject >L5x<, 3;  
    ); uLMjD.`U-8  
其中的ObjectName就是设备名字。DesiredAccess是期望访问的权限。实际使用时可以不要顾忌那么多,


直接填写 FILE_ALL_ACCESS即可。FileObject是一个返回参数,即获得这个设备对象的同时会得到的一


个文件对象(File Object)。就打开串口设备这件事而言,这个文件对象并没有什么用处。但是必须注


意:在使用这个函数之后必须把这个文件对象“解除引用”,否则会引起内存泄漏(请注意后面的代码


)。 B&;Paa|=  
要得到的设备对象就返回在参数DeviceObject中了。示例如下: m3}uDNJ]4  
#include <ntddk.h> 4$/"kx1D!  
// 因为用到了RtlStringCchPrintfW,所以必须使用头文件ntstrsafe.h 6)9TaEv.  
// 这里定义NTSTRSAFE_LIB是为了使用静态的ntstrsafe静态库。这样才能 y}V}k,; Z  
// 兼容Windows2000。 * NOg:fu  
#define NTSTRSAFE_LIB Bx_4a  
#include <ntstrsafe.h> .+m1  
…… (+[Dni15  
// 打开一个端口设备 jI(C _;?  
PDEVICE_OBJECT ccpOpenCom(ULONG id,NTSTATUS *status) 2�1^ek  
{ GfUKdURh=  
    // 外面输入的是串口的id,这里会改写成字符串的形式 7"%YIbK!)  
    UNICODE_STRING name_str; w".`f= }W  
    static WCHAR name[32] = { 0 }; 6V#v GJ$  
    PFILE_OBJECT fileobj = NULL; g4TCZ  
    PDEVICE_OBJECT devobj = NULL; H_Z=`7wy  
    // 根据id转换成串口的名字 ?oxpAD  
    memset(name,0,sizeof(WCHAR)*32); yVQ(EUfbE#  
    RtlStringCchPrintfW( |6A/jF  
        name,32, </v Y!fM  
        L"//Device//Serial%d",id); '?|lEkXb]-  
    RtlInitUnicodeString(&name_str,name); y*|fF"IAK  
    // 打开设备对象 4eVr1mgq  
    *status = IoGetDeviceObjectPointer( qYFKF%Sq7  
            &name_str, &]%W&a<  
            FILE_ALL_ACCESS, ..io1 f^  
            &fileobj, &devobj); x>p.U.S}0  
            uJ $t! -  
    // 如果打开成功了,记得一定要把文件对象解除引用 yM&iA+3  
    // 总之,这一句不要忽视 j�r_>mZ  
    if (*status == STATUS_SUCCESS) DMc{sS5  
        ObDereferenceObject(fileobj); E.911F  
    // 返回设备对象 !%2f~O!9$/  
    return devobj; (Kh+wDKcTx  
} ,Kc=wRV=n  
3.1.5  绑定所有串口 aGnN<X>$]  
计算机上到底有多少个串口?笔者提供不出很好的方法来判定,除了依次去打开串口0、1、2、3…,目


前还不知道如果串口2不存在,是否说明串口3、4…肯定不存在?这是没有依据的,所以只好全部测试一


次。不过有一个好处是,串口是焊死在计算机上的,很少见到能“即插即用”的串口(但是有一种用USB


口来虚拟串口的设备,不知道会不会产生动态生成串口的效果,在这里先忽略这一点)。那么绑定所有


串口,就只需要做一次就可以了,不用去动态地追踪串口的诞生与消亡。 7zv./9%  
下面是一个简单的函数,实现了绑定本机上所有串口的功能。这个函数用到了前面提供的ccpOpenCom和


ccpAttachDevice这两个函数。 cS(v4,C  
为了后面的过滤,这里必须把过滤设备和被绑定的设备(后面暂且称为真实设备吧,虽然这些设备未必


真实)的设备对象指针都保存起来。下面的代码用两个数组保存。数组应该多大?这取决于一台计算机


上最多能有多少个串口。读者应该去查阅IBM PC标准,这里笔者随意地写一个自认为足够大的数字。 


xsRMPB  
// 计算机上最多只有32个串口,这是笔者的假定 x6g-HiSkO  
#define CCP_MAX_COM_ID 32          GZ~z%1og  
// 保存所有过滤设备指针 3>A1C8  
static PDEVICE_OBJECT s_fltobj[CCP_MAX_COM_ID] = { 0 };    <C,N3 ;PB@  
// 保存所有真实设备指针 ^O:Mt//B  
static PDEVICE_OBJECT s_nextobj[CCP_MAX_COM_ID] = { 0 }; kyh;~D;M  
// 这个函数绑定所有的串口 n@DPW, }  
void ccpAttachAllComs(PDRIVER_OBJECT driver) 7�dE;FI  
{ {(s_dtA_  
    ULONG i; RETffF/  
    PDEVICE_OBJECT com_ob; HKz-Ok]vQ  
    NTSTATUS status; g44<@]J=E  
    for(i = 0;i<CCP_MAX_COM_ID;i++) r$"zi.:o<  
    { $@sPn#@ *  
        // 获得object引用 ! Oi]3bO  
        com_ob = ccpOpenCom(i,&status); BK?+&  
        if(com_ob == NULL) Xh1|6#i6l  
            continue; A F^LOA$*j  
        // 在这里绑定,并不管绑定是否成功 /ITFLs!3  
        ccpAttachDevice(driver,com_ob,&s_fltobj,&s_nextobj); !?Ojij.$gg  
    } 7']#0 +  
} x=[A iF9 8  
没有必要关心这个绑定是否成功,就算失败了,看一下s_fltobj即可。这个数组中不为NULL的成员表示


已经绑定了,为NULL的成员则是没有绑定或者绑定失败的。这个函数需要一个DRIVER_OBJECT的指针,这


是本驱动的驱动对象,是系统在DriverEntry中传入的。 D Kn21U  
========
0 0
原创粉丝点击