windows应用程序是由消息(事件)驱动的,任何一个窗体都能接收消息,并可以通过回调函数处理消息。同样,U盘和其它移动设备的插入或者拔出也会有消息对应,这个消息是WM_DEVICECHANGE。若想编程实现对U盘插入拔出的监控,只需要捕获这个消息并对之进行处理就行了。

相关API

WindowProc函数

该函数是一个应用程序定义的函数。它处理发送给窗口的消息。WNDPROC类型定义了一个指向该回调函数的指针。WindowProc是用于应用程序定义函数的占位符。而此处专门用来处理WM_DEVICECHANGE消息。

原型:

LRESULT CALLBACK WindowProc (HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM IParam);

参数:

  • hwnd:指向窗口的句柄
  • uMsg:指定消息类型,这里是WM_DEVICECHANGE消息
  • wParam:指定其余的、消息特定的信息。该参数的内容与UMsg参数值有关。此处可以是Dbt.h头文件中的以下值之一(列举了几个相关的)。
#define DBT_DEVICEARRIVAL               0x8000  // system detected a new device
#define DBT_DEVICEQUERYREMOVE           0x8001  // wants to remove, may fail
#define DBT_DEVICEQUERYREMOVEFAILED     0x8002  // removal aborted
#define DBT_DEVICEREMOVEPENDING         0x8003  // about to remove, still avail.
#define DBT_DEVICEREMOVECOMPLETE        0x8004  // device is gone
#define DBT_DEVICETYPESPECIFIC          0x8005  // type specific event

  • IParam:指定其余的、消息特定的信息。该参数的内容与uMsg参数值有关,格式取决于wParam

DEV_BROADCAST_HDR结构体

struct _DEV_BROADCAST_HDR {     /* */
    DWORD       dbch_size;
    DWORD       dbch_devicetype;
    DWORD       dbch_reserved;
};

typedef struct  _DEV_BROADCAST_HDR      DEV_BROADCAST_HDR;
typedef         DEV_BROADCAST_HDR       DBTFAR *PDEV_BROADCAST_HDR;

成员变量:

  • dbch_size:结构大小(以字节为单位)
  • dbch_devicetype:指定设备类型
#define DBT_DEVTYP_OEM                  0x00000000  // oem定义的设备类型
#define DBT_DEVTYP_DEVNODE              0x00000001  // devnode number
#define DBT_DEVTYP_VOLUME               0x00000002  // 逻辑卷
#define DBT_DEVTYP_PORT                 0x00000003  // 端口设备
#define DBT_DEVTYP_NET                  0x00000004  // 网络资源
#define DBT_DEVTYP_DEVICEINTERFACE      0x00000005  // 设备类
#define DBT_DEVTYP_HANDLE               0x00000006  // 文件系统句柄
  • dbch_reserved:保留

DEV_BROADCAST_VOLUME结构体

该结构体比DEV_BROADCAST_HDR就多了2个成员变量dbcv_unitmaskdbcv_flags

struct _DEV_BROADCAST_VOLUME { /* */
    DWORD       dbcv_size;
    DWORD       dbcv_devicetype;
    DWORD       dbcv_reserved;
    DWORD       dbcv_unitmask;
    WORD        dbcv_flags;
};

typedef struct  _DEV_BROADCAST_VOLUME   DEV_BROADCAST_VOLUME;
typedef         DEV_BROADCAST_VOLUME    DBTFAR *PDEV_BROADCAST_VOLUME;

成员变量:

  • dbcv_size:结构体大小
  • dbcv_devicetype:设备类型(这里和DEV_BROADCAST_HDR用到的一样,此处设为2)
  • dbcv_reserved:保留
  • dbcv_unitmask:标识一个或多个逻辑设备掩码,掩码中的每位对应一个逻辑驱动器。
  • dbcv_flags:此参数可以是以下值之一
含义
DBTF_MEDIA更改影响驱动器的介质。未设置。则更改将影响物理设备或驱动器
DBTF_NET指示逻辑卷是一个网络卷

实现原理

程序主要是对设备的插入和拔出进行监控,所以只要

  • WM_DEVICECHANGE消息回调函数的参数wParam进行判断,判断它是否为DBT_DEVICEARRIVAL(设备已插入)以及DBT_DEVICEREMOVECOMPLETE(设备已移除)操作即可。
  • 然后再重点分析相应操作对应的lParam消息参数里存储的信息数据,从而产生设备盘符消息。

