要編寫一個相似於 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 函數。