WDM Filter 驱动的一点基础知识

54 篇文章 17 订阅
49 篇文章 12 订阅

                                                                             By Fanxiushu ,引用和转载请注明原作者

WDM Filter是一类驱动的总称,它把自己挂载到功能设备(FDO)之下或者之上,拦截所有的IRP,对这些IRP分析处理,从而达到过滤的目的。
它跟 NT式过滤驱动都是一样的目的,比如前面文章讲到的TDI Filter驱动就是个标准的NT式过滤驱动,
但是处理方式上稍微有些不同,主要区别在 WDM需要处理即插即用和电源上。

这里主要讲述 WDM Filter的开发框架和安装方法。

WDM支持即插即用,简单的说,一个设备可以随时插入电脑使用,也可以随时拔出来。
WDM要能达到这种效果,处理方式就与NT式驱动有些不同,

(1)WDM不在 DriverEntry里创建设备,而是注册 DriverObject->DriverExtension->AddDevice 回调函数,
然后在 AddDevice函数里创建设备。
AddDevice函数被系统调用的时机:
某总线驱动(比如PCI总线驱动,USB总线驱动等)发现有设备插入,于是创建一个物理对象(PDO)来表示这个设备,
然后把这个PDO通知给PNP管理器,
PnP管理器获得通知后,在注册表找与这个设备相关的驱动程序,
如果没找到,加载此设备失败(也就是我们常在设备管理器里看到的某个设备出现惊叹号什么的),
找到相关驱动程序后,加载此驱动,调用此驱动的AddDevice函数把PDO作为参数传递进去,此驱动会创建FDO等一些列相关工作。
接着PNP管理器还要在注册表里查找附加在此设备FDO之上或者之下的 过滤驱动程序,
最终会找到我们的过滤驱动,于是接着调用我们过滤驱动程序的AddDevice函数,把PDO传递进来。

在AddDevice的实现里,我们创建一个无名设备,然后Attach到 PDO上,然后做一些其他相关工作,这就是我们的设备的创建流程。

(2)在DriverEntry需要注册 IRP_MJ_PNP 和 IRP_MJ_POWER 回调函数,处理即插即用事件和电源事件。同时也要替换所有派遣函数。
(3)过滤驱动中,在 IRP_MJ_PNP事件中,我们比较感兴趣的是 IRP_MN_REMOVE_DEVICE子事件,他与 AddDeice相反,
        在设备移除时候被调用,用来告诉我们的驱动,要删除我们在AddDevice创建的设备。还有其他一些子事件,用来记录设备的当前状态,
        比如启动,停止,查询是否可以停止,查询是否可以删除,取消停止,取消删除等状态。
(4)过滤驱动中,电源事件我们只需简单传递给下层驱动即可。
(5)使用 IO_REMOVE_LOCK锁记录每个IRP,这样当我们还存在未完成的IRP的时候,阻止设备被删除。
(6)若有必要,需要创建一个控制设备用来与用户层交互信息 .

以下是WDM Filter的框架代码:

struct filter_extension_t
{
      PDEVICE_OBJECT filter_dev;
      PDEVICE_OBJECT lower_dev;
      PDEVICE_OBJECT physical_dev;
      ....
     IO_REMOVE_LOCK  remove_lock; ///删除锁,用来记录每个IRP请求,阻止在未完成IRP时就删除设备。
};

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING reg_path)
{
         for(i=0;i<IRP_MJ_MAXIMUM_FUNCTION;++i)
             DriverObject->MajorFunction[ i] = FilterDispatch; //替换所有的派遣函数
         
        DriverObject->DriverUnlock = FilterUnload; //注册驱动卸载函数
       
        DriverObject->DriverExtension->Adddevice = FilterAddDevice;  //注册驱动添加设备函数
        DriverObject->MajorFunction[ IRP_MJ_PNP ] = FilterPnp;    //注册PNP
        DriverObject->MajorFunction[IRP_MJ_POWER ] = FilterPowwer;//注册POWER
        .... //其他全局变量初始化
       
        return STATUS_SUCCESS;
}

VOID FilterUnload(PDRIVER_OBJECT DriverObject)
{
     ...//一些全局变量的删除
}

