热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

硬件检测:安装、停用

简介现在对于IT的安全来说,热插拨设备是个很大的威胁。在这篇文章中,我将试着开发一个用户应用程序来检测本机系统上的设备改变。例如:插入一个

简介

现在对于IT的安全来说,热插拨设备是个很大的威胁。在这篇文章中,我将试着开发一个用户应用程序来检测本机系统上的设备改变。例如:插入一个USB设备、Ipod、USB无线网卡等等。这个程序同样也可以停用任何支持插拔的设备。在文章的后面,我会简述一下程序的工作原理和它的局限性。

怎么来检测硬件设备的改变?

事实上,Windows操作系统会对上层程序发送WM_DEVICECHANGE消息来通知设备的改变。我们所要作的仅仅是添加一个句柄来处理这个事件。

Collapse

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有些限制:

1 只有顶层窗体的程序才能收到这个消息

2 仅仅串口、磁盘发生改变,才对每个程序广播这个消息

的确不错,至少你可以知道移动U盘、移动硬盘、光盘被安装或弹出了,通过DEV_BROADCAST_VOLUME.dbcv_unitmask你也可以获得其对应的盘符。但实际上,你不知道底层处理的是哪个物理设备实际上被安装到了系统中。

API:RegisterDeviceNotification()

所以,你不得不调用RegisterDeviceNotification()API来注册其他类型的设备改变,或是你的程序仅仅是一个服务程序、没有顶层窗体的程序。例如:如下的例子是用来注册一个设备类型的接口的:

Collapse

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

请注意第8行,NotificationFilter.dbcc_classguid关注的就是你关心的一类设备。

参考这个blog: Doron Holan's blog

一个支持即插即用的设备,有2个不同的GUID相关,一个设备接口GUID, 一个是设备类GUID

设备类GUID:定义了广泛意义上一类设备的GUID,如果你打开设备管理器[我的电脑右键—>设备管理器],默认的是按照“类型”排列的,每一个“类型”就是一个设备类,同时每一个设备类有一个唯一的ID就是设备类GUID。设备GUID定义了此类设备的图标、默认的安全设置、安装属性(例如用户不能手动安装这类设备,而必须通过PNP来遍历),以及其他的设置信息。设备类GUID没有定义对应的I/O接口(请参考术语表),而更像是设备的分组。我认为一个比较好的例子是端口类。串口COM和并口LPT 都是端口类的一部分,但其各有各的I/O接口,而且彼此互不兼容.一个设备仅仅属于一个设备类。我们可以通过设备驱动的INF文件的开头来查看该设备的设备类GUID。

设备接口GUID:定义了相互关联I/O接口的GUID,每一个接口GUID的具体实例都支持基本的I/O设置。设备接口GUID也是对应的驱动程序基于PNP状态来注册、启用、禁用设备。如果需要,一个设备甚至可以注册多个同样GUID的实例(假使每个都有相同的名字)[注:在实际的程序中,多次插拔USB口,确实会驱出相同的串口,例如port12,port12,port12…],尽管在现实世界中完全不需要这样。一个简单的I/O关联接口是键盘设备,每个键盘设备的接口GUID必须相同。

可以通过如下的注册表路径来查看当前设备类GUID, 设备接口GUID:


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

常用设备的接口GUID如下:


设备接口名称

GUID

USB Raw Device/USB设备

{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的解码

如下是修改处理捕获对应事件的函数:

Collapse

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;

    ....

    ....

}

从MSDN中,我们知道

Collapse

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_name知道那个设备安装到了当前系统。J,答案是不对,dbcc_name仅仅是操作系统内部使用来做为ID的,其实不易读的,例如下面的这个dbcc_name:

 

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


  • \\?\USB: USB 意思是这是一个USB设备类
  • Vid_04e8&Pid_053b: Vid/Pid 是一个厂商ID和产品ID(但这是由设备类指定的,USB设备类使用VID/PID,不同的设备类使用不同的命名约定)
  • 002F9A9828E0F06: 不清楚是怎么生成的,是唯一设备ID
  • {a5dcbf10-6530-11d2-901f-00c04fb951ed}:设备接口类GUID

现在,我们来解出设备描述信息或是设备别名,有2种办法:

1 直接读注册表, \\HKLM\SYSTEM\CurrentControlSet\Enum\USB\Vid_04e8&Pid_503b\0002F9A9828E0F06

2 使用 SetupDiXxx 系列API


API:SetupDiXxx()

Windows定义了一组API,让用户通过编程的办法来获取对应的硬件设备信息。例如,我们可以通过dbcc_name来获得设备描述信息或是设备别名。下面是这个办法都具体步骤:

1 首先通过SetupDiGetClassDevs()来获得设备信息集 HDEVINFO,这个操作等同于是一个获取目录句柄的过程。

2 接着使用SetupDiEnumDeviceInfo()来遍历出这个设备信息集内的所有设备,这个操作等同于把目录列表的过程。对于每个遍历出的,我们可以获得SP_DEVINFO_DATA,这个等同于是文件句柄。

3 在上面的枚举过程中,使用SetupDiGetDeviceInstanceId()来读取每个设备的实例ID,这个操作等同于是读文件的属性,一个设备的实例ID类似这个:”USB\Vid_04e8&Pid_503b\0002F9A9828E0F06”,和dbcc_name非常像。

4 如果设备的实例ID等同于dbcc_name,则通过SetupDiGetDeviceRegistryProperty()来获取设备描述信息或是设备别名信息。

程序如下:

Collapse

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座位树节点的tag,当右键单击某一个节点的时候,可以通过调用SetupDiGetClassDevs和SetupDiEnumDeviceInfo来获得所需东西),按照如下的步骤即可禁用一个设备:

1 给SP_PROPCHANGE_PARAMS结构体赋上正确的值

2 把上面赋完值的SP_PROPCHANGE_PARAMS作为参数传入到SetupDiSetClassInstallParams()

3 调用SetupDiCallClassInstaller(),传递参数DIF_PROPEFRTYCHANGE

实际上,DIF也是按位做与运算后兼容的,你也可以去传递不同的DIF参数来调用SetupDiSetClassInstallParams()。 更多信息,请参考MSDN”Handling DIF Codes”

Collapse

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

}

附录:

我使用这个程序,已经多次测试了USB的无线网卡的,插入、拔出测试。

局限性:

1 明显的,必须先运行该程序,才能检测硬件设备。例如:设备在操作系统启动前就已经连接,或者在这个程序运行前的连接都不会被检测。但这个问题,可以通过保存当前系统配置到远程计算机上,等启动完这个程序后再坚持不同的配置来解决

2 我们可以禁用设备,换而言之这也是我们所有能做到。我们不能访问设备底层控制。 我认为可以通过重新用基于内核的过滤驱动来实现,则可以解决这个问题。


推荐阅读
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 基于dlib的人脸68特征点提取(眨眼张嘴检测)python版本
    文章目录引言开发环境和库流程设计张嘴和闭眼的检测引言(1)利用Dlib官方训练好的模型“shape_predictor_68_face_landmarks.dat”进行68个点标定 ... [详细]
author-avatar
望天打股
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有