你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 计算机安全与维护
6.12 USB存储设备监控程序的开发
 

一、引言

随着计算机技术的发展,USB存储设备如U盘、移动硬盘等设备越来越普及,随之而来的一个非常显著的问题就是:如何保证这些设备的安全,比如有些涉密计算机不允许使用某些移动存储设备,但是却允许另外一些USB存储设备访问。

下面将对USB存储设备的监控所涉及到的技术及原理进行探讨,最后给出一个USB存储设备的监控程序的例子。

二、原理

一个USB设备插入到计算机USB端口上时,操作系统硬件管理程序将会发现设备,然后查找该设备的驱动程序是否存在,如果存在,系统加载驱动程序,然后给USB设备分配盘符等。

从上面的分析中可以知道,如果要阻止USB设备在计算机上使用,至少有两个方法可以使用:一是修改设备驱动程序,在设备驱动程序里面加入对设备进行判断的代码,从而阻止非授权USB设备在系统上的识别;第二种方法是不修改驱动程序,而在USB设备枚举完成后,立即把设备卸载,从而在系统中无法使用该设备。

上面两种方法中,第一种需要熟悉驱动程序开发技术,难度比较大;第二种原理比较简单,实现起来也相对容易。本文将采用第二种方法。

第二种方法的原理是:当插入USB存储设备时,应该立即获取该USB设备的信息,然后判断这些信息是否是经过授权的,如果非法,立即调用卸载函数卸载该USB设备。

三、开发过程

从上面的分析中可以知道,系统可以分为三部分:USB存储设备的检测、USB设备信息的读取判断、设备的卸载。

1. USB检测

Windows系统中,当PC机上添加或者删除一个即插即用设备时,将触发系统的WM_DEVICECHANGE消息。对于USB设备的检测也一样,在程序中捕获这个消息,然后在消息处理函数获取设备参数。

声明过程用于检测设备的变化:

procedure WMDeviceChange(var AMessage:TMessage);message WM_DEVICECHANGE;

程序捕捉到这个消息以后,需要进行判断,消息的AMessage.wParam表明了设备信息及当前状态:

DBT_DEVNODES_CHANGED//设备节点发生了变化(关键点A

DBT_DEVICEARRIVAL//插入设备了(关键点B

但是,WM_DEVICECHANGE消息不但响应硬件设备的改变,而且PC上装入光盘等存储设备时,该消息也会响应。那么,如何判断系统插入的是USB存储设备还是放入光盘呢?

通过跟踪调试可以知道,PC机插入设备时,AMessage.wParam值为DBT_DEVICEARRIVAL,可以通过AMessage.LParam的值判断插入的是否是存储设备。

PDEV_BROADCAST_HDR(Message.LParam).dbch_devicetype的值为DBT_DEVTYP_VOLUME表示插入了存储设备,但这个值的还是无法区分是USB存储设备还是光盘等设备。

插入USB设备的时候,AMessage.wParam的值首先变为DBT_DEVNODES_CHANGED(上面关键点A),然后才变成DBT_DEVICEARRIVAL(关键点B);而插入光盘等没有引起系统硬件状态改变的介质时,只会响应关键点B,而不会响应关键点A。因此,通过联合这两个值的状态,就可以确切知道系统插入的是否为USB存储设备。示例代码如下:

procedure TForm1.WMDeviceChange(var Message: TMessage);

var

  pid:DWORD;

begin

  if DisMountCmdOk then    //如果已经发出卸载命令,则不再响应该消息

    exit;

  //监测USB存储设备的插入

  if SelfDisMount then //是否是本程序自己卸载USB设备

    begin

      SelfDisMount:=false;
//
如果是本程序自己卸载了USB设备,则不再响应该消息

      exit;

    end;

  case Message.wParam of

    DBT_DEVICEARRIVAL:   //关键点B:插入设备了

      begin

      case PDEV_BROADCAST_HDR(Message.LParam).dbch_devicetype of

        DBT_DEVTYP_OEM:     ListBox1.Items.Add('DBT_DEVTYP_OEM');

        DBT_DEVTYP_DEVNODE: ListBox1.Items.Add('DBT_DEVTYP_DEVNODE');

        DBT_DEVTYP_VOLUME:  //这个值对U盘和光盘都起作用

          begin

            if IsHardWareChanged then 
//
通过IsHardWareChanged区分USB存储设备和光盘等

              begin

                IsHardWareChanged:=false;

                AllowUSB:=false;

                //在此处获取刚插入U盘的盘符

                diskvol:=FirstDriveFromMask(PDEV_BROADCAST_HDR(Message.LParam).dbcv_unitmask);

                GetUSBInfo(diskvol, Pid);//获取标志信息

                ListBox1.Items.Add('DBT_DEVTYP_VOLUME:插入USB 存储设备;盘符:'+diskvol+':;序列号:'+inttostr(pid));

//判断是否为授权U盘,用变量AllowUSB标志。代码略

