Windows检测硬件插入和/或移除

一个用户模式的应用程序,用于检测硬件的添加/删除与WM_DEVICECHANGE 和 RegisterDeviceNotification()

在这里插入图片描述
源代码:https://www.codeproject.com/KB/system/HwDetect/HwDetect_src.zip
演示DEMO:https://www.codeproject.com/KB/system/HwDetect/HwDetect_exe.zip

介绍

热插拔设备现在是IT安全的一大威胁。在本文中,我们将尝试开发一个用户模式应用程序来检测系统上的设备更改,即插入USB驱动器、iPod、USB无线网卡等。该程序还可以禁用任何新插入的设备。我们将对它的工作原理有一个基本的了解,并在本文的结尾讨论它的局限性。

如何检测硬件更改?

事实上,Windows操作系统会在设备更改后发布WM_DEVICECHANGE。我们需要做的就是添加一个处理程序来处理这个事件。

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;
}

但是,默认情况下,Windows操作系统只会将WM_DEVICECHANGE发布到具有顶级窗口的所有应用程序,以及仅在端口和体积发生变化时。

好吧,这还不错,因为至少你知道什么时候挂载/卸载了一个额外的“磁盘”,你可以通过DEV_BROADCAST_VOLUME.dbcv_unitmask. 缺点是您不知道系统中实际插入了什么物理设备。

API:RegisterDeviceNotification()

要获得其他类型设备更改的通知,或者在作为服务运行且没有顶级窗口时获得通知,必须调用RegisterDeviceNotification()API。例如,要在接口更改时获得通知,可以执行以下操作。

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

PnP设备通常与两个不同的GUID相关联,一个设备接口GUID和一个设备类GUID。

设备类GUID定义了广泛的设备类别。如果打开设备管理器,默认视图是“按类型”。每种类型都是一个设备类,其中每个类都是由设备类GUID唯一标识的。设备类GUID定义类的图标、默认安全设置、安装属性(如用户无法手动安装此类的实例,必须由PNP枚举)和其他设置。设备类GUID没有定义I/O接口(请参阅术语表),而是将其视为一组设备。我认为一个很好的例子是Ports类。COM和LPT设备都是Ports类的一部分,但是它们都有各自不同的I/O接口,这些接口彼此不兼容。一个设备只能属于一个设备类。设备类GUID是您在INF文件顶部看到的GUID。

设备接口GUID定义特定的I/O接口协定。预期接口GUID的每个实例都将支持相同的基本I/O集。设备接口GUID是驱动程序将根据PnP状态注册和启用/禁用的。一个设备可以为自己注册多个设备接口,而不限于一个接口GUID。如果需要的话,设备甚至可以注册同一个GUID的多个实例(假设每个实例都有自己的ReferenceString),尽管我从未见过现实世界需要这样做。一个简单的I/O接口契约是键盘设备与原始输入线程的接口。以下是键盘设备接口GUID的每个实例必须支持的键盘设备契约。

您可以在以下注册表中看到设备类和设备接口类的当前列表:

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

通用设备接口类GUID的列表如下所示:

Device Interface Name	GUID
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}

解码 DEV_BROADCAST_DEVICEINTERFACE

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;
    ....
    ....
}

DEV_BROADCAST_DEVICEINTERFACE 结构体定义如下:

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;

通过使用dbcc_uu名称,我们可以知道哪些设备已插入系统。遗憾的是,答案是否定的,dbcc_uu名称是用于操作系统内部使用的,是一个标识,它是不可读的。dbcc_U名称示例如下:

\\?\USB#Vid_04e8&Pid_503b#0002F9A9828E0F06#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
  • \?\USB:USB表示这是一个USB设备类
  • Vid_04e8&Pid_503b:Vid/Pid是VendorID和ProductID(但这是设备类特定的,USB使用Vid/Pid,不同的设备类使用不同的命名约定)
  • 002F9A9828E0F06:似乎是唯一的ID(不确定如何生成)
  • {a5dcbf10-6530-11d2-901f-00c04fb951ed}:设备接口类GUID

现在,通过使用这些解码信息,我们可以通过两种方法获得设备描述或设备友好名称:

  • 直接读取注册表:对于我们的示例dbcc_ame,它将是,\HKLM\SYSTEM\CurrentControlSet\Enum\USB\Vid\u 04e8&Pid\u 503b\0002F9A9828E0F06
  • 使用SetupDiXxx

API:SetupDiXxx()

Windows有一组API,允许应用程序以编程方式检索硬件设备信息。例如,我们可以使用dbcc\u名称获取设备描述或设备友好名称。程序流程大致如下:

  • 使用SetupDiGetClassDevs()获取设备信息集HDEVINFO的句柄,可以将该句柄视为目录句柄。
  • 使用SetupDiEnumDeviceInfo()枚举信息集中的所有设备,可以将此操作视为目录列表。在每次迭代中,我们都会得到一个SP\u DEVINFO\u数据,您可以将这个句柄看作文件句柄。
  • 在枚举过程中,使用SetupDiGetDeviceInstanceId()读取每个设备的实例ID,您可以将此操作视为读取文件属性。实例ID的格式为“USB\Vid\u04e8&Pid\u503b\0002F9A9828E0F06”,与dbcc\u名称非常相似。
  • 如果实例ID与dbcc_name匹配,则调用SetupDiGetDeviceRegistryProperty()来检索描述或友好名称
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);
}

禁用设备

假设您有正确的HDEVINFO和SP_DEVINFO_DATA(实际上,我们将dbcc_name保存为树节点额外数据,并在右键单击设备图标然后调用SetupDiGetClassDevs和SetupDiEnumDevicInfo时检索该数据),禁用设备的流程如下所示:

  • 正确设置SP_prop更改参数结构
  • 调用SetupDiSetClassInstallParams()并传入SP_PROPCHANGE_PARAMS结构
  • 使用DIF_PROPERTYCHANGE调用SetupDiCallClassInstaller()

事实上,DIF代码有点复杂,对于不同的DIF代码,必须使用不同的结构调用SetupDiSetClassInstallParams()。有关更多信息,请参阅MSDN“处理DIF代码”。

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
}

参考:https://www.codeproject.com/Articles/14500/Detecting-Hardware-Insertion-and-or-Removal

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

站长漫谈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值