Win32使用快照、psapi.dll、wtsapi32.dll、ntdll.dll 四种方式实现 —— 枚举进程

天青色,等烟雨
一年一度的虐狗节又要到了,想想自己还是单身一人就莫名的心疼……
走过路过的哥哥姐姐、前辈们,欢迎大家点赞、评论、鼓励 ^ _ ^
Win32使用快照、psapi.dll、wtsapi32.dll、ntdll.dll 四种方式实现 —— 枚举进程_第1张图片


进 程

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.

我们可以在任务管理器中,查看我们当前正在运行的进程:

Win32使用快照、psapi.dll、wtsapi32.dll、ntdll.dll 四种方式实现 —— 枚举进程_第2张图片
上面有着许多和进程相关的信息,比如PID(进程Id)


为什么会有多种枚举方式

枚举也叫做遍历,此文章将列举出四种不同的方式来遍历这些进程,为什么会有这么多种不同的方式呢?我们知道Windows的发展已经有几十年了,每一个版本的更新都有大量API出现,久而久之,就有很多种不同的方式咯……

枚举系统中正在运行的进程 (包含核心API)

  • 通过系统快照进行枚举
    1. CreateToolhelp32Snapshot
    2. Process32First
    3. Process32Next
  • 通过psapi.dll中的函数进行枚举
    1. EnumProcesses
    2. EnumProcessesModule
    3. GetModuleFileNameEx
  • 通过wtsapi32.dll中的函数进行枚举
    1. WTSOpenServer
    2. WTSEnumerateProcess
    3. WTSCloseServer
  • 通过ntdll.dll中的函数进行枚举
    1. ZwQuerySystemInformation

别看最后一种方式只有一个API,但因为wtsapi32.dll的原因(下面会讲到)

因为有多种不同的方式,所有我们也就有了多种选择,写代码的时候尽量选择简单一点的.


枚举正在运行的进程(代码实现)

了解之后,我们来实现一下这四种方式之间的区别
按照习惯每行代码我都会详细的讲解
前面变量部分可以先不看,先看API的使用比较好理解 ^ _ ^

一、通过系统快照进行枚举


#include 
#include 	// 工具帮助头文件  包含所需的头文件
#include 
using namespace std;

int main()
{
    PROCESSENTRY32 processinfo{ 0 };	// 初始化结构
    processinfo.dwsize = sizeof(PROCESSENTRY32);    // 设置结构的大小

    // TH32CS_SNAPPROCES 创建进程的快照 0 并快照当前进程
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);	// 快照所有进程
    if(hSnapshot == INVALID_HANDLE_VALUE) // 判断是否创建快照成功
    {
        cout << "创建快照失败……" << endl;
        return -1;
    }
    
    // 获取第一个进程的信息
    BOOL isSuccess = Process32First(hSnapshot, &processinfo);

    while(isSuccess)
    {
        cout << "Process ID: " << processinfo.th32ProcessID << 
            << " <" << processinfo.szExeFile << "> " << endl;
        
        // 查找下一个
        isSuccess = Process32Next(hSnapshot, &processinfo);	
    }
	
    CloseHandle(hSnapshot);	// 关闭句柄

    return 0;
}

运行结果如图所示:
Win32使用快照、psapi.dll、wtsapi32.dll、ntdll.dll 四种方式实现 —— 枚举进程_第3张图片


二、通过psapi.dll中的函数进行枚举

psapi --> 进程状态API

#include 
#include 	// Psapi.dll 模块用的头文件

#include 
using namespace std;

int main()
{
    HMODULE hMod[1024];	// 保存一个进程里面的所有模块
    DWORD dwIDbuffer[1024];	// 保存所有进程的ID
    DWORD dwSize;	// 保存所有id所占的字节
    DWORD dwCount;	// 所有id的总个数
    DWORD dwMod;	// 保存所有模块所占的字节
    
    // 枚举所有的进程,保存进程id,并判断是否成功
    if(!EnumProcesses(dwIDbuffer, sizeof(dwIDbuffer), &dwSize))
    {
        cout << "EnumProcess error" << endl;
        return -1;
    }

    dwCount = dwSize / sizeof(DWORD);	// 求出有多少个进程
    cout << "Process num: " << dwCount << endl << endl;

    for(int i = 0; i < dwCount; ++i)
    {
        // 打开进程
        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, dwIDbuffer[i]);
        if(hProcess == INVALID_HANDLE_VALUE)	// 判断是否成功
        {
            cout << "ID: " << dwIDbuffer[i] << "  OpenProcess error" << endl;
            continue;
        }
        
        // 枚举这个进程的所有模块
        if(EnumProcessModules(hProcess, hMod, sizeof(hMod), &dwMod))
        {
            cout << "\n Process ID: " << dwIDbuffer[i] << " Open success" << endl;
            
            // 循环每一个模块	
            for(int j = 0; j < dwMod / sizeof(HMODULE); ++j)	// dwMod是内存大小
            {
                CHAR CuMod[1024];	// 保存模块的名称    
                // 获取模块的名称
                if(GetModuleFileNameEx(hProcess, hMod[j], CuMod, sizeof(CuMod))
                {
                    cout << endl << j << ": " << CuMod
                        << "  " << hMod[j] << endl;
                }                
            } 
            cout << endl;
        }
        else
        {
            cout << "EnumProcessModules error: " << dwIDbuffer[i] << endl;
        }
        
        CloseHandle(hProcess);	// 关闭句柄
    }

    return 0;
}

