使用 NtQuerySystemInformation 遍历进程信息

在 Windows 操作系统中,了解正在运行的进程的信息对于系统管理和性能优化至关重要。通过遍历系统进程信息,我们可以获取进程的 ID、名称、线程数、句柄数以及各种性能指标,从而帮助我们了解系统的运行状况并进行必要的调整和优化。本文将详细介绍一种通过调用 Windows 系统API来遍历系统进程信息的技术,同时提供一段用 C++ 编写的示例代码。

操作原理

Windows 操作系统提供了一系列API函数,可以用于获取系统状态和信息。其中之一是 NtQuerySystemInformation 函数,该函数可以用来获取各种系统信息,包括进程信息。通过调用 NtQuerySystemInformation 函数,并将系统信息结构体填充到提供的缓冲区中,我们可以获得系统中运行的所有进程的信息。

在示例代码中,首先定义了一系列结构体,用于存储进程和线程的详细信息。然后通过调用 NtQuerySystemInformation 函数来获取系统进程信息,遍历返回的进程信息结构体,逐个获取每个进程的信息,包括进程名、进程ID、句柄数、线程数等,并在控制台输出。

提权后通过 NtQueryInformationThread 获取线程实际起始地址,通过 GetMappedFileName 获取所在模块路径

具体实现

示例代码中使用了以下关键技术:

  • 使用 NtQuerySystemInformation 函数获取系统进程信息。
  • 遍历返回的进程信息结构体,获取每个进程的详细信息、通过 NtQueryInformationThread 获取起始地址,通过 GetMappedFileName 获取所在模块路径(需要足够的访问权限)。
  • 输出获取到的进程信息到控制台。

示例代码解析

以下是示例代码中用到的关键部分:

  • 定义了用于存储进程信息的 _SYSTEM_PROCESS_INFORMATION 结构体和用于存储线程信息的 _SYSTEM_THREAD_INFORMATION 结构体。
  • 使用 NtQuerySystemInformation 函数获取系统进程信息,并遍历返回的进程信息结构体,逐个获取每个进程的信息并输出到控制台。

完整代码

#include 
#include 
#include 

#pragma comment(lib, "psapi.lib")

#ifdef _WIN64
typedef ULONG64 KPRIORITY;
#else
typedef LONG KPRIORITY;
#endif
#define SystemProcessInformation    5 // 功能号

#ifdef _M_IX86
typedef struct _CLIENT_ID
{
    DWORD        UniqueProcess;
    DWORD        UniqueThread;
} CLIENT_ID, * PCLIENT_ID;
#endif // x86模式下

#ifdef _M_X64
typedef struct _CLIENT_ID
{
    ULONG64        UniqueProcess;
    ULONG64        UniqueThread;
} CLIENT_ID, * PCLIENT_ID;
#endif // x64模式下

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} UNICODE_STRING, * PUNICODE_STRING;


typedef enum _KWAIT_REASON
{
    Executive = 0,
    FreePage = 1,
    PageIn = 2,
    PoolAllocation = 3,
    DelayExecution = 4,
    Suspended = 5,
    UserRequest = 6,
    WrExecutive = 7,
    WrFreePage = 8,
    WrPageIn = 9,
    WrPoolAllocation = 10,
    WrDelayExecution = 11,
    WrSuspended = 12,
    WrUserRequest = 13,
    WrEventPair = 14,
    WrQueue = 15,
    WrLpcReceive = 16,
    WrLpcReply = 17,
    WrVirtualMemory = 18,
    WrPageOut = 19,
    WrRendezvous = 20,
    Spare2 = 21,
    Spare3 = 22,
    Spare4 = 23,
    Spare5 = 24,
    WrCalloutStack = 25,
    WrKernel = 26,
    WrResource = 27,
    WrPushLock = 28,
    WrMutex = 29,
    WrQuantumEnd = 30,
    WrDispatchInt = 31,
    WrPreempted = 32,
    WrYieldExecution = 33,
    WrFastMutex = 34,
    WrGuardedMutex = 35,
    WrRundown = 36,
    MaximumWaitReason = 37
} KWAIT_REASON;

typedef enum _THREADINFOCLASS {
    ThreadBasicInformation,
    ThreadTimes,
    ThreadPriority,
    ThreadBasePriority,
    ThreadAffinityMask,
    ThreadImpersonationToken,
    ThreadDescriptorTableEntry,
    ThreadEnableAlignmentFaultFixup,
    ThreadEventPair_Reusable,
    ThreadQuerySetWin32StartAddress,
    ThreadZeroTlsCell,
    ThreadPerformanceCount,
    ThreadAmILastThread,
    ThreadIdealProcessor,
    ThreadPriorityBoost,
    ThreadSetTlsArrayAddress,
    ThreadIsIoPending,
    ThreadHideFromDebugger,
    ThreadBreakOnTermination,
    MaxThreadInfoClass
}THREADINFOCLASS;

