要编写一个类似于 Windows 任务管理器的软件,首先遇到的问题是如何实现枚举所有进程。暂且不考虑进入核心态去查隐藏进程一类的,下面提供几种方法。请注意每种方法的使用局限,比如使用这些 API 所需要的操作系统是什么(尤其是是否能在 Windows Mobile 下使用)。

  本文参考用户态枚举进程的几种方法,原文对于每一种方法都给出了完整的代码,被我照抄下来。还有一篇:如何用 Win32 APIs 枚举应用程序窗口和进程。基于我现学现卖的本质,对我演绎的部分请抱着批判的眼光来看,另外代码也没有充分验证。

  使用 ToolHelp API

  ToolHelp API 的功能就是为了获取当前运行程序的信息,从而编写适合自己需要的工具(@MSDN)。它支持的平台比较广泛,可以在 Windows CE 下使用。在 Windows Mobile SDK 的 Samples 里面有一个 PViewCE 的样例程序,就是用这个来查看进程和线程信息的。

  使用方法就是先用 CreateToolhelp32Snapshot 将当前系统的进程、线程、DLL、堆的信息保存到一个缓冲区,这就是一个系统快照。如果你只是对进程信息感兴趣,那么只要包含 TH32CS_SNAPPROCESS 标志即可。

  然后调用一次 Process32First 函数,从快照中获取第一个进程,然后重复调用 Process32Next,直到函数返回 FALSE 为止。这样将遍历快照中进程列表。这两个函数都带两个参数,它们分别是快照句柄和一个 PROCESSENTRY32 结构。调用完 Process32First 或 Process32Next 之后,PROCESSENTRY32 中将包含系统中某个进程的关键信息。其中进程 ID 就存储在此结构的 th32ProcessID。此 ID 传给 OpenProcess API 可以获得该进程的句柄。对应的可执行文件名及其存放路径存放在 szExeFile 结构成员中。在该结构中还可以找到其它一些有用的信息。

  需要注意的是:在调用 Process32First() 之前,要将 PROCESSENTRY32 结构的 dwSize 成员设置成 sizeof(PROCESSENTRY32)。 然后再用 Process32First、Process32Next 来枚举进程。使用结束后要调用 CloseHandle 来释放保存的系统快照。

  以下为参考代码:

#include

  #include

  #include

  void useToolHelp()

  {

  HANDLE procSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, );

  if(procSnap == INVALID_HANDLE_VALUE)

  {

  printf("CreateToolhelp32Snapshot failed, %d ",GetLastError());

  return;

  }

  //

  PROCESSENTRY32 procEntry = {  };

  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 来实现。

  首先使用 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[], cbNeeded, cProcesses;

  unsigned int i;

  if(!EnumProcesses(aProcesses,sizeof(aProcesses),&cbNeeded))

  return;

  cProcesses = cbNeeded/sizeof(DWORD);

  for(i=;i

  PrintProcessNameAndID(aProcesses[i]);

  getchar();

  }

  注意到,此方法由于需要进行 OpenProcess 操作,所以需要一定的权限,当权限不够时,有些进程将不能被打开。下面给出提升权限的相关代码:

  void RaisePrivilege()

  {

  HANDLE hToken;

  TOKEN_PRIVILEGES tp;

  tp.PrivilegeCount = ;

  tp.Privileges[].Attributes = SE_PRIVILEGE_ENABLED;

  if(OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&hToken))

  {

  if(LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[].Luid))

  {

  AdjustTokenPrivileges(hToken,FALSE,&tp,NULL,NULL,);

  }

  }

  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[];

  FILETIME ftCreateTime;

  FILETIME ftUserTime;

  FILETIME ftKernelTime;

  UNICODE_STRING ProcessName;

  DWORD BasePriority;

  DWORD ProcessId;

  DWORD InheritedFromProcessId;

  DWORD HandleCount;

  DWORD Reserved2[];

  DWORD VmCounters;

  DWORD dCommitCharge;

  PVOID ThreadInfos[];

  }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[];

  FILETIME ftCreateTime;

  FILETIME ftUserTime;

  FILETIME ftKernelTime;

  UNICODE_STRING ProcessName;

  DWORD BasePriority;

  DWORD ProcessId;

  DWORD InheritedFromProcessId;

  DWORD HandleCount;

  DWORD Reserved2[];

  DWORD VmCounters;

  DWORD dCommitCharge;

  PVOID ThreadInfos[];

  }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 == )

  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[];

  }SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX;

  #define SystemHandleInformation 0x10 //

  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,,cbBuffer);

  s = ZwQuerySystemInformation(SystemHandleInformation,pBuffer,cbBuffer,NULL);

  if(s == STATUS_INFO_LENGTH_MISMATCH)

  {

  free(pBuffer);

  cbBuffer = cbBuffer * ;

  }

  }while(s == STATUS_INFO_LENGTH_MISMATCH);

  PSYSTEM_HANDLE_INFORMATION_EX pInfo = (PSYSTEM_HANDLE_INFORMATION_EX)pBuffer;

  ULONG OldPID = ;

  for(DWORD i = ;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 函数。

Windows下如何枚举所有进程的更多相关文章

  1. Windows下80端口被进程System占用的解决方法

    最近电脑时不时就发生了80端口被占用的情况,简单百度解决后,当重启电脑的时候又发生被占用的情况.今天非常幸运的是,发生了80端口和8080端口都被占用了情况,忍无可忍决定下定决心解决这个坑爹的问题,经 ...

  2. C++ Windows 下 根据进程名获取进程ID 以及该进程下所有窗口的句柄

    #include <windows.h> #include <stdint.h> #include <tlhelp32.h> #include <stdio. ...

  3. windows下绑定线程(进程)到指定的CPU核心

    一个程序指定到单独一个CPU上运行会比不指定CPU运行时快.这中间主要有两个原因:1)CPU切换时损耗的性能.2)Intel的自动降频技术和windows的机制冲突:windows有一个功能是平衡负载 ...

  4. Windows下80端口被进程System&PID=4占用的解决方法

    我的占用原因是 SQL Server Reporting Services,停止掉这个服务并设置其为手动启动即可 如果你并没有安装 SQL Server,请参考下文解决 =============== ...

  5. SetThreadAffinityMask windows下绑定线程(进程)到指定的CPU核心

    原帖地址:https://www.cnblogs.com/lvdongjie/p/4476766.html 一个程序指定到单独一个CPU上运行会比不指定CPU运行时快.这中间主要有两个原因:1)CPU ...

  6. Windows下用C语言获取进程cpu使用率,内存使用,IO情况

      #ifndef PROCESS_STAT_H #define PROCESS_STAT_H   #ifdef __cplusplus extern “C” { #endif   typedef l ...

  7. Windows下查询指定端口进程,并杀死

    1. 找到指定端口的进程号 c:\devworks\lib\httpd-2.4.10-win32-VC9\Apache24\bin>netstat -ano|findstr "9000 ...

  8. windows 下命令行关闭进程。

    使用 进程名关闭 taskkill /im mspaint.exe /f 使用 进程id 关闭 taskkill /im 12555 /f

  9. windows 下进程池的操作

    在Windows上创建进程是一件很容易的事,但是在管理上就不那么方便了,主要体现在下面几个方面: 1. 各个进程的地址空间是独立的,想要在进程间共享资源比较麻烦 2. 进程间可能相互依赖,在进程间需要 ...

