本系列教程版权归“i春秋”所有,转载请标明出处。
本文配套视频教程,请访问“i春秋”(www.ichunqiu.com)。
经过前几次的讨论,我们对于这次的U盘病毒已经有了一定的了解,那么这次我们就依据病毒的行为特征,来编写针对于这次U盘病毒的专杀工具。
因为这次是一个U盘病毒,所以我打算把这次的专杀工具换一种形式实现。不再像前几次那样需要被动运行,而是当我们的专杀工具执行后,一旦有U盘插入,就能主动检测U盘内容,如果发现病毒,就将其删除掉,之后检查系统中是否也存在病毒,如果有,也一并清理干净。我们这次的专杀工具需要实现以下几个功能:
1、专杀工具开启后,需要时刻监测是否有U盘插入,如果有,则启动检测机制。
这里可以使用OnDeviceChange()这个消息响应函数,从而对当前系统中新添加的设备进行实时监控,并且通过对这个函数的参数具体内容的判断,就可以实现U盘的监控。
2、查找U盘中是否存在autorun.inf文件,如果有,则解析该文件中的“open”语句后的内容,获取自启动病毒程序的名称。
解析autorun.inf文件的内容,可以使用GetPrivateProfileString()这个函数,通过它,先找到“AutoRun”区段,之后找到该区段下“open”后面的内容,就能够获取自启动的病毒程序的名称。
3、删除autorun.inf文件以及它所要打开的程序(Recycle.exe)。
首先应当计算Recycle.exe的CRC32指纹特征码,保存为全局变量,用于之后病毒程序的匹配。由于病毒创建出来的程序具有系统以及隐藏属性,因此在删除之前,应当先将病毒程序的文件属性设置为NORMAL(普通),这可以通过SetFileAttributes()函数实现。之后再利用DeleteFile()将病毒程序删除。
4、全盘搜索U盘中的exe程序,进行CRC32指纹匹配,删除所有病毒程序,并恢复被隐藏的文件夹。
依据上面计算出的病毒程序的CRC32值,就可以对U盘根目录盘中的所有exe文件进行匹配。毕竟病毒程序本体是没有变化的,只不过是换了一个名称而已。另外,病毒还会将与病毒同名文件夹进行隐藏,由于我们并没有分析过病毒是如何选取这些文件夹的(其实也没必要分析),因此我们只能通过病毒名称来查找被隐藏的文件夹,之后将其属性设置为NORMAL,那么文件夹也就恢复了。
5、解析本地系统的注册表启动项,找到病毒程序的名称以及隐藏位置。
在删除本地系统中的病毒程序之前,我们需要先结束掉病毒进程。由于病毒的名称是依据计算机的特征计算出来的,而我们并没有逆向分析它的算法(其实也没有必要),所以我们并不知道病毒程序在不同的计算机中的进程名称是什么,也就不能直接结束病毒进程。但是这里可以使用一种取巧的方法,那就是病毒会将自身加入到注册表启动项中,因此我们只要解析注册表启动项,就能够获取病毒名称以及隐藏位置。经过我在不同的计算机中的测试,可以知道病毒名称是由六个字符组成的,那么我们只要查找启动项中的名称为六个字符,并且它所启动的程序能够匹配之前计算的CRC32指纹特征,那么我们也就找到了病毒的名称以及隐藏位置。
6、依据上面所找到的病毒名称,结束掉当前正在运行的病毒进程,并删除病毒启动项以及病毒本体。
有了病毒名称,就可以在当前进程中搜索,找到之后,就可以结束掉病毒进程,之后再删除病毒本体。
7、将病毒创建的九个动态链接库文件删除,它们位于临时文件夹中的“E_N4”中。
其实这里即便是不删除这几个动态连库文件,也是可以的,但是为了进行彻底的查杀,我在这里还是需要将它们删除,但是这里需要说明的是,一般来说,文件夹是不能够直接删除的,需要先将文件夹中的内容清空,才能移除文件夹。
纵观以上七项内容可以发现,前四项主要是针对于U盘的修复,而后面三项则是针对于本地系统的修复。接下来我会利用MFC来实现这些功能。
这里新建一个“MFC AppWizard(exe)”工程,工程名称设定为“UVirusKiller”,然后创建一个“基本对话框”,再单击“完成”,接下来使用一个“编辑框”以及两个“按钮”控件完成界面设计:
图1 界面设计
接下来对“编辑框”进行如下设定:
图2
并将其ID改为IDC_LIST。之后为“安全打开U盘”按钮控件创建一个类型为“Control”,名为“m_SafeOpen”的变量:
图3
至此,界面设计结束。其实大家完全可以依据自己的喜好来设计自己的程序界面。
由于程序是在MFC下进行开发的,因此可以使用OnDeviceChange()这个消息响应函数。那么我们的第一步就是先在文件UVirusKillerDlg.cpp中添加消息映射:
图4 添加消息映射代码
接下来在头文件UVirusKillerDlg.h中的protected下,添加消息响应函数的定义:
图5 添加消息响应函数定义
之后在文件UVirusKillerDlg.cpp中添加获取盘符的代码:void CUVirusKillerDlg::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'); } } }
最后在头文件UVirusKillerDlg.h中的public下添加:
图6
需要说明的是,由于程序中使用了DBT_DEVTYP_VOLUME这样以DBT_开头的宏,所以一定要在UVirusKillerDlg.cpp中包含头文件“Dbt.h”。再定义一个字符型全局变量i用于保存可移动磁盘的盘符。并且定义一个CString类型的csTxt用于保存提示内容。至此,判断盘符的程序已经完成。编写完以上程序后,接下来我们还需完善OnDeviceChange()函数。
BOOL CUVirusKillerDlg::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 ) { // 在未对U盘进行查杀之前,令“安全打开U盘”不可用 m_SafeOpen.EnableWindow(FALSE); SetDlgItemText(IDC_LIST, csTxt); //获取盘符名称 GetDriverName(dwData); //显示可移动磁盘的盘符 CString TmpFile; TmpFile.Format("检测到可移动磁盘为:%c\r\n", i + 'A'); csTxt += TmpFile; SetDlgItemText(IDC_LIST, csTxt); // 如果成功获取盘符,则继续执行 if ( DriverName != "" ) { // 创建CString类型的File,令其保存autorun.inf的完整路径 CString File = DriverName; File += "\\autorun.inf"; // 用于保存由autorun.inf所启动的程序名 char szBuff[MAX_PATH] = { 0 }; // 判断可移动磁盘中的autorun.inf文件是否存在 if ( GetFileAttributes(File.GetBuffer(0)) == -1 ) { csTxt += "在可移动磁盘中没有检测到autorun.inf\r\n"; SetDlgItemText(IDC_LIST, csTxt); // 如果当前U盘中不存在autorun.inf,则令“安全打开U盘”按钮可用 m_SafeOpen.EnableWindow(TRUE); return FALSE; } csTxt += "在可移动磁盘中检测到autorun.inf\r\n"; csTxt += "正在解析autorun.inf的启动内容\r\n"; SetDlgItemText(IDC_LIST, csTxt); // 获取AutoRun.inf文件中open后面的内容,即所要自动打开的可疑文件 GetPrivateProfileString( "AutoRun", //The name of the section containing the key name. "open", //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. ); // DelFile保存由autorun.inf启动的程序的路径 CString DelFile = DriverName; DelFile += '\\'; DelFile += szBuff; csTxt += "由autorun.inf启动的程序为:"; csTxt += DelFile; csTxt += "\r\n正在计算病毒程序的哈希值...\r\n"; SetDlgItemText(IDC_LIST, csTxt); // 获取病毒程序的CRC32值 VirusCRC32 = CalcCRC32(DelFile); // 删除可移动磁盘中的autorun.inf以及由之启动的文件,需要首先将病毒程序的属性调整为NORMAL csTxt += "正在删除可移动磁盘中的autorun.inf以及由之启动的程序...\r\n"; SetFileAttributes(File, FILE_ATTRIBUTE_NORMAL); // 删除autorun.inf BOOL bRet = DeleteFile(File); csTxt += File; if (bRet) { csTxt += _T("病毒程序被删除!\r\n"); } else { csTxt += _T("病毒程序无法删除!\r\n"); } SetFileAttributes(DelFile, FILE_ATTRIBUTE_NORMAL); // 删除由autorun.inf启动的病毒程序 bRet = DeleteFile(DelFile); csTxt += DelFile; if (bRet) { csTxt += _T("病毒程序被删除!\r\n"); } else { csTxt += _T("病毒程序无法删除!\r\n"); } SearchAndDeleteVirus(DriverName); csTxt += _T("U盘修复完毕,您现在可以安全地打开U盘或修复本地系统!\r\n"); SetDlgItemText(IDC_LIST, csTxt); //令"安全打开U盘"按钮可用 m_SafeOpen.EnableWindow(TRUE); } } //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; }
void CUVirusKillerDlg::OnBtnRepair() { // TODO: Add your control notification handler code here csTxt += _T("开始检测本地系统...\r\n正在检查注册表启动项...\r\n"); char RegName[]="SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\"; DWORD dwType = 0; DWORD dwBufferSize = MAXBYTE; DWORD dwKeySize = MAXBYTE; char szValueName[MAXBYTE] = { 0 }; char szValueKey[MAXBYTE] = { 0 }; //打开注册表启动项 HKEY hKey = NULL; LONG lRetRun = RegOpenKey(HKEY_LOCAL_MACHINE, RegName, &hKey); if ( lRetRun != ERROR_SUCCESS ) { AfxMessageBox("注册表启动项打开失败!"); return ; } int i = 0; while ( TRUE ) { // 枚举键项 lRetRun = RegEnumValue(hKey, i, szValueName, &dwBufferSize, NULL, &dwType, (unsigned char *)szValueKey, &dwKeySize); if ( lRetRun == ERROR_NO_MORE_ITEMS ) { break; } // 如果键项为6个字符,并且键值指向的程序的CRC32指纹与病毒指纹匹配,则关闭病毒进程以及删除键项与病毒本体 if ( lstrlen(szValueName) == 6 && CalcCRC32(szValueKey) == VirusCRC32 ) { BOOL bRet = FALSE; DWORD dwPid = 0; // 删除病毒的注册表启动项 RegDeleteValue(hKey, szValueName); csTxt += _T("注册表启动项清理完毕!\r\n"); strcat(szValueName, ".EXE"); bRet = FindTargetProcess(szValueName, &dwPid); if(TRUE) { csTxt += _T("检查系统内存...\r\n"); csTxt += _T("系统中存在病毒进程:"); csTxt += szValueName; csTxt += _T("准备进行查杀...\r\n"); SetDlgItemText(IDC_LIST,csTxt); // 提升权限 bRet = EnableDebugPrivilege(SE_DEBUG_NAME); if (bRet == FALSE) { csTxt += _T("提升权限失败\r\n"); } else { csTxt += _T("提升权限成功!\r\n"); } SetDlgItemText(IDC_LIST,csTxt); // 打开并尝试结束病毒进程 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPid); if (hProcess == INVALID_HANDLE_VALUE) { csTxt += _T("无法结束病毒进程\r\n"); return ; } bRet = TerminateProcess(hProcess,0); if (bRet == FALSE) { csTxt += _T("无法结束病毒进程\r\n"); return ; } csTxt += _T("病毒进程已经结束\r\n"); SetDlgItemText(IDC_LIST,csTxt); CloseHandle(hProcess); } break; } //清空缓冲区 ZeroMemory(szValueName,MAXBYTE); ZeroMemory(szValueKey, MAXBYTE); i++; } RegCloseKey(hKey); // 删除自启动的病毒程序 csTxt += szValueKey; // 获取病毒程序所在文件夹的路径 szValueKey[lstrlen(szValueKey)-11] = '\0'; SetFileAttributes(szValueKey, FILE_ATTRIBUTE_NORMAL); BOOL bRet = DeleteDirectory(szValueKey); if (bRet) { csTxt += _T("病毒程序被删除!\r\n"); } else { csTxt += _T("病毒程序无法删除!\r\n"); } // 获取保存有病毒DLL文件的文件夹路径 char tempPath[100]; DWORD dwSize = 100; GetTempPath(dwSize, tempPath); lstrcat(tempPath, "\E_N4"); SetFileAttributes(tempPath, FILE_ATTRIBUTE_NORMAL); // 删除病毒文件夹 bRet = DeleteDirectory(tempPath); csTxt += tempPath; if (bRet) { csTxt += _T("病毒文件夹被删除!\r\n"); } else { csTxt += _T("病毒文件夹无法删除!\r\n"); } SetDlgItemText(IDC_LIST,csTxt); }
至此,所有主要的程序编写完毕,接下来就可以进行专杀工具的测试了。
这里我们需要让本地系统还有U盘保持中毒的状态,先拔出U盘,然后启动专杀工具,使其开始进行监控,之后插入U盘,专杀工具就会自动开始查杀:
图7
此时如果点击“安全打开U盘”,就会打开U盘,可以发现此时U盘中已经没有了病毒程序,并且被隐藏的文件夹也都显示出来了。如果点击“修复本地系统”,那么专杀工具会首先结束掉病毒进程,之后再删除系统中的病毒程序,从而对系统进行彻底的清理。