很简单的一个USB过滤驱动,是按照张帆的那本书里最后两个驱动例子写的;主要功能就是使USB存储设备写保护;
当U盘插入计算机时,USB总线会枚举一个PDO,并将一个USBSTOR的驱动作为FDO加载到PDO,USBSTOP会创建一个设备PDO2,上面挂载这DISK.sys驱动,再其上是Partmgr驱动(分区驱动),具体结构如下图:
安装驱动的话可以自己写INF文件,我写INF文件的时候遇到了一个问题,驱动是成功安装了,但是不起作用,连DriverEntry都没有进去,仔细查找才发现原来驱动在Service域里的ServiceBinary=%10%\system32\drivers\USBFilter.sys 这个路径写错了,多写了个字母,结果是驱动运行去找这个sys文件的时候发现找不到,就不工作了,所以写INF文件还是要小心;
驱动的加载:USB 存储设备一般每个U盘都会在注册表SYSTEM\CurrentControlSet\Enum\USBStor 中留有记录,每个USB设备都不一样,只要在那台机子上插过就会有,如图:
只要在希望禁止的U盘设备下,建立LowerFilter,值为USBFiter就行了,当然这样的话只能限制该USB设备为只读,不能限制其他USB设备;曾经尝试过在Class注册表里的磁盘GUID里加入LowerFilter,来限制所有USB,但是需要判断是否是USB存储还是磁盘存储,不能限制磁盘存储,否则系统会起不来的,下面会讲遇到的这个问题;还有一种方法就是在Class中的USB串口控制器里加入这个,这样会导致所有是USB设备的都会被限制到,如键盘,鼠标之类的,所以这个方法不是很好;
现在讲讲代码里的一些问题吧:
DriverEntry基本没什么问题,大多程序都是这么写的;
在AddDevice里因为粗心,出了一个还是很令人头疼的问题的,在我IoAttachDeviceToDeviceStack一个设备之后,再设置本层DEVICE_OBJECT的Flags属性的时候不小心设置成被Attach的那个设备,也就是下一层设备的Flags,导致设备一加载,然后系统就调用Unload函数卸载驱动了;刚开始还莫名其妙不知道错在哪里,这个根本调式不出来到底错哪里了;讲讲Flags设置吧,一般都用被挂载的下一层设备的flags赋值给当前的这层的,
pUSBDeviceObject->Flags |= pfido->Flags&(DO_DIRECT_IO | DO_BUFFERED_IO |DO_POWER_PAGABLE);因为不知道下层的是DO_DIRECT_IO 还是DO_BUFFERED_IO,所以这里两个都做了判断,DO_POWER_PAGEBLE是WDM驱动必须设置的,详情可以看WDK;pUSBDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; 当创建设备的时候,io管理器会设置这个值,表示该设备正常被初始化,一旦在本层设备attach下层设备且设置了power state就可以取消这个值,表示设备已经初始化完了;
接下来要讲的是,磁盘过滤命令;磁盘过滤主要就是对IRP_MJ_SCSI这个IRP的拦截,其实就是 IRP_MJ_INTERNAL_DEVICE_CONTROL的一个别名,在disk.sys和USBSTOR.sys之间传递的是标准的SCSI指令;
对于SCSI命令,都在DDK的srb.h和scsi.h中有定义:
如果想变为只读,只要将SCSIOP_WRITE 请求设置为失败就行,所有U盘的写请求最终都会变成SCSIOP_WIRTER ,然后用这种方法的话,系统会提示很久才失败,因为系统在接受到SCSIOP_WRITER失败时候就会不断的尝试再发送该请求的;所以我们一般在SCSIOP_MODE_SENSE 请求时,设置SCSIOP_MODE_SENSE的MODE_DSP_WRITER_PROTECT位,以达到保护效果,简略代码如下:
if(opCode == SCSIOP_MODE_SENSE && pCurSrb->DataBuffer &&
pCurSrb->DataTransferLength>=sizeof(MODE_PARAMETER_HEADER))
{
KdPrint(("SCSIOP_MODE_SENSE COMING!\n"));
PMODE_PARAMETER_HEADER pModeData = (PMODE_PARAMETER_HEADER)pCurSrb->DataBuffer;
pModeData->DeviceSpecificParameter |= MODE_DSP_WRITE_PROTECT;
}
在我们的这层设备层刚开始工作的时候上层会有很多SCSI检测指令下来,SCSIOP_MODE_SENSE也就是在那时候设置的;
挂载磁盘而能正常工作,在网上搜到的是在接收到的SCSI指令之后发送指令给总线判断一下是什么总线,如果是USB总线的话就设置protect位,否则不设置;具体怎么做没有尝试;而是尝试了自己的一种方法,当然先说明结果是失败的,但是也能明白点事儿:
我的想法是每个磁盘除了系统盘都装这层的device,我们的目的主要是验证下保护能否正常工作就行,只要系统盘不被保护,系统启动时临时文件这些都能写入系统盘,系统就不会蓝屏;基于这种考虑,我就在IRP_MN_START_DEVICE 指令处,自己build了一个Control向底层发送一个IOCTL_STORAGE_GET_DEVICE_NUMBER指令:
status = DeviceIoctl(pDevEx->m_LowDeviceObject,
IOCTL_STORAGE_GET_DEVICE_NUMBER,
NULL,
0,
&storageDeviceNum,
sizeof(STORAGE_DEVICE_NUMBER),
&ioStatusBlock);
在STORAGE_DEVICE_NUMBER这个结构体里有一个DeviceNumber的变量是指明磁盘的disk索引号的,我记录下每个磁盘的索引号,当遇到0的时候,就不设置保护位;理论设想是好的,但是实际证明还是会蓝屏,经过调式发现调用IOCTL_STORAGE_GET_DEVICE_NUMBER这个指令返回的却是STATUS_NOT_SUPPORT,这就奇怪了,以前我用的都是好好的,我怀疑是不是被中间的lowerfilters拒绝掉了,我直接发送最底层也是这个结果,后来与同事讨论发现,这个IRP是在Disk.sys支持的,disk.sys处理完之后就直接结束了,不会往下传了,我们的驱动在Disk.sys下层,所以会失败,以前都是用的disk.sys的上层,所以成功;
还遇到一个问题就是在IRP_MN_START_DEVICE 中调用函数发送IOCTL_STORAGE_GET_DEVICE_NUMBER去底层发挥STATUS_NOT_SUPPORT,我不小心直接把这个值当成case里的返回值,倒是系统认为IRP_MN_START_DEVICE这个IRP是STATUS_NOT_SUPPORT而导致一直起不来而蓝屏;
case IRP_MN_START_DEVICE:
KdPrint(("IRP_MN_START_DEVICE\n"));
Status = SynchorousSendPnpIrpDown(pDevEX->m_LowDeviceObject,pIrp);
if(NT_SUCCESS(status))
{
//status = FindDiskIndex(pDevEX)原来的
FindDiskIndex(pDevEX);
}
IoReleaseRemoveLock(&pDevEX->m_RemoveLock,NULL);
return status;
总的来说驱动比较简单,关键是对底下的驱动每层作用以及以下IRP的作用不了解所以老出问题,这也是不开源,难找文档的鄙陋之处;