运行结果如图所示:

Win32使用快照、psapi.dll、wtsapi32.dll、ntdll.dll 四种方式实现 —— 枚举进程_第4张图片

但是这种方式对很多的进程都没有访问权限:

Win32使用快照、psapi.dll、wtsapi32.dll、ntdll.dll 四种方式实现 —— 枚举进程_第5张图片

如何提权,可以百度查询,这里就不多讲解(其实我不会……)
实在不行就不用这种方式(这种方式也比较麻烦)


三、通过wtsapi32.dll中的函数进行枚举

Wtsapi --> Windows 终端服务API

#include 
#include 	// Wtsapi.dll 模块用的头文件
#pragma comment(lib, "WtsApi32.lib")

#include 
using namespace std;

int main()
{
    HANDLE hServer = WTSOpenServer("HUAMENG");	// 打开服务终端 参数是当前计算机名
    if(hServer == INVALID_HANDLE_VALUE)	// 判断是否成功
    {
        cout << "打开终端服务失败……" << endl;
        return -1;
    }
    
    PWTS_PROCESS_INFO processinfo;	// 服务进程信息结构体,是个指针
    DWORD count;

    // 枚举进程
    WTSEnumerateProcesses(hServer, 0, 1, &processinfo, &count);

    for(int i = 0; i < count; ++i)	// 循环输出每个进程的信息
    {
        cout << "Process ID: " << processinfo[i].ProcessId <<
            " <" << processinfo[i].pProcessName << "> " << endl;
    }

    WTSCloseServer(hServer);	// 关闭句柄
    
    return 0;
}

运行结果如图所示:

Win32使用快照、psapi.dll、wtsapi32.dll、ntdll.dll 四种方式实现 —— 枚举进程_第6张图片


四、通过ntdll.dll中的函数进行枚举

ntdll.dll 是一个内核级文件,微软并没有提供它的头文件,
所以我们不能直接的去调用它, 需要一些特殊的手段去获取里面的函数
比如获取函数的地址

#include 
#include  // NT安全头文件

#include 
using namespace std;

// 函数指针,拥有4个参数(不能直接调用ntdll.dll的函数,间接调用)
typedef DWORD(WINAPI* ZWQUERYSYSTEMINFORMATION)(DWORD, PVOID, DWORD, PDWORD);

#define SystemProcessesAndThreadsInformation 5	// 代表着进程的一个值

// 需要我们自己定义的一个进程信息结构体(因为我们无法直接使用ntdll.dll中的东西)
// 我们需要的信息只有其中的两个(一个是id,另一个是进程名称)
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_INFOMATION;

int main()
{
    // 获取模块的句柄
    HMODULE hMod = GetModuleHandle("ntdll.dll");
    if(hMod == NULL)
    {
        cout << "GetModuleHandle error" << endl;
        return -1;
    }

    // 获取模块中的函数(此步非常重要)
    ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation =
        (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hMod, 
            "ZwQuerySystemInformation");

    ULONG cbBuffer = 0x100000;	// 内存大小(此值一定要大一点,太小会报错)
    LPVOID pBuffer = NULL;	// 指向开辟的内存的指针

    pBuffer = HeapAlloc(GetProcessHeap(), 0, cbBuffer);	// 在堆中开辟内存    
    if(pBuffer == NULL)
    {
        cout << "HeapAlloc error" << endl;
        return -1;
    }

    // 查询系统信息(这个函数的功能很多,这里我们用来查询进程)
    ZwQuerySystemInformation(SystemProcessesAndThreadsInformation,
        pBuffer, cbBuffer, NULL);

    // 转一下类型为 PSYSTEM_PROCESS_INFOMATION 
    PSYSTEM_PROCESS_INFOMATION pInfo = (PSYSTEM_PROCESS_INFOMATION)pBuffer;
    
    for(;;)
    {
        printf("ProcessID: %d (%ls)\n", pInfo->ProcessId, pInfo->ProcessName.Buffer);
        
        if (pInfo->NextEntryDelta == 0)	// 用这个成员可以判断是否结束
   	    break;
        
        // 这里是需要注意一点的,这样转换可以转到下一个进程的信息
        pInfo = (PSYSTEM_PROCESS_INFOMATION)(((PUCHAR)pInfo) + pInfo->NextEntryDelta);
    }
    
    // 释放内存
    HeapFree(GetProcessHeap(), 0, pBuffer);
    
    return 0;    
}

需要注意的两点:

  • PSYSTEM_PROCESS_INFOMATION pInfo = (PSYSTEM_PROCESS_INFOMATION)pBuffer;
  • pInfo = (PSYSTEM_PROCESS_INFOMATION)(((PUCHAR)pInfo) + pInfo->NextEntryDelta);

运行结果如图所示:

Win32使用快照、psapi.dll、wtsapi32.dll、ntdll.dll 四种方式实现 —— 枚举进程_第7张图片

从这四种不同的方式我们可以看出,第一种和第三种是比较简单方便的


作者:浪子花梦

TIME:2020.02.12

你可能感兴趣的:(Win32)