设备已插入操作DBT_DEVICEARRIVAL

此时参数wParam的值是DBT_DEVICEARRIVAL,表示设备已插入操作,接下来便是分析lParam,lParam表示事件特定数据结构体的指针,此处是DEV_BROADCAST_HDR结构体指针。而想要获取设备盘符,则需要以下步骤:

  • 判断lParam dbch_devicetype成员变量的值是否是DBT_DEVTYP_VOLUME(表示该设备是逻辑卷),不是逻辑卷则不会产生盘符。
  • 若是逻辑卷,则lParam实际上是DEV_BROADCAST_VOLUME结构体指针(结构体指针是可以强制类型转换的,参考C语言结构体的强制类型转换),其多出的成员变量dbcv_unitmask可以用来计算盘符。

设备已插入操作DBT_DEVICEARRIVAL

此时参数wParam的值是DBT_DEVICEARRIVAL,表示设备已移除操作。接下来就是获取盘符,该过程和设备插入的过程类似。

示例代码

#include<stdio.h>
#include<Windows.h>
#include<Dbt.h>

#define IDD_DIALOG1      101

LRESULT OnDeviceChange(WPARAM wParam, LPARAM lParam) 
{
	switch (wParam)
	{
		// 设备已经插入
	case DBT_DEVICEARRIVAL:
	{
		PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
		// 逻辑卷
		if (DBT_DEVTYP_VOLUME == lpdb->dbch_devicetype)
		{
			// 根据 dbcv_unitmask 计算出设备盘符
			PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
			DWORD dwDriverMask = lpdbv->dbcv_unitmask;
			DWORD dwTemp = 1;
			char szDriver[4] = "A:\\";
			for (szDriver[0] = 'A'; szDriver[0] <= 'Z'; szDriver[0]++)
			{
				if (0 < (dwTemp & dwDriverMask))
				{
					// 获取设备盘符
					::MessageBox(NULL, szDriver, "设备已插入", MB_OK);
				}
				// 左移1位, 接着判断下一个盘符
				dwTemp = (dwTemp << 1);
			}
		}
		break;
	}
	// 设备已经移除
	case DBT_DEVICEREMOVECOMPLETE:
	{
		PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
		// 逻辑卷
		if (DBT_DEVTYP_VOLUME == lpdb->dbch_devicetype)
		{
			// 根据 dbcv_unitmask 计算出设备盘符
			PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb;
			DWORD dwDriverMask = lpdbv->dbcv_unitmask;
			DWORD dwTemp = 1;
			char szDriver[4] = "A:\\";
			for (szDriver[0] = 'A'; szDriver[0] <= 'Z'; szDriver[0]++)
			{
				if (0 < (dwTemp & dwDriverMask))
				{
					// 获取设备盘符
					::MessageBox(NULL, szDriver, "设备已移除", MB_OK);
				}
				// 左移1位, 接着判断下一个盘符
				dwTemp = (dwTemp << 1);
			}
		}
		break;
	}
	default:
		break;
	}

	return 0;
}


BOOL CALLBACK ProgMainDlg(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
	if (WM_DEVICECHANGE == uiMsg)
	{
		OnDeviceChange(wParam, lParam);
	}
	else if (WM_CLOSE == uiMsg)
	{
		::EndDialog(hWnd, NULL);
	}

	return FALSE;
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevinstance,LPSTR lpCmdLine,int nCmdShow)
{
	::DialogBoxParam(hInstance, (LPCSTR)IDD_DIALOG1, NULL, (DLGPROC)ProgMainDlg, NULL);
	::ExitProcess(NULL);
	return 0;
}

执行结果:
插入U盘
在这里插入图片描述
拔出U盘
在这里插入图片描述

总结

示例程序会监控U盘的插入操作并可以获取加载盘符的根目录,然后用MessageBox显示出来。实际中的恶意代码还可以加上文件夹遍历和文件读取等操作,从根目录中扫描U盘里的文件,进而读取感兴趣的文件内容,甚至是新建一个连接发回。

参考

Windows黑客编程技术详解

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