你好,欢迎来到电脑编程技巧与维护杂志社! 杂志社简介广告服务读者反馈编程社区  
合订本订阅
 
 
您的位置:杂志经典 / 计算机安全与维护
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之类的专用工具软件。
  推荐精品文章

·2024年9月目录 
·2024年8月目录 
·2024年7月目录 
·2024年6月目录 
·2024年5月目录 
·2024年4月目录 
·2024年3月目录 
·2024年2月目录 
·2024年1月目录
·2023年12月目录
·2023年11月目录
·2023年10月目录
·2023年9月目录 
·2023年8月目录 

  联系方式
TEL:010-82561037
Fax: 010-82561614
QQ: 100164630
Mail:gaojian@comprg.com.cn

  友情链接
 
Copyright 2001-2010, www.comprg.com.cn, All Rights Reserved
京ICP备14022230号-1,电话/传真:010-82561037 82561614 ,Mail:gaojian@comprg.com.cn
地址:北京市海淀区远大路20号宝蓝大厦E座704,邮编:100089