检测硬件的插入与拔除

来自:http://www.codeproject.com/Articles/14500/Detecting-Hardware-Insertion-and-or-Removal

Introduction

Hot-pluggable device is now a big threat to IT security. In this article, we will try to develop a user-mode application to detect device change on the system, i.e. plug-in a USB drive, iPod, USB wireless network card, etc. The program can also disable any newly plugged devices. We will get a basic idea on how this works and talk about its limitations at the end of this article.

How to detect hardware change?

Well, in fact, Windows OS will post WM_DEVICECHANGE upon device change. All we need to do is to add a handler to handle this event.

BEGIN_MESSAGE_MAP(CHWDetectDlg, CDialog)
    // ... other handlers
    ON_MESSAGE(WM_DEVICECHANGE, OnMyDeviceChange)
END_MESSAGE_MAP()

LRESULT CHWDetectDlg::OnMyDeviceChange(WPARAM wParam, LPARAM lParam)
{
    // for more information, see MSDN help of WM_DEVICECHANGE
    // this part should not be very difficult to understand
    if ( DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam ) {
        PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam;
        switch( pHdr->dbch_devicetype ) {
            case DBT_DEVTYP_DEVICEINTERFACE:
                PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
                // do something...
                break;

            case DBT_DEVTYP_HANDLE:
                PDEV_BROADCAST_HANDLE pDevHnd = (PDEV_BROADCAST_HANDLE)pHdr;
                // do something...
                break;

            case DBT_DEVTYP_OEM:
                PDEV_BROADCAST_OEM pDevOem = (PDEV_BROADCAST_OEM)pHdr;
                // do something...
                break;

            case DBT_DEVTYP_PORT:
                PDEV_BROADCAST_PORT pDevPort = (PDEV_BROADCAST_PORT)pHdr;
                // do something...
                break;

            case DBT_DEVTYP_VOLUME:
                PDEV_BROADCAST_VOLUME pDevVolume = (PDEV_BROADCAST_VOLUME)pHdr;
                // do something...
                break;
        }
    }
    return 0;
}

However, by default, Windows OS will only post WM_DEVICECHANGE to

  1. All applications with a top-level window, and
  2. Only upon port and volume change.

Well, this is not bad, since at least you will know when an extra "disk" is mounted/unmounted, and you can get the affected drive letter by using theDEV_BROADCAST_VOLUME.dbcv_unitmask. The downside is that you won't know what physical device has actually been plugged into the system.

API: RegisterDeviceNotification()

To get notified on other types of device changes, or to get notified if you are running as a service and don't have a top-level window, you have to callRegisterDeviceNotification() API. For example, to get notified upon interface change, you can do the following.

