Windows下如何枚举所有进程

Windows下如何枚举所有进程

Posted on 13:37:00 by 晓月 and filed under Coding, Windows, Windows Mobile

  要编写一个类似于 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 <windows.h>
#include <tlhelp32.h>
#include <stdio.h>

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

  首先使用 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 <windows.h>
#include <stdio.h>
#include <tchar.h>
#include "psapi.h"
#pragma comment(lib,"psapi.lib")

void PrintProcessNameAndID(DWORD processID)
{
TCHAR szProcessName[MAX_PATH] = _T("<unknown>");
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<cProcesses;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 <windows.h>
#include <ntsecapi.h>
#include <stdio.h>

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 <windows.h>
#include <ntsecapi.h>
#include <ntstatus.h>
#include <stdio.h>

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;i<pInfo->NumberOfHandles;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下如何枚举所有进程,布布扣,bubuko.com

时间: 2024-09-29 21:20:48

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

windows下强制杀死tomcat进程

在Windows操作系统中,我们在启动一个tomcat服务器时,经常会发现8080端口已经被占用的错误,而我们又不知道如何停止这个tomcat服务器. 本文将通过命令来强行终止这个已经运行的tomcat进程如下: 1.首先查找到占用8080端口的进程号PID是多少 CMD>netstat -ano | findstr 8080 这个命令输出的最后一列表示占用8080端口的进程号是多少,假设为1234 2.kill掉这个进程 CMD>taskkill /F /PID 1234 这样8080端口就

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

我的占用原因是 SQL Server Reporting Services,停止掉这个服务并设置其为手动启动即可 如果你并没有安装 SQL Server,请参考下文解决 =============================================================================== 突然发现 80 端口被莫名占用,咋一看还是 System 这个进程 又无法结束这货,于是开始慢慢查看到底是什么东西占用了 首先,打开 cmd 跑这一句,80 端口被Sys

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

一个程序指定到单独一个CPU上运行会比不指定CPU运行时快.这中间主要有两个原因:1)CPU切换时损耗的性能.2)Intel的自动降频技术和windows的机制冲突:windows有一个功能是平衡负载,可以将一个线程在不同时间分配到不同CPU,从而使得每一个CPU不“过累”.然而,Inter又有一个技术叫做SpeedStep,当一个CPU没有满负荷运行时自动降频从而达到节能减排的目的.这两个功能实际是冲突的:一个程序被分配到多个CPU协同工作->每个CPU都不是满载->每个CPU都会降频-&g

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

要点击对应PID的进程右键转到服务停止才行.

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

#ifndef PROCESS_STAT_H #define PROCESS_STAT_H #ifdef __cplusplus extern “C” { #endif typedef long long int64_t; typedef unsigned long long uint64_t; /// 获取当前进程的cpu使用率,返回-1失败 int get_cpu_usage(); /// 获取当前进程内存和虚拟内存使用量,返回-1失败,0成功 int get_memory_usage(ui

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

1. 找到指定端口的进程号 c:\devworks\lib\httpd-2.4.10-win32-VC9\Apache24\bin>netstat -ano|findstr "9000" TCP 0.0.0.0:9000 0.0.0.0:0 LISTENING 4916 TCP 127.0.0.1:63189 127.0.0.1:9000 TIME_WAIT 0 TCP 127.0.0.1:63214 127.0.0.1:9000 TIME_WAIT 0 TCP [::]:900

Windows下查看、杀死进程指令

通过Xlib枚举指定进程下所有窗体

在windows系统下如果想要枚举指定进程的窗体,我们可以通过EnumWindows加上自己实现的回调函数进行实现,那么在linux下该如何做呢? 其实也很简单,在linux下,我们可以通过xlib中提供的API进行实现,关于xlib后面会专门写一篇文章讲解. 一.实现思路 从root窗体开始逐层遍历每一个窗体,将这些窗体所属进程与给定进程比较从而进行筛选. 二.实现代码 #include <X11/Xlib.h> #include <X11/Xatom.h> #include &

windows下bat批处理实现守护进程(有日志)

开发部的一个核心程序总是会自己宕机,然后需要手工去起,而这个服务的安全级别又很高,只有我可以操作,搞得我晚上老没法睡,昨晚实在受不了了,想起以前在hp-ux下写的shell守护进程,这回搞个windows下的bat版守护程序吧,当时晚上思路已经很迟钝了,就叫了个兄弟让他写了,上去后运行效果不错,至少昨晚我安心睡了7小时. 早上来把程序改完善一些,增加了记录等功能. 实现: 检查是否有notepad,要用的话就算成自己的进程名,如果进程宕了就过会自动重启(会在当前目录下生成一个start.bat)