//理论上在此处可以放置卸载设备代码,但是经过测试发现:如果把卸载代码(在//Timer1Timer过程中)

//放置在此处,卸载将会非常缓慢。所以采用了延时的方法解决这个问题

                isusb:=true;

                DisMountCmdOk:=true;//卸载命令已经发出

                //使用定时器延时后弹出设备

                timer1.Enabled:=true;

              end

            else

            ListBox1.Items.Add('DBT_DEVTYP_VOLUME:插入CD等其他存储介质');

          end;

      end;

      end;

    DBT_DEVNODES_CHANGED: //关键点A:插入USB设备后首先响应这里

      begin

         IsHardWareChanged:=true;

 //设备发生变化。这个值将被用来区分是USB存储设备还是别的存储介质(如光盘)

      end;

  end;

  inherited;

end;

弹出、卸载USB存储设备的代码在定时器的消息响应中。当系统检测到USB存储设备后,需要弹出USB存储设备时,使定时器有效;延时时间到后就可弹出USB存储设备。

2USB读取

上文中使用过程GetVolSerial获取USB存储设备标志信息。该过程完成对PC机上刚插入的USB存储设备标志信息的获得,从而作为我们判断设备是否合法的依据。

API函数GetVolumeInformation用于获取指定根路径的卷和文件系统的相关信息。此处只需获得卷的序列号作为标志信息,所以只关心参数lpVolumeSerialNumber的值。

BOOL GetVolumeInformation(

LPCTSTR lpRootPathName, // 根路径指针

LPTSTR lpVolumeNameBuffer,  // 卷名称指针

DWORD nVolumeNameSize,  // 卷名称字符串长度

LPDWORD lpVolumeSerialNumber,   //序列号指针

LPDWORD lpMaximumComponentLength,   //文件名称最大长度指针

LPDWORD lpFileSystemFlags,  //文件系统指针

LPTSTR lpFileSystemNameBuffer,  //文件系统名称指针

DWORD nFileSystemNameSize   //文件系统名称字符串长度指针

);

API函数GetVolumeInformation进行封装成GetVolSerial。实现代码如下:

function GetUSBInfo(diskVol:string;var lpVolumeSerialNumber: DWORD):boolean;

var

  lpRootPathName: PChar;

  lpVolumeNameBuffer:PChar;

  nVolumeNameSize: DWORD;

  lpMaximumComponentLength, lpFileSystemFlags: DWORD;

  lpFileSystemNameBuffer: PChar;

  nFileSystemNameSize: DWORD;

  ifVolOK:boolean;

begin

  lpVolumeNameBuffer:=AllocMem(256);

  lpFileSystemNameBuffer:=AllocMem(256);

  lpRootPathName:= PChar(diskVol+':\');

  nVolumeNameSize:=256;

  lpVolumeSerialNumber:=0;

  lpMaximumComponentLength:=256;

  lpFileSystemFlags:=0;

  nFileSystemNameSize:=256;

  ifVolOK:=GetVolumeInformation(lpRootPathName,lpVolumeNameBuffer,256,

@lpVolumeSerialNumber,lpMaximumComponentLength,lpFileSystemFlags,lpFileSystemNameBuffer,nFileSystemNameSize);

  Freemem(lpVolumeNameBuffer);

  Freemem(lpFileSystemNameBuffer);

  result:=ifVolOK;

end;

该函数将返回盘符为diskVolUSB存储设备的序列号到参数lpVolumeSerialNumber中。程序可以检测该参数,从而判定USB存储设备是否合法。

这里提供的函数比较简单,网上有很多类似代码探讨如何获得U盘的序列号,但是都不是很理想。感兴趣的读者可以进一步探讨这个问题。

3USB卸载

上文中提到,USB存储设备的卸载是通过定时器的消息响应来完成的。实验表明,如果把卸载代码放在判定设备是否合法后面,系统卸载USB存储设备将会非常缓慢。

procedure TForm1.Timer1Timer(Sender: TObject);

begin

   //AllowUSB=true; //此处应该恢复可以使用USB存储设备(测试程序中注释掉)

   isusb:=false;

   Timer1.Enabled:=false;

   SelfDisMount:=true;  //表示此次卸载是本程序自己完成的

   IniDevice; //获得设备列表,以及USB存储设备的在列表中的ID

   RejectUSB; //卸载设备

   DisMountCmdOk:=false;

end;

卸载设备的时候,首先调用SetupDiGetClassDevsA函数建立系统当前设备列表,然后调用函数SetupDiEnumDeviceInfo遍历这个列表,查找设备名称为“USB Mass Storage Device”的设备(Windows设备管理程序中,所有USB存储设备都使用这个名字),获得其在设备列表中的ID,然后调用函数CM_Request_Device_Eject请求系统卸载该设备。这些代码分别在过程IniDeviceRejectUSB中实现。

1)获得设备信息

1)获取系统中所有设备信息到hDevInfo指针所指空间

function GetDevInfo(var hDevInfo: hDevInfo): boolean;