1.  DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
2.  ZeroMemory( &NotificationFilter, sizeof(NotificationFilter) );
3.  NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
4.  NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
5.  // assume we want to be notified with USBSTOR
6.  // to get notified with all interface on XP or above
7.  // ORed 3rd param with DEVICE_NOTIFY_ALL_INTERFACE_CLASSES and dbcc_classguid will be ignored
8.  NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_USBSTOR;
9.  HDEVNOTIFY hDevNotify = RegisterDeviceNotification(this->GetSafeHwnd(),
        amp;NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
10. if( !hDevNotify ) {
11.     // error handling...
12.     return FALSE;
13. }

Pay extra attention to line 8, the NotificationFilter.dbcc_classguid. SeeDoron Holan's blog

A PnP device is typically associated with two different GUIDs, a device interface GUID and a device class GUID.

A device class GUID defines a broad category of devices. If you open up device manager, the default view is "by type." Each type is a device class, where each class is uniquely ID's by the device class GUID. A device class GUID defines the icon for the class, default security settings, install properties (like a user cannot manually install an instance of this class, it must be enumerated by PNP), and other settings. The device class GUID does not define an I/O interface (see Glossary), rather think of it as a grouping of devices. I think a good clarifying example is the Ports class. Both COM and LPT devices are a part of the Ports class, yet each has its own distinct I/O interface which are not compatible with each other. A device can only belong to one device class. The device class GUID is the GUID you see at the top of an INF file.

A device interface GUID defines a particular I/O interface contract. It is expected that every instanceof the interface GUID will support the same basic set of I/Os. The device interface GUID is what the driver will register and enable/disable based on PnP state. A device can register many device interfaces for itself, it is not limited to one interface GUID. If need be, the device can even register multiple instances of the same GUID (assuming each have their own ReferenceString), although I have never seen a real world need for this. A simple I/O interface contract is the keyboard device interface to the raw input thread. Here is the keyboard device contract that each instance of the keyboard device interface GUID must support.

You can see the current list of device classes and device interface classes at the following registries:

  • \\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class
  • \\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses

A list of common device interface class GUIDs is given below:

Device Interface NameGUID
USB Raw Device{a5dcbf10-6530-11d2-901f-00c04fb951ed}
Disk Device{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
Network Card{ad498944-762f-11d0-8dcb-00c04fc3358c}
Human Interface Device (HID){4d1e55b2-f16f-11cf-88cb-001111000030}
Palm{784126bf-4190-11d4-b5c2-00c04f687a67}

Decoding DEV_BROADCAST_DEVICEINTERFACE

Let's change our handler code OnMyDeviceChange() as follows:

LRESULT CHWDetectDlg::OnMyDeviceChange(WPARAM wParam, LPARAM lParam)
{
    ....
    ....
    if ( DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam )
    {
        PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam;
        switch( pHdr->dbch_devicetype )
        {
            case DBT_DEVTYP_DEVICEINTERFACE:
                PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
                UpdateDevice(pDevInf, wParam);
                break;
    ....
    ....
}

And from MSDN, we know

typedef struct _DEV_BROADCAST_DEVICEINTERFACE {
    DWORD dbcc_size;
    DWORD dbcc_devicetype;
    DWORD dbcc_reserved;
    GUID dbcc_classguid;
    TCHAR dbcc_name[1];
} DEV_BROADCAST_DEVICEINTERFACE *PDEV_BROADCAST_DEVICEINTERFACE;

It seems by using the dbcc_name, we can know what device has been plugged into the system. Sadly, the answer is NO,dbcc_name is for OS internal use and is an identity, it is not human readable. A sample ofdbcc_name is as follows:

\\?\USB#Vid_04e8&Pid_503b#0002F9A9828E0F06#{a5dcbf10-6530-11d2-901f-00c04fb951ed}

  • \\?\USB: USB means this is a USB device class
  • Vid_04e8&Pid_053b: Vid/Pid is VendorID andProductID(but this is device class specific, USB use Vid/Pid, different device classes use different naming conventions)
  • 002F9A9828E0F06: seems to be a unique ID (not sure about how this got generated)
  • {a5dcbf10-6530-11d2-901f-00c04fb951ed}: the device interface class GUID

Now, by using this decoded information, we can get the device description or device friendly name by two methods:

  1. Read the registry directly: for our example dbcc_name, it will be,\\HKLM\SYSTEM\CurrentControlSet\Enum\USB\Vid_04e8&Pid_503b\0002F9A9828E0F06
  2. Use SetupDiXxx

API: SetupDiXxx()

Windows has a set of API to allow an application to retrieve hardware device information programmatically. For example, we can get the device description or device friendly name with thedbcc_name. The flow of the program is roughly as follows:

  1. Use SetupDiGetClassDevs() to get a handle of device info set HDEVINFO, you can think of the handle as a directory handle.
  2. Use SetupDiEnumDeviceInfo() to enumerate all the device in the info set, you can think of this operation as a directory listing. Upon each iteration, we will get aSP_DEVINFO_DATA, you can think of this handle as the file handle.
  3. During the enumeration, use SetupDiGetDeviceInstanceId() to read the instance ID for each device, you can think of this operation as reading file attribute. The instance ID is in the form of "USB\Vid_04e8&Pid_503b\0002F9A9828E0F06", very similar to the dbcc_name.
  4. If the instance ID match the dbcc_name, we call SetupDiGetDeviceRegistryProperty() to retrieve the description or friendly name

The program is listed as follows:

void CHWDetectDlg::UpdateDevice(PDEV_BROADCAST_DEVICEINTERFACE pDevInf, WPARAM wParam)
{
    // dbcc_name:
    // \\?\USB#Vid_04e8&Pid_503b#0002F9A9828E0F06#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
    // convert to
    // USB\Vid_04e8&Pid_503b\0002F9A9828E0F06
    ASSERT(lstrlen(pDevInf->dbcc_name) > 4);
    CString szDevId = pDevInf->dbcc_name+4;
    int idx = szDevId.ReverseFind(_T('#'));
    ASSERT( -1 != idx );
    szDevId.Truncate(idx);
    szDevId.Replace(_T('#'), _T('\\'));
    szDevId.MakeUpper();

    CString szClass;
    idx = szDevId.Find(_T('\\'));
    ASSERT(-1 != idx );
    szClass = szDevId.Left(idx);

    // if we are adding device, we only need present devices
    // otherwise, we need all devices
    DWORD dwFlag = DBT_DEVICEARRIVAL != wParam
        ? DIGCF_ALLCLASSES : (DIGCF_ALLCLASSES | DIGCF_PRESENT);
    HDEVINFO hDevInfo = SetupDiGetClassDevs(NULL, szClass, NULL, dwFlag);
    if( INVALID_HANDLE_VALUE == hDevInfo )
    {
        AfxMessageBox(CString("SetupDiGetClassDevs(): ")
            + _com_error(GetLastError()).ErrorMessage(), MB_ICONEXCLAMATION);
        return;
    }

    SP_DEVINFO_DATA* pspDevInfoData =
        (SP_DEVINFO_DATA*)HeapAlloc(GetProcessHeap(), 0, sizeof(SP_DEVINFO_DATA));
    pspDevInfoData->cbSize = sizeof(SP_DEVINFO_DATA);
    for(int i=0; SetupDiEnumDeviceInfo(hDevInfo,i,pspDevInfoData); i++)
    {
        DWORD DataT ;
        DWORD nSize=0 ;
        TCHAR buf[MAX_PATH];

        if ( !SetupDiGetDeviceInstanceId(hDevInfo, pspDevInfoData, buf, sizeof(buf), &nSize) )
        {
            AfxMessageBox(CString("SetupDiGetDeviceInstanceId(): ")
                + _com_error(GetLastError()).ErrorMessage(), MB_ICONEXCLAMATION);
            break;
        }

        if ( szDevId == buf )
        {
            // device found
            if ( SetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData,
                SPDRP_FRIENDLYNAME, &DataT, (PBYTE)buf, sizeof(buf), &nSize) ) {
                // do nothing
            } else if ( SetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData,
                SPDRP_DEVICEDESC, &DataT, (PBYTE)buf, sizeof(buf), &nSize) ) {
                // do nothing
            } else {
                lstrcpy(buf, _T("Unknown"));
            }
            // update UI
            // .....
            // .....
            break;
        }
    }

    if ( pspDevInfoData ) HeapFree(GetProcessHeap(), 0, pspDevInfoData);
    SetupDiDestroyDeviceInfoList(hDevInfo);
}

Disable a device

Suppose you have the correct HDEVINFO and SP_DEVINFO_DATA (actually, we save thedbcc_name as the tree node extra data and retrieve that data when we right click on the device icon and then callSetupDiGetClassDevs and SetupDiEnumDevicInfo), the flow to disable a device is as follows:

  1. Setup the SP_PROPCHANGE_PARAMS structure properly
  2. Call SetupDiSetClassInstallParams() and pass-in the SP_PROPCHANGE_PARAMS structure
  3. Call SetupDiCallClassInstaller() with DIF_PROPERTYCHANGE

In fact, the DIF codes are a bit complicated, and you will have to call SetupDiSetClassInstallParams() with different structures for different DIF codes. For more information, see MSDN "Handling DIF Codes".

SP_PROPCHANGE_PARAMS spPropChangeParams ;
spPropChangeParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
spPropChangeParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE ;
spPropChangeParams.Scope = DICS_FLAG_GLOBAL ;
spPropChangeParams.HwProfile = 0; // current hardware profile
spPropChangeParams.StateChange = DICS_DISABLE

if( !SetupDiSetClassInstallParams(hDevInfo, &spDevInfoData,
    // note we pass spPropChangeParams as SP_CLASSINSTALL_HEADER
    // but set the size as sizeof(SP_PROPCHANGE_PARAMS)
    (SP_CLASSINSTALL_HEADER*)&spPropChangeParams, sizeof(SP_PROPCHANGE_PARAMS)) )
{
    // handle error
}
else if(!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, &spDevInfoData))
{
    // handle error
}
else
{
    // ok, show disable success dialog
    // note, after that, the OS will post DBT_DEVICEREMOVECOMPLETE for the disabled device
}

Minor Issue

I experience multiple DBT_DEVICEARRIVAL/DBT_DEVICEREMOVECOMPLETE messages on the same insertion/removal of my USB wireless card.

Limitations

  1. Obviously, the program can only detect device changes when the application is running, e.g. device plugged in before the system power-up, or before the application starts up will not be detected. But this can be solved by saving the current state at a remote computer and then checking the difference during application starts up.
  2. We can disable the device, but that is all we can do. We can't access control the device against the logon user, nor we can provide read only access. IMHO, I think this can only be fixed if we re-implement the whole program as a kernel mode filter driver.

History

  • June 19th, 2006: Initial release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

 Sam NG

Hong KongHong Kong
Member

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
赋所有源代码,开发工具vs2010 framework3.5 baidu搜索c# HidUsb都是大同小异案例,而且拿下来基本不能用。大都是围绕public static extern int CreateFile(省略众多参数..);发现没有,copy下来测试基本都是用不了的。 原因很简单:windows不允许你用程序随便就去访问硬件设备。所以在此把之前做过的基于C#开发读写HidUsb设备的项目整理成一个简单的小案例,分享给大家,开发环境VS2010。 该案例重点在public static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, int hTemplateFile); 看着貌似也是用到CreateFile这个函数,其实并不然,注意到没有"SafeFileHandle",这就是重点! 这样windows是允许程序访问外接hidusb设备的。 当然具体如何运用这个函数现在已经不是您应该 关心的了,因为我已经为您把它封装成一个类,您只要调用相应的方法就OK. 例: //第一步:获取HidUsb设备信息 List slist = new List(); UsbHidDevice usbhid = new UsbHidDevice(); usbhid.GetDeviceList(ref slist); //HidUsb设备信息包含在List数据集中 注:当获取到HidUsb设备信息为:\\?\hid#vid_0e2c&pid;_0112#6&1b44c403;&0&0000;#{4d1e55b2-f16f-11cf-88cb-001111000030}, 注意该字符串里的“vid_0e2c”和“pid_0112”部分,那么: vid为0e2c, pid为:0112 //第二步:创建一个HidUsb设备访问实例 UsbHidDevice Device = new UsbHidDevice(vid, pid); //第三步:连接HidUsb设备 Boolean connBool = Device.Connect(); //第四步:实现数据接收事件 Device.DataReceived += new UsbHidDevice.DataReceivedDelegate(Device_DataReceived); //当HidUsb设备返回信息时触发此事件 void Device_DataReceived(byte[] data) { //处理接收到的数据逻辑 } //第五步:向Hid设备发送数据"0xa0 00 0x12 0x9 0x22" string txt = "0xa0 00 0x12 0x9 0x22"; //把数据转换为字节数组 byte[] data = ConvertHelper.StringToByte(txt2); byte bt = 0; CommandMessage cmdMsg = new CommandMessage(bt, data); Boolean sbool = Device.SendMessage(cmdMsg); //发送数据 //第六步:释放所有资源 Device.Dispose();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值