一、前言
我在上一篇文章中讨论了如何制作AutoRun.inf免疫程序,虽然这个免疫程序可以对所有的盘符有效,但是其实主要还是针对于U盘来进行防护的。由于目前新版的操作系统已经基本不支持AutoRun.inf,因此一般来说我们无需特别地关注这个问题。作为对U盘防御研究的收尾,这次我所讨论的是制作一个U盘防火墙。通过这个防火墙,当检测到有U盘插入时,则会产生提示,并且自动检查U盘中是否有AutoRun.inf文件,进行解析再进行删除操作,通过这个防火墙就可以安全地打开U盘。
其实现在很多杀毒软件都集成了这样的功能,当用户插入U盘后,杀软就会检测U盘的盘符,然后自动进行病毒木马的扫描,如下图所示:
图1 杀毒软件对U盘进行检测
我所制作的U盘防火墙虽然没有商业软件这么漂亮,功能也没有那么齐全,但是所运用的原理是相似的。而且随着讨论的深入,我的这些安全工具也会不断地进化,相信在不久的将来,其功能也能够基本达到专业软件的效果。
二、界面的制作
这个程序我依旧使用MFC制作,只需要在界面中添加一个按钮控件即可,如下图所示:
图2 界面的制作
盘符直接用一个对话框显示,无需在程序界面中画出来。只要在界面中设置一个“安全打开U盘”的按钮控件即可。为方便之后的操作,还需为按钮控件创建一个类型为“Control”,名称为“m_SafeOpen”的变量。
三、编写显示U盘盘符的代码
由于程序是在MFC下进行开发的,因此可以使用OnDeviceChange()这个消息响应函数。这里先来简单讲一下“映射机制”:
MFC使用一种消息映射机制来处理消息,在应用程序框架中的表现就是一个消息与消息处理函数一一对应的消息映射表,以及消息处理函数的声明和实现等代码。当窗口接收到消息时,会到消息映射表中查找该消息对应的消息处理函数,然后由消息处理函数进行相应的处理。SDK编程时需要在窗口过程中一一判断消息值进行相应的处理,相比之下MFC的消息映射机制要方便好用的多。
那么我们的第一步就是先在文件UFirewallDlg.cpp中添加一个消息映射:
BEGIN_MESSAGE_MAP(CUFireWallDlg, CDialog) //{{AFX_MSG_MAP(CUFireWallDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_BTN_SAFEOPEN, OnBtnSafeopen) ON_MESSAGE(WM_DEVICECHANGE, OnDeviceChange) //}}AFX_MSG_MAP END_MESSAGE_MAP()
接下来在头文件UFirewallDlg.h中的protected下,添加消息响应函数的定义:
// Generated message map functions //{{AFX_MSG(CUFireWallDlg) virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg BOOL OnDeviceChange(UINT nEventType, DWORD dwData); afx_msg void OnBtnSafeopen(); //}}AFX_MSG
之后添加获取盘符的代码:
void CUFireWallDlg::GetDriverName(DWORD dwData) { PDEV_BROADCAST_HDR pDevHdr = (PDEV_BROADCAST_HDR)dwData; //如果设备类型为DBT_DEVTYP_VOLUME,则把当前结构体转换为 //DBT_DEVTYP_VOLUME类型的结构体 if ( pDevHdr->dbch_devicetype == DBT_DEVTYP_VOLUME ) { //结构体转换 PDEV_BROADCAST_VOLUME pDevVolume = (PDEV_BROADCAST_VOLUME)pDevHdr; //如果pDevVolume->dbcv_flags为0表示为可移动磁盘 if ( pDevVolume->dbcv_flags == 0 ) { //通过将pDevVolume->dbcv_unitmask移位来判断逻辑盘符, //第0位表示A盘,第1位表示B盘,依此类推。 DWORD dwUnitmask = pDevVolume->dbcv_unitmask; //最多循环移动26位,因为至多有26位 for (i = 0; i < 26; ++i) { //因为新插入的可移动设备一定会是最后一个盘符的后一个, //所以这里寻找dwUnitmask中的最低位数值为0x1的位。 if ( dwUnitmask & 0x1) { //找到则跳出循环 break; } //没找到则继续移位寻找 dwUnitmask = dwUnitmask >> 1; } //如果循环完26位依旧没找到,则返回 if ( i >= 26 ) { return ; } //格式操作转化为字符串 DriverName.Format("%c:", i + 'A'); } } }
最后在头文件UFirewallDlg.h中的public下添加:
void GetDriverName(DWORD dwData); CString DriverName;
需要说明的是,由于程序中使用了DBT_DEVTYP_VOLUME这样以DBT_开头的宏,所以一定要包含头文件“Dbt.h”。再定义一个字符型全局变量i用于保存可移动磁盘的盘符。至此,判断盘符的程序已经完成。编写完以上程序后,程序还不能运行的,因为我们还需完善OnDeviceChange()函数。
四、检测U盘中的AutoRun.inf文件
这里不再进行讲解,我已经在代码中添加了足够的注释:
BOOL CUFireWallDlg::OnDeviceChange( UINT nEventType, //An event type. DWORD dwData //The address of a structure that contains event-specific data. ) { //The system broadcasts the DBT_DEVICEARRIVAL device event when //a device or piece of media has been inserted and becomes available. if ( nEventType == DBT_DEVICEARRIVAL ) { //获取盘符名称 GetDriverName(dwData); //显示可移动磁盘的盘符,由于这里不是为了调试而显示,所以不使用AfxMessageBox()函数 CString TmpFile; TmpFile.Format("检测到可移动磁盘为:%c", i + 'A'); MessageBox(TmpFile); //如果成功获取盘符,则继续执行 if ( DriverName != "" ) { //令"安全打开U盘"按钮可用 m_SafeOpen.EnableWindow(TRUE); //创建CString类型的File,令其保存AutoRun.inf的完整路径 CString File = DriverName; File += "\\AutoRun.inf"; char szBuff[MAX_PATH] = { 0 }; //判断可移动磁盘中的AutoRun.inf文件是否存在,不存在则令"安全打开U盘"按钮不可用 if ( GetFileAttributes(File.GetBuffer(0)) == -1 ) { m_SafeOpen.EnableWindow(FALSE); return FALSE; } //获取AutoRun.inf文件中open后面的内容,即所要自动打开的可疑文件 GetPrivateProfileString( "AutoRun", //The name of the section containing the key name. "shell\\auto\\command", //The name of the key whose associated string is to be retrieved. NULL, //A default string. szBuff, //A pointer to the buffer that receives the retrieved string. MAX_PATH,//The size of the buffer pointed to by the lpReturnedString parameter, in characters. File.GetBuffer(0) //The name of the initialization file. ); //保存由AutoRun.inf启动的程序的路径 CString DelFile = DriverName; DelFile += szBuff; CString str; str = "请选择是否删除可疑文件:"; str += szBuff; if ( MessageBox(str, NULL, MB_YESNO) == IDYES ) { //删除可移动磁盘中的AutoRun.inf以及由之启动的文件 DeleteFile(File); DeleteFile(DelFile); } } } //The system broadcasts the DBT_DEVICEREMOVECOMPLETE device event //when a device or piece of media has been physically removed. else if ( nEventType == DBT_DEVICEREMOVECOMPLETE ) { //当U盘被拔出时,令"安全打开U盘"按钮不可用 m_SafeOpen.EnableWindow(FALSE); } return TRUE; }
程序可以检测U盘中是否存在AutoRun.inf文件,如果存在,则对其进行解析,找到其中所包含的欲运行的可疑程序,询问用户是否删除,若用户同意,则将二者删除。
五、编写按钮控件代码
由于使用了按钮控件的特殊效果,因此这里需要给按钮控件添加声明。找到文件UFirewallDlg.cpp,在BOOL CUFirewallDlg::OnInitDialog()中添加:
// TODO: Add extra initialization here m_SafeOpen.EnableWindow(FALSE);
最后添加按钮控件代码:
void CUFireWallDlg::OnBtnSafeopen() { // TODO: Add your control notification handler code here ShellExecute( NULL, //指定父窗口句柄 "open", //指定动作, 譬如: open、print、edit、explore、find DriverName.GetBuffer(0), //指定要打开的文件或程序 NULL, //给要打开的程序指定参数; 如果打开的是文件这里应该是NULL NULL, //缺省目录 SW_SHOW //打开选项 ); }
至此所有代码编写完毕,接下来就是进行测试。
六、软件运行测试
为了测试我们的程序,这里我首先在U盘中编写一个AutoRun.inf程序:
[autorun] shell\auto\command=Hacked.exe
然后放入我之前用于模拟病毒的对话框程序(详见《反病毒攻防研究》系列,或者建立一个空文件,命名为“Hacked.exe”也可)。之后运行U盘防火墙程序,再插入U盘,程序就能够检测到盘符:
图3 程序检测到盘符
单击“确定”按钮后,由于程序检测到了U盘中包含有“AutoRun.inf”程序,于是解析该程序中欲启动文件的文件名,之后弹出对话框,询问用户是否删除可疑文件:
图4 询问是否删除可疑文件
这里点击“是(Y)”以后,程序就会删除掉U盘中的AutoRun.inf文件以及欲由之运行的恶意程序。最后可以单击“安全打开U盘”按钮,来安全地打开U盘。
七、小结
由以上的分析可见,微软为我们提供了非常丰富的API函数以实现各种各样的功能。我们在实际的编程中,奉行的其实就是“拿来主义”,所以平时就需要多多积累。有一点需要说明的是,对于AutoRun.inf来说,为了实现恶意程序的启动,可能会使用“open”指令,如果这样,只要稍加改动上述程序即可。我也希望上述程序能够起到抛砖引玉的效果。