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