6.14 进程管理器(下) |
|
六、模块的关联信息 1.版本信息 通过查看模块的版本信息,这些对于了解模块的来源和用途十分有用,在编写诸如程序安装、进程管理、病毒检测等软件时,通常需要先检测和判断版本信息。 当在Windows资源管理器里右击一个文件时,会弹出一个标准的文件属性对话框,可以看到文件的多个属性项描述,其实现代码如下。 SHELLEXECUTEINFO sei; ZeroMemory(&sei,sizeof(sei)); sei.cbSize = sizeof(sei); sei.lpFile = szFileName; sei.lpVerb = _T("properties"); sei.fMask = SEE_MASK_INVOKEIDLIST; ShellExecuteEx(&sei); 注意SHELLEXECUTEINFO结构的成员变量lpVerb的赋值,必须指定为"properties"。 为获取模块版本信息的更多细节,本文使用了version.dll的导出函数VerQueryValue,它能够查询更多的文件版本信息。 在调用VerQueryValue之前,要先调用GetFileVersionInfoSize返回目标文件版本信息块的大小,如果存在版本信息块,则返回的值不为0,就可以据此分配缓存区。然后,再调用GetFileVersionInfo将目标文件版本信息块复制到缓存区,之后就可以调用VerQueryValue函数从缓存区检索操作系统信息,以及描述文件版本信息分类项目描述字串。下面的示例代码中,szFilename为包含路径的目标模块名。 char pBuf[MAX_PATH] ={0}, char szLanguageName[MAX_PATH] ={0}; long *pValue = NULL, LangCodePage = 0; DWORD m_dwHandle = 0, m_cbUser = 0; LPVOID m_lpBuffer = NULL, m_lpData = NULL; HANDLE hMemBuffer, hMemBufferData; UINT m_uiDataSize = MAX_PATH; VS_FIXEDFILEINFO vsinfo; m_cbUser = GetFileVersionInfoSize(szFilename, &m_dwHandle); if(m_cbUser == 0) return; else { hMemBuffer = GlobalAlloc(GMEM_MOVEABLE, m_cbUser); m_lpBuffer = GlobalLock(hMemBuffer); hMemBufferData = GlobalAlloc(GMEM_MOVEABLE, m_uiDataSize); m_lpData = GlobalLock(hMemBufferData); if (hMemBuffer) { // 获取版本信息块 GetFileVersionInfo(szFilename, 0, m_cbUser, m_lpBuffer); // 从版本信息块中获取操作系统信息 VerQueryValue(m_lpBuffer, TEXT("\\"), &m_lpData, &m_uiDataSize); MoveMemory((void*)&vsinfo, m_lpData, m_uiDataSize); //根据vsinfo.dwFileType判断模块类型根据vsinfo.dwFileOS判断运行平台 // 获取系统语言和页代码 VerQueryValue(m_lpBuffer, TEXT("\\VarFileInfo\\Translation"), &m_lpData, &m_uiDataSize); pValue = (long *)m_lpData; LangCodePage=LOWORD(pValue[0])<<16 | HIWORD(pValue[0]); // 获取系统语言名称 VerLanguageName(LOWORD(pValue[0]), szLanguageName, MAX_PATH); // 格式化文件版本信息“描述”项检索请求标记 sprintf(pBuf, "%s%08x%s%s", "\\StringFileInfo\\", LangCodePage, "\\", "FileDescription" ); VerQueryValue(m_lpBuffer, pBuf, &m_lpData, &m_uiDataSize); //记录(char *)m_lpData存放的文件版本信息“描述”项字符串; //将sprintf 语句内最后一个参数分别替换为"CompanyName"、 // " LegalCopyright"、"OriginalFilename"、"ProductName"、 // "ProductVersion"、"Comments"、"LegalTrademarks"、 // "PrivateBuild"、"SpecialBuild"、"InternalName"、"FileVersion", // 则得到版本信息各分类项目的描述字串 } GlobalUnlock(hMemBufferData); GlobalFree(hMemBufferData); GlobalUnlock(hMemBuffer); GlobalFree(hMemBuffer); } 上面用到的VerQueryValue原型定义如下: BOOL VerQueryValue(const LPVOID pBlock, LPTSTR lpSubBlock, LPVOID *lplpBuffer, PUINT puLen); 参数pBlock为指向存放文件版本信息块缓存区的指针,lpSubBlock为要检索的文件版本信息分类项目描述字串检索请求标志,分以下几种: “\” - 检索文件类型和操作系统信息的标志。需要将返回的信息块复制到VS_FIXEDFILEINFO结构中。VS_FIXEDFILEINFO结构定义如下: typedef struct VS_FIXEDFILEINFO { DWORD dwSignature; DWORD dwStrucVersion; DWORD dwFileVersionMS; DWORD dwFileVersionLS; DWORD dwProductVersionMS; DWORD dwProductVersionLS; DWORD dwFileFlagsMask; DWORD dwFileFlags; DWORD dwFileOS; DWORD dwFileType; DWORD dwFileSubtype; DWORD dwFileDateMS; DWORD dwFileDateLS; } VS_FIXEDFILEINFO; VS_FIXEDFILEINFO vsinfo, *pvs; “\VarFileInfo\Translation” - 检索文件所用语言代码及名称的标志。 “\StringFileInfo\lang-codepage\string-name” - 检索版本信息分类项目描述字符串的标志。其中,lang-codepage为语言和页代码,string-name为下列描述文件版本信息分类项目的字符串常量之一(注意区分大小写): CompanyName、FileDescription、LegalCopyright、OriginalFilename、ProductName、ProductVersion、Comments、LegalTrademarks、PrivateBuild、SpecialBuild、InternalName、FileVersion 参数lplpBuffer 为指向要获取的文件版本信息分类项目缓存区的指针,参数puLen 为文件版本信息分类项目缓存区的大小。 在检索操作系统及文件版本之前的信息,必须先锁定预分配的全局内存,检索完后解锁。 2.宿主进程 许多用户都曾遇到过这样的情况,杀毒软件提示发现某个病毒文件,却提示文件正在使用无法删除。Unlocker是个很好工具软件,它可以找到调用病毒文件的宿主进程,断开病毒文件与宿主进程的关联。笔者借鉴Unlocker的方法,采用二重循环的方式,外层循环逐个枚举进程,内层循环逐个枚举进程引用的模块列表。若在模块列表中发现给定的带路径模块名,则当前枚举到的那个进程就是给定模块的一个宿主进程。 七、进程和模块管理 1.设置进程优先级和运行权限 通过调用SetPriorityClass函数,其第二个参数可设为IDLE_PRIORITY_CLASS (低)、NORMAL_PRIORITY_CLASS (标准)、HIGH_PRIORITY_CLASS (高)和REALTIME_PRIORITY_CLASS (实时)四个级别,而不是MSDN所述的十三个级别。 运行本文实例程序需要具有管理员权限,否则部分功能受到限制。下面语句设置实例进程的权限为“调试”级别: SetTokenPrivilege(::GetCurrentProcess(), "SeDebugPrivilege"); 2.结束进程 结束进程很简单,先打开进程,获得退出代码,最后调用TerminateProcess强制进程退出。 HANDLE handle; DWORD ExitCode; handle = OpenProcess (PROCESS_TERMINATE, TRUE, ProcessID); GetExitCodeProcess(handle, &ExitCode); TerminateProcess(handle, ExitCode) ; 3.将模块注入到远程进程空间内 将模块(DLL)注入到远程进程空间内,能够实现捆绑而又十分隐蔽,这是许多木马病毒惯用的手法,但许多有名的杀毒软件也采用了此法,如图1下部红色标注的apihookdll.dll,即为木马客星注入Explorer.exe进程空间内的模块。 下面是注入模块的核心代码。在此特别提醒读者朋友,切勿将其用于非法目的。 WCHAR pszLibFilename[MAX_PATH]={0}; DWORD dwID; //szDLLFilenam为模块名,dwRemoteProcessId为被注入进程的ID MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, szDLLFilename, strlen(szDLLFilename), pszLibFilename, MAX_PATH); HANDLE hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwRemoteProcessId ); //允许创建远程线程和远程虚内存写操作 int cbSize = (lstrlenW(pszLibFilename) +1) * sizeof(WCHAR); PWSTR pszLibFileRemote = (PWSTR) VirtualAllocEx( hRemoteProcess, NULL, cbSize, MEM_COMMIT, PAGE_READWRITE); //在远程进程空间为模块名分配空间 int iReturnCode = WriteProcessMemory(hRemoteProcess, pszLibFileRemote, (PVOID)pszLibFilename, cbSize, NULL); //将模块名复制到远程进程空间 PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW"); HANDLE hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL); //创建远程线程,调用注入模块 WaitForSingleObject(hRemoteThread, INFINITE); //清理现场 if (pszLibFileRemote != NULL) ::VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE); if (hRemoteThread != NULL) { ::GetExitCodeThread(hRemoteThread, &dwID); ::TerminateThread(hRemoteThread, dwID); ::CloseHandle(hRemoteThread); } if (hRemoteProcess != NULL) { ::GetExitCodeProcess(hRemoteProcess, &dwID); ::TerminateProcess(hRemoteProcess, dwID); ::CloseHandle(hRemoteProcess); } 实例程序可向单个或多个远程进程空间内注入模块,实际上是将模块名和模块代码段的起始地址写到远程进程空间内,而模块代码段是各个远程进程共享的。 在采用上述方法时,如果DLL模块本身不稳固,在向系统核心进程winlogon.exe和csrss.exe注入的过程中(smss.exe进程拒绝注入),通常会因导致系统重启,这就是为什么有些病毒入侵后,会反复导致系统重启的主要原因。考虑到这一点,实例程序在注入模块时,特意跳过winlogon.exe和csrss.exe进程。 有时会出现无法向目标进程空间注入模块的情形,一般是因为目标进程受到了保护,如木马克星就会采用拦截API的方式,来保护Explorer.exe进程不会被注入非法模块。 4.从远程进程空间内卸载模块 知道了将模块注入远程地址空间的过程,也就不难将模块从远程进程空间内卸载掉,有名的IceSword和Unlocker都具有此功能。下面是卸载模块的核心代码。 //ProcessID为远程进程ID, hRemoetMoudle为模块代码段在目标进程空间的起始地址 HANDLE hThread; HANDLE hProcess=OpenProcess( PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION| PROCESS_VM_WRITE,FALSE, ProcessID); if(!hProcess) return; hThread=CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)FreeLibrary, (LPVOID)hRemoetMoudle, 0, NULL); //创建卸载操作的远程线程 WaitForSingleObject(hThread, INFINITE); VirtualFreeEx(hProcess, &hRemoetMoudle, 0, MEM_RELEASE); //卸载模块 ::CloseHandle(&hRemoetMoudle); //清理现场 if (hThread != NULL) { ::GetExitCodeThread(hThread, (DWORD *)&ProcessID); ::TerminateThread(hThread, ProcessID); ::CloseHandle(hThread); } if (hProcess != NULL) { ::GetExitCodeProcess(hProcess, (DWORD *)&ProcessID); ::TerminateProcess(hProcess, ProcessID); ::CloseHandle(hProcess); } 与注入过程类似,在卸载系统核心进程winlogon.exe和scrss.exe空间内的模块时,有可能会导致系统重启;在卸载Exploer.exe进程空间内的模块时,通常会导致Exploer.exe关闭后重新运行。 实例程序可从单个或多个远程进程空间内卸载模块。有时会出现无法卸载的情形,一般是因为目标模块受到了保护,如木马克星就会采用拦截API的方式,来保护自身注入Explorer.exe进程空间的模块不会被卸载。 5.注册/注销模块 Windows提供的Regsvr32.exe用来注册/注销模块,它是通过调用模块内部的DllRegisterServer/DllUnregisterServer接口来实现的,实现的关键代码如下: // pszDllName为要注册/注销的模块名 // regFunction为函数名称DllRegisterServer或DllUnregisterServer HINSTANCE hLib = LoadLibrary(pszDllName); if (hLib < (HINSTANCE)HINSTANCE_ERROR) return; FARPROC lpDllEntryPoint = GetProcAddress(hLib, regFunction); if(lpDllEntryPoint != NULL) (*lpDllEntryPoint)(); FreeLibrary(hLib); 八、枚举驱动及服务 WindowsXP/2000的服务分两类:一类是Win32应用服务(一般用无窗口的.EXE类程序启动),另一类是内核服务(一般用.SYS类程序驱动,也就是通常所说的驱动程序)。 可能是考虑到安全性,WindowsXP/2000自带的服务管理器只提供Win32应用服务管理功能,这让不少采用内核服务(底层驱动)方式的木马病毒钻了空子,用户采用常规手段根本无法清除这些病毒。 本文实例程序不仅能管理Win32应用服务,也能管理内核服务,比WindowsXP/2000自带的服务管理器功能强大,可以有效地查杀采用底层驱动的木马病毒。 1.使用PSAPI枚举驱动设备 PSAPI提供EnumDeviceDrivers用于枚举系统内核驱动设备,与枚举进程和模块的过程类似,但获取得信息较少,下面的是实现的代码: PVOID aDrivers[1024]; DWORD cbNeeded; char szBaseName[MAX_PATH]={0}; char szDriverFileName[MAX_PATH]={0}; unsigned i; if ( !EnumDeviceDrivers( aDrivers, sizeof(aDrivers), &cbNeeded)) return; DWORD cDrivers = cbNeeded / sizeof(aDrivers[0]); for (i = 0; i < cDrivers; i++ ) { if (GetDeviceDriverBaseName( aDrivers[i], szBaseName, sizeof(szBaseName))) { GetDeviceDriverFileName( aDrivers[i], szDriverFileName, sizeof(szDriverFileName)); …… } } 2.使用注册表枚举服务 WindowsXP/2000的服务设置存储在注册表中,通过读取注册表中的HKEY_LOCAL_MACHINESYSTEM\SYSTEM\CurrentControlSet\Services分支即可获得所有服务的以下各项参数: DisplayName、Description、ImagePath、ObjectName、DependOnGroup、Start、Group、DependOnService、Type。 以上各参数字面意思不难理解,在此只对Start和Type参数含义作些解释。Start指服务的启动方式,有自动、手工、禁用和系统I/O(仅限于内核服务) 四种。Type指服务的类型,有独立进程服务和共享进程服务两种。 对于采用类似\%SystemRoot%\system32\svchost.exe –k netsvcs 方式通过svchost.exe启动的Win32应用服务,还要在注册表中查找对应的服务模块,例如,要查找AudioSrv的服务模块,就需要读注册表中的以下分支: HKEY_LOCAL_MACHINESYSTEMSYSTEM\CurrentControlSet\Services\AudioSrv\Parameters。 从注册表中获得的内核服务项目和数量,与利用PSAPI的EnumDeviceDrivers函数获得的内核服务项目和数量会略有差异,几个最核心的内核服务在上述注册表分支中是找不到的。 九、服务管理 服务管理内容包括服务的安装、启动、停止、删除、启动方式设置等,在此只介绍后四项功能的实现方法。 1.启动服务 Windows提供了服务管理器接口,具有启动服务、停止服务、删除服务、设置服务启动方式的功能。下面的代码演示了启动AudioSrv服务的过程:先调用OpenSCManager函数打开服务管理器,再调用OpenService函数打开指定服务的句柄,最后调用StartService函数启动AudioSrv服务。启动服务后,要及时释放服务管理器的句柄。 OpenService函数的第三个参数可设为SERVICE_START或SERVICE_STOP,分别指定启动服务或停止服务的操作。 SC_HANDLE hSCManager= OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS); if(hSCManager != NULL ) { SC_HANDLE hService = OpenService( hSCManager,"AudioSrv", SERVICE_START); if(hService) if(!StartService(hService, 0, NULL)) …… CloseServiceHandle(hService); CloseServiceHandle(hSCManager); } 2.停止服务 与启动服务的代码类似,不过要调用ControlService函数来停止服务。下面的代码演示了停止AudioSrv服务的过程。 SC_HANDLE hSCManager= OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS); if(hSCManager != NULL ) { SERVICE_STATUS svrstatus; SC_HANDLE hService = OpenService( hSCManager, "AudioSrv", SERVICE_STOP); if(hService) if(!ControlService(hService, SERVICE_CONTROL_STOP, &svrstatus)) …… CloseServiceHandle(hService); CloseServiceHandle(hSCManager); } 3.删除服务 也与启动服务的代码类似,直接调用DeleteService函数即可删除服务。下面的代码演示了删除AudioSrv服务的过程。 SC_HANDLE hSCManager= OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS); if(hSCManager != NULL ) { SC_HANDLE hService = OpenService( hSCManager, "AudioSrv", DELETE); if(hService) if(!DeleteService(hService)) …… CloseServiceHandle(hService); CloseServiceHandle(hSCManager); } 4.设置服务启动方式 设置服务的启动方式需要修改注册表中服务的Start键值(DWORD类型),值1、2、3、4分别对应系统I/O(仅限于内核服务)、自动、手工和禁用四种启动方式。下面代码演示了将AudioSrv服务设置为自动启动方式的过程。 HKEY hRootKey=HKEY_LOCAL_MACHINE; HKEY hKey; char szKeyName[MAX_PATH]={"SYSTEM\\CurrentControlSet\\Services\\AudioSrv"}; BYTE *pVal=(BYTE *)&dwModel; DWORD i = RegOpenKey(hRootKey, szKeyName, &hKey); if(i == ERROR_SUCCESS) RegSetValueEx(hKey, "Start", 1, REG_DWORD, pVal, sizeof(DWORD)); RegCloseKey(hKey);” 十、端口扫描 实例程序的端口扫描功能与Windows自带的Netstat功能类似,只是存在一个问题,那就是在WindowsXP下扫描正常,但在Windows2000下扫描却无效,笔者至今也没有找到有效的解决办法。 端口扫描功能的实现,最主要的是利用了iphlpapi.dll的两个导出函数AllocateAndGetTcpExTableFromStack和AllocateAndGetUdpExTableFromStack,两者分别用于获取TCP和UDP端口的连接信息。 十一、窗口扫描 相信许多读者都遇到过这样的情况,IE浏览器的主页设置按钮被禁用无法使用,这一般是恶意网站所为。 本文实例程序可以恢复IE浏览器的主页设置按钮,例如,恢复设置空白页的过程如下: 运行IE浏览器→点击"工具"→"Internet 选项"→运行实例程序→点击"窗口扫描"→在窗口列表中依次展开"Internet 选项"→"常规"→选定"使用空白页"→点击"恢复"即可。 1.使用递归法扫描窗口 图6所示的窗口列表通过递归法枚举扫描获得,它与文件目录类似,呈树状结构。下面列出实例程序中递归法扫描窗口函数BTSearchWindow的代码,参数hWnd为扫描起始的一个根节点即父窗口,szClass为窗口的类名,szName为窗口的标题。 BOOL CSearchWindow::BTSearchWindow(HWND hWnd, CString, CString) { HWND hWndChild, hWndNext; if( !hWnd ) return FALSE; this->InsertTreeItem(hWnd); //插入父窗口 if(IsFound(hWnd, szClass, szName)) //判断是否指定窗口 { hChild=hWnd; return TRUE; } hWndChild=::GetWindow(hWnd, GW_CHILD); //扫描子窗口 branch=TRUE; BTSearchWindow(hWndChild, szClass, szName); hWndNext=::GetWindow(hWnd, GW_HWNDNEXT); //扫描兄弟窗口 branch=FALSE; hTreeItem=m_WndTree.GetParentItem(hTreeItem); BTSearchWindow(hWndNext, szClass, szName); return TRUE; } 在调用BTSearchWindow时,hWnd、szClass和szName可选。一般宜调用GetDesktopWindow获得桌面窗口的句柄作为扫描起始根节点。如指定szClass和szName,则查找到匹配窗口后停止。 2.使用鼠标捕捉窗口 SPY++具有鼠标捕捉窗口功能,其原理很简单,先进行屏幕坐标转换,然后调用WindowFromPoint函数,跟踪鼠标光标并判断其在哪个窗口内。 ClientToScreen(&point); ::GetCursorPos(&point); CWnd *pWnd=WindowFromPoint(point); if(pWnd) { if(GetWindowThreadProcessId(pWnd->m_hWnd, NULL) != GetWindowThreadProcessId(this->m_hWnd, NULL)) //判断是否捕捉程序自身的窗口 } 实例程序可列出注册表中12个分支的启动项设置。在执行删除前,勾选"Invers"可备份删除的键值,需要时可用添加命令恢复。添加操作只支持REG_SZ、REG_MULTI_SZ和REG_DWORD三种类型键值设置。 十二、用实例程序查杀木马病毒 实例程序运行在WindowsXP/2000环境下,用户需具有管理员权限。实例程序界面为对话框,除使用按钮外,更多功能需在列表框上按右键使用快捷菜单调出。 查杀木马病毒时,先观察有无可疑进程,再查看进程引用的模块。在查看模块时,要特别要留心红色标记的模块,凡是来历不明的大都是注入进程内部的病毒模块。当出现进程无法终止或模块无法卸载的现象时,有可能是对应的服务处于活动状态,可停止服务后再尝试终止进程或卸载模块,必要时在安全模式下运行。查看启动项设置、扫描端口和窗口,有助于发现一些隐藏的进程或注入的模块。 在使用文件删除功能时,要特别小心,文件一旦被删除便无法恢复,除非使用Recover4All之类的专用工具软件。
|
|
|
|
|
|
|
|
|
|
|
|
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn
|
|
|
|
|
|