// 线程结构体
typedef struct _SYSTEM_THREAD_INFORMATION
{
    LARGE_INTEGER KernelTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER CreateTime;
    ULONG WaitTime;
    PVOID StartAddress;
    CLIENT_ID ClientId;
    KPRIORITY Priority;
    LONG BasePriority;
    ULONG ContextSwitches;
    ULONG ThreadState;
    KWAIT_REASON WaitReason;
} SYSTEM_THREAD_INFORMATION, * PSYSTEM_THREAD_INFORMATION;


// 进程结构体
typedef struct _SYSTEM_PROCESS_INFORMATION
{
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    LARGE_INTEGER WorkingSetPrivateSize; // since VISTA
    ULONG HardFaultCount; // since WIN7
    ULONG NumberOfThreadsHighWatermark; // since WIN7
    ULONGLONG CycleTime; // since WIN7
    LARGE_INTEGER CreateTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER KernelTime;
    UNICODE_STRING ImageName;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    HANDLE InheritedFromUniqueProcessId;
    ULONG HandleCount;
    ULONG SessionId;
    ULONG_PTR UniqueProcessKey; // since VISTA (requires SystemExtendedProcessInformation)
    SIZE_T PeakVirtualSize;
    SIZE_T VirtualSize;
    ULONG PageFaultCount;
    SIZE_T PeakWorkingSetSize;
    SIZE_T WorkingSetSize;
    SIZE_T QuotaPeakPagedPoolUsage;
    SIZE_T QuotaPagedPoolUsage;
    SIZE_T QuotaPeakNonPagedPoolUsage;
    SIZE_T QuotaNonPagedPoolUsage;
    SIZE_T PagefileUsage;
    SIZE_T PeakPagefileUsage;
    SIZE_T PrivatePageCount;
    LARGE_INTEGER ReadOperationCount;
    LARGE_INTEGER WriteOperationCount;
    LARGE_INTEGER OtherOperationCount;
    LARGE_INTEGER ReadTransferCount;
    LARGE_INTEGER WriteTransferCount;
    LARGE_INTEGER OtherTransferCount;
    //SYSTEM_THREAD_INFORMATION Threads[1];
} SYSTEM_PROCESS_INFORMATION, * PSYSTEM_PROCESS_INFORMATION;

typedef struct _THREAD_BASIC_INFORMATION {
    LONG ExitStatus;
    PVOID TebBaseAddress;
    CLIENT_ID ClientId;
    LONG AffinityMask;
    LONG Priority;
    LONG BasePriority;
}THREAD_BASIC_INFORMATION, * PTHREAD_BASIC_INFORMATION;

// 定义函数声明
typedef NTSTATUS (WINAPI* __NtQuerySystemInformation) (
    UINT systemInformation,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
);

typedef NTSTATUS (WINAPI* __NtQueryInformationThread)(
    IN HANDLE ThreadHandle,
    IN THREADINFOCLASS ThreadInformationClass,
    OUT PVOID ThreadInformation,
    IN ULONG ThreadInformationLength,
    OUT PULONG ReturnLength OPTIONAL
);

