要编写一个相似于 Windows 任务管理器的软件,首先遇到的问题是如何实现枚举全部进程。暂且不考虑进入核心态去查隐藏进程一类的,下面提供几种方法。请注意每种方法的使用局限,好比使用这些 API 所须要的操做系统是什么(尤为是是否能在 Windows Mobile 下使用)。api
本文参考用户态枚举进程的几种方法,原文对于每一种方法都给出了完整的代码,被我照抄下来。还有一篇:如何用 Win32 APIs 枚举应用程序窗口和进程。基于我现学现卖的本质,对我演绎的部分请抱着批判的眼光来看,另外代码也没有充分验证。数组
使用 ToolHelp API函数
ToolHelp API 的功能就是为了获取当前运行程序的信息,从而编写适合本身须要的工具(@MSDN)。它支持的平台比较普遍,能够在 Windows CE 下使用。在 Windows Mobile SDK 的 Samples 里面有一个 PViewCE 的样例程序,就是用这个来查看进程和线程信息的。工具
使用方法就是先用 CreateToolhelp32Snapshot 将当前系统的进程、线程、DLL、堆的信息保存到一个缓冲区,这就是一个系统快照。若是你只是对进程信息感兴趣,那么只要包含 TH32CS_SNAPPROCESS 标志便可。ui
而后调用一次 Process32First 函数,从快照中获取第一个进程,而后重复调用 Process32Next,直到函数返回 FALSE 为止。这样将遍历快照中进程列表。这两个函数都带两个参数,它们分别是快照句柄和一个 PROCESSENTRY32 结构。调用完 Process32First 或 Process32Next 以后,PROCESSENTRY32 中将包含系统中某个进程的关键信息。其中进程 ID 就存储在此结构的 th32ProcessID。此 ID 传给 OpenProcess API 能够得到该进程的句柄。对应的可执行文件名及其存放路径存放在 szExeFile 结构成员中。在该结构中还能够找到其它一些有用的信息。spa
须要注意的是:在调用 Process32First() 以前,要将 PROCESSENTRY32 结构的 dwSize 成员设置成 sizeof(PROCESSENTRY32)。 而后再用 Process32First、Process32Next 来枚举进程。使用结束后要调用 CloseHandle 来释放保存的系统快照。操作系统
如下为参考代码:线程
#include #include #include void useToolHelp() { HANDLE procSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(procSnap == INVALID_HANDLE_VALUE) { printf("CreateToolhelp32Snapshot failed, %d ",GetLastError()); return; } // PROCESSENTRY32 procEntry = { 0 }; procEntry.dwSize = sizeof(PROCESSENTRY32); BOOL bRet = Process32First(procSnap,&procEntry); while(bRet) { wprintf(L"PID: %d (%s) ", procEntry.th32ProcessID, procEntry.szExeFile); bRet = Process32Next(procSnap, &procEntry); } CloseHandle(procSnap); } void main() { useToolHelp(); getchar(); }
使用 Processing Status API指针
在 Windows SDK 中能够找到 PSAPI,经过 PSAPI 能够获取进程列表和设备驱动列表。经过 EnumProcesses、EnumProcessModules、GetModuleFileNameEx 和 GetModuleBaseName 来实现。code
首先使用 EnumProcesses 来枚举全部进程,它有三个参数:DWORD 类型的数组指针 lpidProcess;该数组的大小尺寸 cb;以及一个指向 DWORD 的指针 cbNeeded,它接收返回数据的长度。DWORD 数组用于保存当前运行的进程 IDs。cbNeeded 返回数组所用的内存大小。下面算式能够得出返回了多少进程:nReturned = cbNeeded / sizeof(DWORD)。
注意:虽然文档将返回的 DWORD 命名为“cbNeeded”,其实是没有办法知道到底要传多大的数组的。EnumProcesses 根本不会在 cbNeeded 中返回一个大于 cb 参数传递的数组值。因此,惟一确保 EnumProcesses 函数成功的方法是分配一个 DWORD 数组,而且,若是返回的 cbNeeded 等于 cb,分配一个较大的数组,并不停地尝试直到 cbNeeded 小于 cb 。
下面是参考代码:
#include #include #include #include "psapi.h" #pragma comment(lib,"psapi.lib") void PrintProcessNameAndID(DWORD processID) { TCHAR szProcessName[MAX_PATH] = _T(""); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS/* | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ*/,FALSE,processID); //Process name. if(NULL!=hProcess) { HMODULE hMod; DWORD cbNeeded; if(EnumProcessModules(hProcess,&hMod,sizeof(hMod), &cbNeeded)) { GetModuleBaseName(hProcess,hMod,szProcessName,sizeof(szProcessName)/sizeof(TCHAR)); } } wprintf(_T("PID: %d (%s) "),processID,szProcessName); CloseHandle(hProcess); } void main( ) { DWORD aProcesses[1024], cbNeeded, cProcesses; unsigned int i; if(!EnumProcesses(aProcesses,sizeof(aProcesses),&cbNeeded)) return; cProcesses = cbNeeded/sizeof(DWORD); for(i=0;i PrintProcessNameAndID(aProcesses[i]); getchar(); } 注意到,此方法因为须要进行 OpenProcess 操做,因此须要必定的权限,当权限不够时,有些进程将不能被打开。下面给出提高权限的相关代码: void RaisePrivilege() { HANDLE hToken; TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if(OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&hToken)) { if(LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[0].Luid)) { AdjustTokenPrivileges(hToken,FALSE,&tp,NULL,NULL,0); } } if(hToken) CloseHandle(hToken); }
使用 Native API
在 使用Native API 探测本机系统信息 中我介绍了 Native API 中的 NtQuerySystemInformation(ZwQuerySystemInformation)。当设置查询的信息类型为 SystemProcessesAndThreadsInformation 时(第5号功能),能够用来枚举全部进程和线程。
提醒:这个函数属于 Undocumented API,而且不建议使用,由于不一样系统的结构和常量有所不一样。下面列出 Windows XP 下能够用的相关结构和常量:
typedef DWORD (WINAPI *ZWQUERYSYSTEMINFORMATION)(DWORD, PVOID, DWORD, PDWORD); typedef struct _SYSTEM_PROCESS_INFORMATION { DWORD NextEntryDelta; DWORD ThreadCount; DWORD Reserved1[6]; FILETIME ftCreateTime; FILETIME ftUserTime; FILETIME ftKernelTime; UNICODE_STRING ProcessName; DWORD BasePriority; DWORD ProcessId; DWORD InheritedFromProcessId; DWORD HandleCount; DWORD Reserved2[2]; DWORD VmCounters; DWORD dCommitCharge; PVOID ThreadInfos[1]; }SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION; #define SystemProcessesAndThreadsInformation 5
而后动态加载 ntdll.dll,得到函数的地址。即可以进行进程的枚举相关代码以下:
#include #include #include typedef DWORD (WINAPI *ZWQUERYSYSTEMINFORMATION)(DWORD, PVOID, DWORD, PDWORD); typedef struct _SYSTEM_PROCESS_INFORMATION { DWORD NextEntryDelta; DWORD ThreadCount; DWORD Reserved1[6]; FILETIME ftCreateTime; FILETIME ftUserTime; FILETIME ftKernelTime; UNICODE_STRING ProcessName; DWORD BasePriority; DWORD ProcessId; DWORD InheritedFromProcessId; DWORD HandleCount; DWORD Reserved2[2]; DWORD VmCounters; DWORD dCommitCharge; PVOID ThreadInfos[1]; }SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION; #define SystemProcessesAndThreadsInformation 5 void main() { HMODULE hNtDll = GetModuleHandle(L"ntdll.dll"); if(!hNtDll) return; ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"ZwQuerySystemInformation"); ULONG cbBuffer = 0x10000; LPVOID pBuffer = NULL; pBuffer = malloc(cbBuffer); if(pBuffer == NULL) return; ZwQuerySystemInformation(SystemProcessesAndThreadsInformation,pBuffer,cbBuffer,NULL); PSYSTEM_PROCESS_INFORMATION pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuffer; for(;;) { wprintf(L"PID: %d (%ls) ",pInfo->ProcessId,pInfo->ProcessName.Buffer); if(pInfo->NextEntryDelta == 0) break; pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo) + pInfo->NextEntryDelta); } free(pBuffer); getchar(); }
对这个方法有问题的,能够参考我以前的那篇介绍 Native API 的文章。
一样使用 ZwQuerySystemInformation 函数,查询类型若是设置为 SystemHandleInformation(第16号功能)也能够达到目的。它能获取系统中全部句柄,再加上进程 ID 的判断就能够枚举全部进程了。
#include #include #include #include typedef NTSTATUS (WINAPI *ZWQUERYSYSTEMINFORMATION)(DWORD, PVOID, DWORD, PDWORD); typedef struct _SYSTEM_HANDLE_INFORMATION { ULONG ProcessId; UCHAR ObjectTypeNumber; UCHAR Flags; USHORT Handle; PVOID Object; ACCESS_MASK GrantedAccess; }SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; typedef struct _SYSTEM_HANDLE_INFORMATION_EX { ULONG NumberOfHandles; SYSTEM_HANDLE_INFORMATION Information[1]; }SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; #define SystemHandleInformation 0x10 //16 void main() { HMODULE hNtDll = LoadLibrary(L"ntdll.dll"); if(!hNtDll) return; ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"ZwQuerySystemInformation"); ULONG cbBuffer = 0x4000; LPVOID pBuffer = NULL; NTSTATUS s; do { pBuffer = malloc(cbBuffer); if(pBuffer == NULL) return; memset(pBuffer,0,cbBuffer); s = ZwQuerySystemInformation(SystemHandleInformation,pBuffer,cbBuffer,NULL); if(s == STATUS_INFO_LENGTH_MISMATCH) { free(pBuffer); cbBuffer = cbBuffer * 2; } }while(s == STATUS_INFO_LENGTH_MISMATCH); PSYSTEM_HANDLE_INFORMATION_EX pInfo = (PSYSTEM_HANDLE_INFORMATION_EX)pBuffer; ULONG OldPID = 0; for(DWORD i = 0;iNumberOfHandles;i++) { if(OldPID != pInfo->Information[i].ProcessId) { OldPID = pInfo->Information[i].ProcessId; wprintf(L"PID: %d ",OldPID); } } free(pBuffer); FreeLibrary(hNtDll); getchar(); }
原文中提到,在进行进程“隐藏”工做的时候,此处的句柄是一件容易被忽略的地方,所以须要注意隐藏由程序打开的相关句柄。因为系统中句柄数量常常变换,因此没有什么必要修改其中的 NumberOfHandles 域,由于若是修改此处的值,则须要不停对句柄的变化进行维护,开销比较大。在用户态下的进程枚举已经变得不可靠,由于一个内核级的 Rootkit 很容易就可以更改这些函数的返回结果。因此进程的可靠枚举应在内核态中实现,能够经过编写驱动来实现。
有关16位程序
根据参考的第二篇文章:在 Windows 95,Windows 98 和 Windows ME 中,ToolHelp32 对待16位程序一视同仁,它们与 Win32 程序同样有本身的进程 IDs。可是在 Windows NT,Windows 2000 或 Windows XP 中状况并非这样。在这些操做系统中,16位程序运行在所谓的 VDM 当中(也就是DOS机)。
为了在 Windows NT,Windows 2000 和 Windows XP 中枚举16位程序,必须使用一个名为 VDMEnumTaskWOWEx 的函数。它的声明包含在 Windows SDK 中的 VDMDBG.h 中,而且须要在项目中连接 VDMDBG.lib 文件。
微软的网上帮助里面有一篇介绍的文章:如何在 Windows NT、 Windows 2000 和 Windows XP 上使用 VDMDBG 函数。