商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > C++如何统计进程占用的所有句柄类型 _ 句柄枚举与分析【实战】

C++如何统计进程占用的所有句柄类型 _ 句柄枚举与分析【实战】

  发布于2026-07-06 阅读(0)

扫一扫,手机访问

很多人以为直接用EnumHandleInformation就能拿到进程句柄,但事实并非如此——这个接口是内核态专属,只有驱动或高权限系统工具才碰得到。对于用户态程序而言,真正可行的路径是借助NtQuerySystemInformation,搭配SystemExtendedHandleInformation(适用于Win10 1607及以上版本)或兼容旧版系统的SystemHandleInformation,同时别忘了把SeDebugPrivilege权限和同架构运行条件备齐。

C++如何统计进程占用的所有句柄类型 _ 句柄枚举与分析【实战】

如何用 EnumHandleInformation 获取进程所有句柄(Windows)

Windows 并没有一个公开的API能直接返回所谓的“句柄类型名”。EnumHandleInformation 是Windows 10 1809+才新增的内核态接口,普通用户态程序根本没法直接调用。所以,实际开发中真正管用的,还是走 NtQuerySystemInformation 加上 SystemHandleInformation(虽然已废弃但还能跑),或者更稳定的 SystemExtendedHandleInformation(Win10 1607+开始支持)。

这里有个关键点:你必须拿到 SeDebugPrivilege 权限才能打开目标进程,同时还得确保调用线程拥有调试权限——否则 NtQuerySystemInformation 要么返回 STATUS_ACCESS_DENIED,要么只给你吐出一部分句柄,根本不够用。

  • 首先,得用 OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE, FALSE, pid) 把目标进程打开。
  • 其次,调用前要通过 AdjustTokenPrivilegesSE_DEBUG_NAME 启用上。
  • 最后,SystemExtendedHandleInformation 返回的每个 SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX 结构里,有个 ObjectTypeIndex 字段,但这仅仅是类型索引,不是字符串——要想知道真实的类型名,还得去查系统全局对象类型表(即 ObGetObjectType,但函数不可用;要么靠硬编码映射,要么读取 ntdll!ObTypeIndexTable,不过符号没导出,稳定做法是找已知偏移加特征扫描,或者依赖 Psapi.dllGetProcessImageFileName 做辅助判断)。

为什么 GetFileTypeGetHandleInformation 不能用于句柄类型分类

这两个函数经常被人误用。GetFileType 只对文件或设备句柄有效,碰到事件、互斥体、节(section)、注册表键这些,直接返回 FILE_TYPE_UNKNOWNGetHandleInformation 则只返回标志位(比如 HANDLE_FLAG_INHERIT),和类型信息八竿子打不着。

常见的翻车现场是,遍历句柄时对每个句柄都调用 GetFileType,结果80%都返回 FILE_TYPE_UNKNOWN,然后你傻眼以为“不是文件”,其实人家可能是 EventSection

  • GetFileType 的本质是向对象发送 IRP_MJ_QUERY_INFORMATION,只有 FileObject 类型会回应,其余大多数对象直接撂挑子。
  • GetHandleInformation 读的是句柄表项的 Flags 字段,和对象类型完全不搭。
  • 唯一能间接推断类型的轻量方法,是用 DuplicateHandle 尝试复制为 EVENT_ALL_ACCESSMUTANT_ALL_ACCESS,然后看 GetLastError 返回什么——但这开销大,还容易触发安全软件拦截,只适合离线分析场景。

实战:用 NtQuerySystemInformation 枚举并映射句柄类型(C++ 示例核心逻辑)

下面这段代码可以嵌入任何 Win32 C++ 工程里(记得链接 ntdll.lib,还得自己声明 NtQuerySystemInformation):

// 假设已获取 target_pid 和 hProcess
ULONG size = 0;
NTSTATUS status = NtQuerySystemInformation(SystemExtendedHandleInformation, nullptr, 0, &size);
if (status == STATUS_INFO_LENGTH_MISMATCH) {
    auto info = std::make_unique(size);
    status = NtQuerySystemInformation(SystemExtendedHandleInformation, info.get(), size, &size);
    if (NT_SUCCESS(status)) {
        auto p = reinterpret_cast(info.get());
        for (ULONG i = 0; i < p->NumberOfHandles; ++i) {
            auto h = &p->Handles[i];
            if (h->UniqueProcessId == target_pid) {
                // h->ObjectTypeIndex 是关键:查表得类型名
                const char* type_name = GetTypeNameByIndex(h->ObjectTypeIndex); // 自定义查表函数
                printf("Handle: %p, Type: %s, Attributes: %x\n",
                        (void*)h->HandleValue, type_name, h->HandleAttributes);
            }
        }
    }
}

GetTypeNameByIndex 函数需要维护一个静态数组,对应 Windows 内核中 ObTypeIndexTable 的常见索引(比如 0x19 是 Event,0x1A 是 Mutant,0x1E 是 Section,0x20 是 Key)。这个表在不同 Windows 版本里有微小变化,最好从 ntoskrnl.exe 符号或 WinDbg 中提取最新版,别想着一口气硬编码完 100 多项。

  • 务必要检查 h->HandleValue 是否合法(不能是 0 或 0xffffffff)。
  • h->HandleAttributesHANDLE_FLAG_PROTECT_FROM_CLOSE 位用来识别受保护句柄,别误关了。
  • 另外,枚举结果不包含句柄所指对象的名称(比如文件路径、事件名),要获取名称还得额外调用 NtQueryObject,而且成功率看对象支不支持 ObjectNameInformation

容易被忽略的兼容性与权限陷阱

很多代码在开发机上跑得飞起,一上线就翻车,问题几乎全出在权限和版本适配这些细节上。

  • Windows Server 2016+ 默认不给非 SYSTEM 进程开 SeDebugPrivilege,得在组策略里把调试程序权限启用,或者用 RunAs 启动(但生产环境不推荐这么做)。
  • SystemExtendedHandleInformation 在 Windows 7 上根本不存在,要向下兼容只能退回到 SystemHandleInformation,但返回的结构里没有 ObjectTypeIndex,只能靠对象地址低字节猜类型,准确率不到 70%。
  • 32 位程序在 64 位系统上枚举 64 位进程句柄会直接失败(STATUS_INVALID_HANDLE),必须确保架构一致,或者折腾 WOW64 兼容层加 NtWow64QueryInformationProcess64,复杂得不行,通常直接要求同架构。
  • 杀毒软件或 EDR 常常 Hook NtQuerySystemInformation,返回截断数据或者伪造句柄,这时候就得结合 Toolhelp32 和内存扫描交叉验证。

真正的稳定句柄类型统计,从来不是单次 API 调用就能搞定的——它牵涉权限控制、版本适配、错误容忍和多源验证。别指望写一个 for 循环就能输出完美报表。

本文转载于:https://www.php.cn/faq/2441902.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注