随机推荐

  1. php 生日提醒程序

    <?php   $startdate=time();  //当前时间   $birth="2013-05-13";  // 出生日期  $arr=explode(" ...

  2. 由struts错误使用引发的漏洞,使用参数作为返回的文件路径或文件名,作为返回result 值

    该错误可以导致他人任意访问该路径下的任何文件. struts 文件 <?xml version="1.0" encoding="UTF-8" ?> ...

  3. C# HttpWebRequest与HttpWebResponse详解

    C# HttpWebRequest与HttpWebResponse详解  http://www.codeproject.com/Articles/6554/How-to-use-HttpWebRequ ...

  4. Windows 2008安装SQL 2008图解

    SQL Server 2008是一个重大的产品版本,它推出了许多新的特性和关键的改进,使得它成为至今为止的最强大和最全面的SQL Server版本. 在现今数据的世界里,公司要获得成功和不断发展,他们 ...

  5. Eclipse的安装和java环境变量的设置

    首先准备工作是要下载好Eclipse和java JDK. 必须要注意的是,Eclipse和java JDK必须下载同一位数的版本,即64位同为64位,32位同为32位.否则在安装完成运行Eclipse ...

  6. 编译器问题:运行maven,报错-Dmaven.multiModuleProjectDirectory system propery is not set. Check $M2_HOME environment variable and mvn script match.

    1.新建环境变量M2_HOME 2.指向你的maven安装目录 例如 :M2_HOME=D:\Apps\apache-maven-3.3.9 3.进入Myeclipse进行修改,Window-> ...

  7. VM - Bridge Adapter

    如何让外部可以连到本地的虚拟机. 1. 网络模式 - Bridged Adapter 2. 确保本机插上网线 3. 如果虚拟机是 Windows 8.1, 需要开启如下选项.

  8. 能看到U盘占用内存,但看不到文件

    原因:是u盘感染了病毒 .病毒把U盘里的东西加上了隐藏属性和系统属性. 解决办法:1.在“运行”里面输入:cmd,回车:2.在cmd中进入U盘.比如你的U盘是H盘,就输入:h:,回车:3.进入U盘之后 ...

  9. 在jsp中常用的内置对象(5个)小总结和两种页面跳转方式(服务器端调转、客户端跳转)的区别

    jsp中常用的几个内置对象: 一.request对象 主要作用:  (1)获取请求页面的信息   比如:request.getParameter("参数名");  (2)获取客户端 ...

  10. Xamarin.Android之下拉刷新

    一.前言 当今任何一个App中只要存在列表,基本上都会使用下拉刷新,而身为Xamarin一族的我们自然也不会落后,下面笔者将带领大家在Xamarin下实现Android中的下拉刷新的效果. 二.准备工 ...