NTSTATUS FilterAddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo )
{
        NTSTATUS status ;
        PDEVICE_OBJECT  filter_dev, lower_dev;
        ///获得物理设备的设备类型
        PDEVICE_OBJECT obj = IoGetAttachedDeviceReference( pdo );
        DEVICE_TYPE dev_type = obj->DeviceType;
        ObDereferenceObject( obj );
        //创建设备
        status = IoCreateDevice(... , sizeof(filter_extension_t),NULL, dev_type , ... &filter_dev);
        if(!NT_SUCCESS(status))return status;
        ///把FIDO附加到PDO上。
        lower_dev = IoAttachDeviceToDeviceStack( filter_dev, pdo ); //
        if(!lower_dev )//失败,出错处理
       ///修改过滤设备的标志和Characteristics
       filter_dev->Flags |= lower_dev->Flags & (DO_DIRECT_IO | DO_BUFFERED_IO | DO_POWER_PAGABLE); //修改Flags
       filter_dev->Characteristics = lower_dev->Characteristics ; //修改 Characteristics
       
       filter_extension_t* ex = filter_dev->DeviceExtension;
       ....//初始化设备扩展结构
      IoInitializeRemoveLock( &ex->remove_lock, .... );
      ......

     /如果有必要,创建一个控制设备,方便驱动跟用户层通讯
     CreateMyControlDevice(); ///
     //因为可能有多个设备插入,所以AddDevice可能被多次调用。我们创建控制设备时,需要使用一个计数,当计数达到1,表示第一次
     //调用AddDevice,于是创建设备; 在响应IRP_MN_REMOVE_DEVICE消息时,减少计数,
     //到达0,表示所有设备都被删除,于是可以删除我们的控制设备了。
     //之所以采用此办法,而不是在 在DriverEntry和 DriverUnload函数里创建和删除设备,是因为PNP管理器可以动态卸载驱动,
     //如果在这两个地方创建和删除设备,PNP无法自动卸载驱动。
    
     filter_dev->->Flags &= ~DO_DEVICE_INITIALIZING;
     return STATUS_SUCCESS;
}

NTSTATUS FilterPower( PDEVICE_OBJECT dev, PIRP irp )
{
      ... //电源管理,只需直接把IRP传递到下层驱动。
      ext = dev->DeviceExtension;
      PoStartNextPowerIrp( irp );
      status = IoAcquireRemoveLock( &ext->remove_lock );
      if(!NT_SUCCESS(status)){//调用失败,因为设备正在被删除,此时直接完成IRP
            irp->IoStatus.Status = status;
            IoCompleteRequest( irp );
            return status;
      }
      IoSkipCurrentIrpStackLocation(irp);
      status = PoCallDriver( dev, irp);
      IoReleaseRemoveLock( &ext->remove_lock );
      return status;
}
///处理PNP事件
NTSTATUS FilterPnp( PDEVICE_OBJECT dev, PIRP irp )
{
     ....
     status = IoAcquireRemoveLock( &ext->remove_lock );
     if(!NT_SUCCESS(status))//调用失败,因为设备正在被删除,此时直接完成IRP
     。。。
     switch( irp->MinorFunction)
    {
    case IRP_MN_START_DEVICE: //启动设备
        break;
    case IRP_MN_REMOVE_DEVICE: //设备删除,在这里我们处理的事情比较多,所有在AddDevice里创建的东西,都要在这里销毁
     。。。
     IoReleaseAndWaitRemoveLock(...);  //等待所有的IRP完成。
     ....
     IoDetachDevice(。。。)
     IoDeleteDevice(。。。);
     ......
      break;
    case 。。。。
    }
    。。。。。。。
  
}

NTSTATUS FilterDispatch(PDEVICE_OBJECT dev, PIRP irp )
{
       ///检测是控制设备发送的IRP还是过滤设备发送的,判断办法可以在设备扩展结构中加一个公共参数来判断或者直接通过设备指针来判断
       if(  是控制设备的IRP )
            return MyControlDeviceDispatch( dev, irp ); ///进入到控制设备的派遣函数。
       
        status = IoAcquireRemoveLock( &ext->remove_lock );
        if(!NT_SUCCESS(status)) //失败,直接完成本IRP返回错误
        。。。
        switch( irp->MajorFunction )
        {
           //在这里,可以对我们感兴趣的IRP进行过滤处理。不感兴趣的,直接PASS到底层驱动。
        }
        .....
        IoReleaseemoveLock( &ext->remove_lock );
        ...
}