BOOL QueryProcessInformationCallAll() {
    ULONG cbSize = sizeof(SYSTEM_PROCESS_INFORMATION);
    __NtQuerySystemInformation NtQuerySystemInformation = NULL;
    __NtQueryInformationThread NtQueryInformationThread = NULL;
    PSYSTEM_PROCESS_INFORMATION sysPSIEntry = NULL, 
        sysForwardLinks = NULL;
    HMODULE hNtDll = GetModuleHandleW(L"ntdll.dll");

    if (hNtDll == NULL)
    {
        wprintf(L"LoadLibrary Failed.\n");
        return FALSE;
    }

    NtQuerySystemInformation = (__NtQuerySystemInformation)GetProcAddress(
        hNtDll, "NtQuerySystemInformation");
    NtQueryInformationThread = (__NtQueryInformationThread)GetProcAddress(
        hNtDll, "NtQueryInformationThread");
    
    if (NtQuerySystemInformation == NULL || NtQueryInformationThread == NULL)
    {
        wprintf(L"GetProcAddress Failed.\n");
        return FALSE;
    }

    // 调用 NtQuerySystemInformation 查询进程信息
    NTSTATUS status = 0;
    status = NtQuerySystemInformation(SystemProcessInformation, NULL, 0, &cbSize);
    if (status == 0xC0000004)
    {
        sysPSIEntry = (PSYSTEM_PROCESS_INFORMATION)malloc(cbSize);
        status = NtQuerySystemInformation(SystemProcessInformation, sysPSIEntry, cbSize, &cbSize);
        if (status)
            free(sysPSIEntry);
    }

    // 拷贝结构体指针副本
    sysForwardLinks = sysPSIEntry;

    HANDLE hThread = NULL;
    HANDLE hProcess = NULL;
    LPVOID lpStartAddress = NULL;
    WCHAR  wsFileName[256] = { 0 };
    THREAD_BASIC_INFORMATION tbi = { 0 };
    PSYSTEM_THREAD_INFORMATION threadInfo = nullptr;
    DWORD curThreadIndex = 0;
    // 使用指针副本遍历进程信息
    while (true)
    {
        if (sysForwardLinks->ImageName.Buffer != NULL)
        {
            wprintf(L"进程名:\t%s \t进程ID:%I64u \t句柄总数:%u \t线程总数:%u \n", 
                sysForwardLinks->ImageName.Buffer, (UINT64)sysForwardLinks->UniqueProcessId,
                sysForwardLinks->HandleCount, sysForwardLinks->NumberOfThreads);
            // 打印线程信息
            threadInfo = (PSYSTEM_THREAD_INFORMATION)((UINT64)sysForwardLinks + sizeof(SYSTEM_PROCESS_INFORMATION));
            hThread = NULL; hProcess = NULL; curThreadIndex = 0;
            for (; curThreadIndex < sysForwardLinks->NumberOfThreads; ++curThreadIndex)
            {
                lpStartAddress = NULL;
                memset(wsFileName, 0, sizeof(wsFileName));
                // 打开线程
                hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, (DWORD)threadInfo->ClientId.UniqueThread);
                if (hThread == NULL)
                {
                    wprintf(L"打开线程 %lld 失败。\n", threadInfo->ClientId.UniqueThread);
                }
                else {
                    // 获取线程入口地址
                    NtQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress,
                        &lpStartAddress, sizeof(lpStartAddress), NULL);

                    // 获取线程所在模块
                    tbi = { 0 };
                    NtQueryInformationThread(hThread, ThreadBasicInformation, &tbi, sizeof(tbi), NULL);

                    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)sysForwardLinks->UniqueProcessId);

                    if (hProcess != NULL)
                    {
                        // 检查入口地址是否位于某模块中
                        if (0 == GetMappedFileName(hProcess, lpStartAddress,
                            wsFileName, sizeof(wsFileName) / sizeof(*wsFileName)))
                        {
                            wprintf(L"没有足够的权限来获取模块路径。\n");
                        }
                        CloseHandle(hProcess);
                    }
                    else {
                        wprintf(L"打开进程 %lld 失败。\n", (UINT64)sysForwardLinks->UniqueProcessId);
                    }
                    CloseHandle(hThread);
                }

#ifdef _WIN64
                wprintf(L"\t线程ID:%I64u \t起始地址:0x%I64X \t位于模块:[%ws] \t线程的状态码:%u\n",
                    threadInfo->ClientId.UniqueThread,
                    (UINT64)lpStartAddress,
                    wsFileName,
                    threadInfo->ThreadState);
#else
                wprintf(L"\t线程ID:%d \t起始地址:0x%I32X \t位于模块:[%ws] \t线程的状态码:%u\n",
                    threadInfo->ClientId.UniqueThread,
                    (UINT)lpStartAddress,
                    wsFileName,
                    threadInfo->ThreadState);
#endif
                threadInfo += 1;
            }
            wprintf(L"\n");
        }

        if (sysForwardLinks->NextEntryOffset == 0)
            break;
        // 指针的加减运算的单位是根据所指向数据类型大小的。
        // 字节指针就是1,所以加减运算没问题。
        // 这里是结构体指针,所以必须转成数字类型再运算。
        sysForwardLinks = (PSYSTEM_PROCESS_INFORMATION)((UINT64)sysForwardLinks + sysForwardLinks->NextEntryOffset);
    }
    
    // 释放内存
    free(sysPSIEntry);
    sysPSIEntry = NULL;
    sysForwardLinks = NULL;

    return TRUE;
}

int main()
{
    setlocale(LC_ALL, ".utf8");// 转换控制台代码页编码为 UTF-8
    QueryProcessInformationCallAll();
    return 0;
}

测试结果截图:

使用 NtQuerySystemInformation 遍历进程信息_第1张图片 结果截图

总结

通过遍历系统进程信息,我们可以了解系统中运行的各种进程的详细信息,从而更好地进行系统管理和性能优化。本文介绍了一种通过调用 Windows 系统API来获取并遍历系统进程信息的技术,并提供了一段示例代码以帮助读者理解该技术的实现方法。希望本文对于对 Windows 系统进程信息感兴趣的读者有所帮助。


发布于:2024.02.07,更新于:2024.02.07

你可能感兴趣的:(Windows,基础编程,windows,微软,c语言,c++,visual,studio)