Windows下如何枚舉全部進程

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

相關文章
相關標籤/搜索