begin

  hDevInfo := SetupDiGetClassDevsA(nil,nil,0,DIGCF_PRESENT or DIGCF_ALLCLASSES);

  Result := hDevInfo <> Pointer(INVALID_HANDLE_VALUE);

end;

API函数SetupDiGetClassDevsA获取系统当前设备列表到指针hDevInfo的数据空间。

2)遍历DevInfo,获得U盘在当前系统设备列表中的ID

function EnumAddDevices(ShowHidden: Boolean;DevInfo: hDevInfo): Boolean;

var

  i, Status, Problem: DWord;

  pszText: PChar;

  DeviceInfoData:TSPDevInfoData;

begin

  DeviceInfoData.cbSize := SizeOf(TSPDevInfoData);

  i := 0;

  //遍历设备列表,查找USB存储设备信息

  while SetupDiEnumDeviceInfo(DevInfo, i, DeviceInfoData) do

  begin

    inc(i);

    //获取设备节点状态信息

    if (CM_Get_DevNode_Status(@Status, @Problem, DeviceInfoData.DevInst, 0) <> CR_SUCCESS) then

    begin

      break;

    end;

    try

      GetMem(pszText, 256);

      ConstructDeviceName(DevInfo, DeviceInfoData, pszText, DWord(nil));

//创建设备可见名称列表

      if pos(MyDevice,StrPas(pszText))<>0 then    

//比较字符串,找到USB存储设备

        MyDevice_ID:=i-1;  //得到USB存储设备在当前设备列表中的ID

    finally

      FreeMem(pszText);

    end;

  end;

  Result := true;

end;

API函数SetupDiEnumDeviceInfo获取当前设备列表(DevInfo)中当前设备节点(i个节点)的信息到参数DeviceInfoData

API函数CM_Get_DevNode_Status查询当前设备节点的状态信息,如果查询表示设备存在并且工作正常。

函数ConstructDeviceName是程序中自己实现的非系统函数,其功能是获得到当前设备节点的可见设备名称,该名称就是设备管理器显示的设备名称。限于篇幅,此处不再详细介绍该函数的实现,读者可以参考源代码。

3)获得设备ID

procedure IniDevice;

begin

  MyDevice_ID:=0;

  DevInfo := nil;

  if not GetDevInfo(DevInfo) then

    begin

      ShowMessage('枚举设备失败!');

      exit;

    end;

  EnumAddDevices(TRUE,DevInfo);

end;

该过程调用上面两个函数,过程执行完毕后,将把USB存储设备在系统当前设备列表中的ID存储到参数MyDevice_ID中,卸载过程将使用该ID完成设备的卸载。

2)卸载USB存储设备

procedure RejectUSB;

var

  DeviceInfoData:TSPDevInfoData;

  Status, Problem: DWord;

  VetoType: TPNPVetoType;

  VetoName: array[0..256] of Char;

  result_index:Cardinal;

begin

DeviceInfoData.cbSize := SizeOf(TSPDevInfoData);

//判断设备ID

    if (not SetupDiEnumDeviceInfo(DevInfo, MyDevice_ID, DeviceInfoData)) then

      exit;

    //查询设备状态

if (CM_Get_DevNode_Status(@Status, @Problem, DeviceInfoData.DevInst, 0) <> CR_SUCCESS) then

      exit;

    VetoName[0] := #0;

    //请求系统卸载设备

result_index:=CM_Request_Device_Eject(DeviceInfoData.DevInst, VetoType, @VetoName, SizeOf(VetoName), 0);

    case result_index of

      CR_SUCCESS:

               SelfDisMount:=true;

  end;

end;

过程中比较重要的代码是:

if (not SetupDiEnumDeviceInfo(DevInfo, MyDevice_ID, DeviceInfoData)) then

      exit;

这段代码判断当前设备是否是上面得到的ID所标志的设备。接着查询设备状态,然后调用API函数CM_Request_Device_Eject请求系统卸载设备。

具体代码比较复杂,详见本文附带源代码。源代码中作了非常详细的注释,相信读者完全可以掌握。

四、结语

具体实现时,在窗体上放置一个Listbox控件,用于显示当前插入设备的简单信息。使用的时候,首先启动程序,然后插入USB设备即可察看程序运行结果。

本文所附源代码没有完成USB存储设备是否合法的判定。程序直接将刚插入的USB存储设备卸载。文章中已经给出了获取USB存储设备标志信息的方法(调用GetUSBInfo函数即可),因此,读者只需做一个简单判断即可知道当前插入的USB存储设备是否是授权可以使用的设备。USB操作的所有的方法放置在USBinfo.pas文件中。

另外,系统中插入的USB设备可能会自动运行,因此还应该在程序中加入禁止设备自动运行的代码。鉴于篇幅和时间,程序中没有实现该功能。程序在XPDelphi7下调试通过。

  推荐精品文章

·2024年12月目录 
·2024年11月目录 
·2024年10月目录 
·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089