0%
毅种循环

模块 34 - 进程枚举 - NtQuerySystemInformation

恶意软件开发课程 - 进程枚举 - NtQuerySystemInformation


模块 34 - 进程枚举 - NtQuerySystemInformation#

[!IMPORTANT] 本知识库声明

本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示实践心得

  1. 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
  2. 版权归属:原始知识产权归原作者/官方所有。
  3. 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程
  4. 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。

进程枚举 - NtQuerySystemInformation#

简介#

本模块讨论了一种更独特的进程枚举方法,使用 NtQuerySystemInformation,这是一个 syscall(稍后将详细介绍 syscall)。NtQuerySystemInformationntdll.dll 模块导出,因此需要使用 GetModuleHandleGetProcAddress 来获取其地址。

微软关于 NtQuerySystemInformation 的文档 显示它可以返回有关系统的许多信息。本模块的重点是使用它来执行进程枚举。

💡 初学者提示:WinAPI vs NTAPI

  • WinAPI (如 Kernel32.dll):微软官方记录并推荐使用的 API。它们通常只是对底层 NTAPI 的封装。
  • NTAPI (如 Ntdll.dll):直接与内核交互的底层 API。
  • 为什么用 NTAPI?
    1. 绕过 Hook:安全软件通常 Hook 高层的 WinAPI (如 CreateToolhelp32Snapshot),直接调用 NTAPI 可能绕过监控。
    2. 更多信息:NTAPI 通常能提供 WinAPI 无法提供的详细底层信息。
    3. 更难分析:对于不熟悉内核结构的分析人员来说,代码更难理解。

获取 NtQuerySystemInformation 的地址#

如前所述,需要 GetProcAddressGetModuleHandlentdll.dll 中检索 NtQuerySystemInformation 的地址。

NtQuerySystemInformation 参数#

NtQuerySystemInformation 的参数如下所示。

__kernel_entry NTSTATUS NtQuerySystemInformation(
  [in]            SYSTEM_INFORMATION_CLASS SystemInformationClass,
  [in, out]       PVOID                    SystemInformation,
  [in]            ULONG                    SystemInformationLength,
  [out, optional] PULONG                   ReturnLength
);
c
  • SystemInformationClass - 决定函数返回什么类型的系统信息。
  • SystemInformation - 指向接收请求信息的缓冲区的指针。返回的信息将采用根据 SystemInformationClass 参数指定的类型的结构形式。
  • SystemInformationLength - SystemInformation 参数指向的缓冲区的大小,以字节为单位。
  • ReturnLength - 指向 ULONG 变量的指针,该变量将接收写入 SystemInformation 的信息的实际大小。

由于目标是进程枚举,因此将使用 SystemProcessInformation 标志。使用此标志将使函数返回 SYSTEM_PROCESS_INFORMATION 结构数组(通过 SystemInformation 参数),系统中的每个正在运行的进程对应一个结构。

图片
图片

SYSTEM_PROCESS_INFORMATION 结构#

下一步是查看 微软的文档 以了解 SYSTEM_PROCESS_INFORMATION 结构的样子。

重点关注包含进程名称的 UNICODE_STRING ImageName 和进程 ID 的 UniqueProcessId。此外,NextEntryOffset 将用于移动到返回数组中的下一个元素。

由于使用 SystemProcessInformation 标志调用 NtQuerySystemInformation 将返回未知大小的 SYSTEM_PROCESS_INFORMATION 数组,因此需要调用 NtQuerySystemInformation 两次。第一次调用将检索数组大小,用于分配缓冲区,然后第二次调用将使用分配的缓冲区。

预计第一次 NtQuerySystemInformation 调用将失败,并出现 STATUS_INFO_LENGTH_MISMATCH (0xC0000004) 错误,因为传递了无效参数仅仅是为了检索数组大小。

遍历进程#

现在已成功检索数组,下一步是遍历它并访问保存进程名称的 ImageName.Buffer。每次迭代都会将进程名称与目标进程名称进行比较。

要访问数组中 SYSTEM_PROCESS_INFORMATION 类型的每个元素,必须使用 NextEntryOffset 成员。要查找下一个元素的地址,请将前一个元素的地址添加到 NextEntryOffset。如下面的代码片段所示。

// 'SystemProcInfo' 现在将表示数组中的一个新元素
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemProcInfo + SystemProcInfo->NextEntryOffset);
c

释放分配的内存#

在将 SystemProcInfo 移动到数组中的新元素之前,需要保存分配内存的初始地址以便稍后释放。因此,就在循环开始之前,需要将地址保存到一个临时变量中。

// 因为我们将修改 'SystemProcInfo' 指针,我们需要在 while 循环之前保存它的初始值以便稍后释放
pValueToFree = SystemProcInfo;
c

NtQuerySystemInformation 进程枚举#

使用 NtQuerySystemInformation 执行进程枚举的完整代码如下所示。

NtQuerySystemInformation 的未文档化部分#

NtQuerySystemInformation 在很大程度上仍未记录,其中很大一部分仍然未知。例如,注意 SYSTEM_PROCESS_INFORMATION 中的 Reserved 成员。

图片
图片

本模块中提供的代码使用了 SYSTEM_PROCESS_INFORMATION 结构的不同版本。无论如何,微软的版本和模块代码中使用的版本都会导致相同的输出。主要区别在于本模块中使用的结构包含更多信息,而不是包含多个 Reserved 成员的微软受限版本。此外,还使用了另一个版本的 SYSTEM_INFORMATION_CLASS 结构,该结构也比微软的版本有更多的记录。可以通过下面的链接查看这两个结构。

📚 知识扩展:文档的来源

既然是“未文档化”的,这些结构定义是从哪来的?

  • 逆向工程:安全研究员分析 Windows 内核。
  • 泄漏源码:历史上的 Windows 源码泄漏。
  • 符号文件 (PDB):微软发布的调试符号有时包含结构定义。
  • 开源项目:ReactOS(旨在二进制兼容 Windows 的开源 OS)和 System Informer (原 Process Hacker) 是查找这些未文档化结构的宝库。

演示#

下图显示了编译并运行本模块中显示的代码后的输出。目标进程是 notepad.exe(在 Windows 10 上)和 Notepad.exe(在 Windows 11 上)。

图片
图片


🎯 总结#

在本模块中,我们学习了:

  1. NTAPI 枚举:使用 NtQuerySystemInformation (Syscall) 进行进程枚举,这比 WinAPI 更底层,更难被传统的用户模式 Hook 监控。
  2. 处理未文档化结构:如何定义和使用未在官方头文件中完全披露的结构(如 SYSTEM_PROCESS_INFORMATION)。
  3. 内存遍历:如何使用 NextEntryOffset 手动遍历内核返回的结构链表。

💡 关键要点

  • 在恶意软件开发中,直接使用 NTAPI 被称为 “Native API 调用”
  • 它是规避 EDR 的第一步,因为许多 EDR 只能监控 Kernel32/User32 等高层 API。
  • 下一步进化是 “Direct Syscalls”(直接系统调用),完全绕过 ntdll.dll,我们将在后续模块中深入探讨。

📚 下一步学习

下一个模块将介绍 Thread Hijacking - Local Thread Creation。我们将开始探索如何不仅仅是注入代码,而是劫持程序原本的执行流!