以上就是 WDM Filter驱动的框架,应该是比较简单的。  当然比起NT式过滤驱动,稍微复杂点。
上章在介绍 TDI过滤驱动时,也简答说了下NT过滤驱动的框架。

WDM过滤驱动的安装办法。
既可以用INF安装,也可以手动添加。
在INF安装的时候,正确填写 INF配置信息文件,然后用臭名昭著的SetupDiXXX一类函数进行安装处理。
我比较喜欢手动添加,
首先用CreateService创建驱动服务,然后直接在注册表里,添加 LowerFilters或者UpperFilters字段,比较的简单,
比起又要配置 INF文件,又要是使用SetupDiXXXX函数方便多了。
其实看看INF文件里,主要也是干两件事:一是创建服务,二是添加 LowerFiltes或者 UpperFilters 。
CreateService就不用说了,就按照通常创建驱动服务的方法创建就可以了。
关键是 LowerFilters和UpperFilters字段的添加。
LowerFilters顾名思义是下层过滤驱动,UpperFilters是上层过滤驱动,这都是相对 FDO而言的。
LowerFilters是挂载在FDO下面,UpperFilters是挂载在 FDO上面。
加载 WDM Filters有两种途径:
一是直接挂载到某个设备的FDO之上或者之下,
在注册表 HKLM\ \SYSTEM\CurrentControlSet\Enum 的位置,找到我们需要过滤的设备,然后
在里边加一个 UpperFilters(LowerFilters)多字符串字段,接着把这个字段设置成 我们CreateService创建的驱动名字,
有可能发现已经有UpperFilters(LowerFilters)存在,并且还可能有别的服务名,那就说明此设备还安装有别的过滤驱动。
二是挂载到类驱动FDO之上或者之下,
在注册表  HKLM\SYSTEM\CurrentControlSet\Control\Class\, 里边有许多 <CLASS-GUID>,都是类驱动标志
比如 HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E967-E325-11CE-BFC1-08002BE10318} 对应的是磁盘类驱动,
所有磁盘驱动,包括 IDE,SCSI,USB磁盘等等,都在这一类里。
同样的,我们在此类里,加入
UpperFilters(LowerFilters),并把他设置成我们的驱动服务名,
这个时候,此类下的所有设备驱动加载的时候,都会加载我们的过滤驱动。

 最后的问题,WDM Filter用来干什么? 它可以干得事情挺多!
 比如常用的U盘数据加密,设置U盘只读,硬盘加密。。。
 键盘按键拦截,摄像头视频拦截等等。
简单介绍一下U盘加密的处理思路,关于此类的代码,网上介绍的比较多。
U盘加密是按照 LowerFiters(底层过滤驱动)来安装的,一般是安装在磁盘类驱动里。
如上面所说,这里会有个问题,安装到磁盘类驱动里,所有的磁盘都会被加密,所以我们必须判断哪些是U盘驱动。
 先看看U盘的大致流程,当U盘插入到电脑USB接口,系统获得通知创建一个PDO,
并判断出这是个U盘,于是系统把USBSTOR驱动Attach在上面,USBSTOR另外再创建一个PDO,并把DISK类驱动附加在他上面,
我们的过滤驱动其实就是在 USBSTOR.sys 和 DISK.sys之间。
于是我们找到一种判断是否U盘的办法:
就是在 AddDevice函数里,判断 PDO->DriverObject->DriverName 的名字是否是 \Driver\USBSTOR,如果是说明我们正附加在U盘上,
不是的话,说明是其他类磁盘,直接返回错误即可。

 USBSToR和DISK之间传输的是标准 SCSI命令。

按照 WDMFilter框架,主要处理 IRP_MJ_SCSI 控制码,对此IRP进行拦截处理,就能实现磁盘加密解密,以及U盘只读保护等许多功能。
比如设置只读保护,在
SCSIOP_MODE_SENS命令的完成函数里,设置MODE_DSP_WRITE_PROTECT 标志位。
又比如加密,在
SCSIOP_WRITE命令对写的扇区执行数据加密操作,在SCSIOP_READ命令的完成函数里,对这些加密的扇区执行数据解密操作,
这样就完成了一个加密解密流程。
 

下章会比较详细的描述 摄像头过滤驱动的开发过程。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值