USB存储设备单向控制的研究与实现

1 引言


USB存储设备的单向控制可以有效地解决这个问题。USB存储设备单向控制技术对接入计算机的存储介质进行控制,防止信息被有意或者无意从移动存储设备泄漏出去。用户能够根据需要设定存储设备的使用权限(比如只读或者读写等),既保留了移动设备的方便性,又堵截了移动存储设备可能带来的安全隐患。


    2  设计与实现


    本文采用对磁盘驱动器进行过滤的方法实现USB存储设备单向控制。以DDK中的filter为原形,采用标准的WDM过滤,拦截所有的对USB存储设备的写操作,实现了U盘的单向控制。过滤器驱动程序是可选择的驱动程序,给设备增加值或修改设备的行为。过滤器驱动程序能服务于一个或多个设备。顶层过滤器驱动程序典型地为一设备提供增值的特征,低层过滤器驱动程序典型地修改设备硬件的行为。所以本文选择使用低层设备过滤器驱动程序,监视和修改磁盘驱动器的I/O请求。


    2.1  驱动程序基本结构


    一个WDM 驱动程序的基本结构包括一组必要的系统定义的标准驱动程序函数,加上一些可选的标准函数与内部函数,这取决于驱动程序的类型和下层设备。所有的驱动程序,不管它们在附属驱动程序链中所处的层,都必须有一组基本标准函数以处理IRP。一个驱动程序是否必须执行附加标准函数取决于该驱动程序的类型和下层设备,是控制一个物理设备的驱动程序,还是在一个物理设备驱动程序之上的驱动程序,也取决于下层物理设备的属性。控制物理设备的最低层驱动程序比较高层驱动程序拥有更多要求的函数,较高层驱动程序一般传送IRP给较低层驱动程序处理。


    下面列出了本驱动程序所需要的标准驱动程序函数,


(1)DriveEntry:初始化驱动程序并设置其他标准函数的入口点 

 

      当驱动程序的DriverEntry函数被调用,它直接在驱动程序对象中设置Dispatch、和Unload入口点,如下所示: 

 

      DriverObject->MajorFunction[IRP_NJ_xxx]=DispatchXxx; 

 

                :              : 

 

      DriverObject->MajorFunction[IRP_NJ_yyy]=DispatchYyy; 

 

                :              : 

 

      DriverObject->DriverUnload=Unload; 

 

      在驱动程序对象内的DriverExtension中,设置它的AddDevice函数的入口点,如下: 

 

  DriverObject->DriverExtension->AddDevice=DDAddDevice;驱动程序能定义若干Dispatch入口点,但是它只能在其驱动程序对象中定义一个AddDevice入口点,和一个Unload入口点。 

 

      (2)AddDevice:创建设备对象,在DriverObject-> DriverExtension->AddDevice 

 

      (3)Dispatch:至少一个Dispatch入口点,用一个或多个主要功能编码处理IRP,以得到请求PnP、电源、和I/O操作的IRP。



    2.2  驱动程序的实现


    (1)为设备定义 GUID。驱动程序使用设备名和GUID(globally unique identifiers)来标识不同的物理、逻辑或虚拟设备。PnP驱动程序注册并激活一个与GUID连接的设备接口,应用程序和其他系统组件可以通过接口对设备进行I/O请求和控制。WDM过滤驱动禁止给它们的设备对象命名,所以只要为设备定义 GUID。


    (2)为驱动程序函数选择名字。在每个驱动程序中都要包含标准的驱动程序函数,因此使用一套区别于其他驱动程序的函数命名机制,会使程序更容易开发、调试和测试。


    (3)编写一个为AddDevice、DispatchPnP、DispatchPower和DispatchCreate函数设置入口点的DriverEntry函数。


    (4)编写一个完成下面内容的AddDevice函数:


    ①调用IoCreateDevice创建一个独立设备对象(如何设置一个设备对象见第3章)。


    ②调用IoAttachDeviceToDeviceStack把它自己加入设备栈,填写PDEVICE_EXTENSION。


    ③调用IoRegisterDeviceInterface为它的设备暴露一个接口。暴露的接口为访问该设备的应用程序提供了途径。


    ④调用IoSetDeviceInterfaceState以激活它先前注册的接口。


这样完成以后,过滤设备就可以在DeviceTree中看到。


    (5)为IRP_MJ_PNP请求编写一个基本DispatchPnP函数。该DispatchPnP函数必须准备处理具体的PnP IRP。


    (6)为IRP_MJ_POWER编写一个基本DispatchPower函数。


    (7)为IRP_MJ_CREATE请求编写一个基本Dispatch Create函数。


    (8)拦截相应的IRP请求。


    (9)为I/O控制请求编写一个基本DispatchDevCtrl函数,与应用程序进行通讯,处理具体的控制请求。


    3  关键技术

    3.1  SCSI命令的分析


    对应不同的过滤功能,需要拦截的IRP也不同。要对U盘进行单向控制,需要拦截所有的写操作,使U盘成为只读的。但是写U盘的时候,发送的并不是通常的IRP_MJ_WRITE请求,而是要分析相应的SCSI命令,对SCSI命令的取得和操作大致如下:

(1)得到当前的SCSI命令: 
 
     irpStack = IoGetCurrentIrpStackLocation(irp); 
 
    CurSrb=irpStack->Parameters.Scsi.Srb; 
 
    cdb=CurSrb->Cdb; 
 
    opCode=cdb->CDB6GENERIC.OperationCode; 
 
    现在opCode中就是当前的SCSI命令。 
 

      (2)分析SCSI命令 
 
    if(opCode==SCSIOP_MODE_SENSE)//一个mode sense结构 
 
    { } 
 
    if(opCode==SCSIOP_WRITE||opCode==SCSIOP_WRITE6)//写操作 
 
    { }

    3.2  单向控制的实现


    系统进行写操作的时候,通常都是先写在缓存中,在一定的延时后才会写到真正的磁盘中。所以当拦截SCSI命令中的SCSIOP_WRITE后,虽然系统不会真正的写东西到U盘上,但却要过很久才会提示延时写错误。所以本文采用了另外一种方法:用软件实现“带写保护功能”的U盘,效果与硬件上实现的写保护一样,实现了U盘的只读。


    软件实现方法如下:

irpStack = IoGetCurrentIrpStackLocation(Irp); 

 

           CurSrb=irpStack->Parameters.Scsi.Srb; 

 

           cdb=CurSrb->Cdb; 

 

           opCode=cdb->CDB6GENERIC.OperationCode; 

 

           if(opCode==SCSIOP_MODE_SENSE  && 

 

           CurSrb->DataBuffer  && 

 

           CurSrb->DataTransferLength >= 

 

           sizeof(MODE_PARAMETER_HEADER) 

 

          { 
 
            modeData = (PMODE_PARAMETER _HEADER) CurSrb->DataBuffer; 
 
          modeData->DeviceSpecificParameter|=MODE_DSP_WRITE_PROTECT; 
 
         }

     3.3  区分硬盘和U盘驱动器


     作为磁盘驱动器的低层设备过滤器驱动程序,如何区分硬盘和U盘驱动器?


    Device_Object->DeviceType的值并不能真正区分硬盘和U盘驱动器。这个值对于U盘而言第一次插入时是0x2d,但是一旦被虚拟化成磁盘分区后,将变成07和本地硬盘没有任何区别。如果采用IoGetDeviceProperty来获得当前物理设备对象的总线类型的GUID,又往往会导致操作系统出现蓝屏(系统死机)。因为函数调用要求在passive-level执行,而不是dispatch-level。


status=IoGetDeviceProperty( 
 
       deviceExtension->PhysicalDeviceObject, 
 
       DevicePropertyBusTypeGuid, 
 
       length, 
 
       (PVOID)&guidBusType, 
 
       &uReturn);


    但是符号链接的名称和光盘驱动器的设备类型将保持不变。所以我修改设备扩展的内部结构,增加DeviceType项,根据符号链接的名称和设备类型来设置deviceExtension->DeviceType,对光盘和硬盘以及 USB都有很好的支持。


    代码如下:

#define IDE_DISK_STRING L"//??//IDE#Disk" 
 
#define USBSTOR_DISK_STRING L"//??//USBSTOR#Disk" 
 

if(PhysicalDeviceObject->DeviceType == FILE_DEVICE_ CD _ROM) 
 
     deviceExtension->bCdRom = TRUE; 
 
else 
 
     deviceExtension->bCdRom = FALSE; 
 
      
 
if(RtlCompareMemory(deviceExtension->ifSymLinkName.Buffer,IDE_DISK_STRING,sizeof(IDE_DISK_STRING)-sizeof(WCHAR)) == sizeof(IDE_DISK_STRING)-sizeof(WCHAR)) 
 
     deviceExtension->DeviceType = IDE_DISK; 
  

  if(RtlCompareMemory(deviceExtension->ifSymLinkName.Buffer,USBSTOR_DISK_STRING,sizeof(USBSTOR_DISK_STRING)-sizeof(WCHAR)) == sizeof(USBSTOR_DISK_STRING)- sizeof(WCHAR)) 
 
     deviceExtension->DeviceType = USBSTOR_DISK;


    4  结束语


    本文在分析了磁盘读写技术的基础上,采用对磁盘驱动器进行过滤的方法,设计并实现了基于过滤驱动的USB存储设备单向控制。这种技术的实现能有效地解决涉密信息的外泄,是内网安全的一种重要辅助手段。