ZwQuerySystemInformation通过PsLoadedModuleList枚举内核模块
分析
ZwQuerySystemInformation构造KTRAP_FRAME:

实际上会调用NtQuerySystemInformation:

枚举驱动模块,需要传递SystemModuleInformation枚举值,该值为0x0B,传递给ExpQuerySystemInformation函数,在ExpQuerySystemInformation函数中进行匹配:

然后调用了ExpQueryModuleInformation:

接着调用了MmEnumerateSystemImagesShared:

在MiEnumerateSystemImages中,就使用了PsLoadedModuleList:

与DriverObject->DriverSection的区别
两者最终看的其实是同一条数据------PsLoadedModuleList 里的 KLDR_DATA_TABLE_ENTRY,差别在于"从哪里进、靠什么找到节点"。
txt
DRIVER_OBJECT (驱动对象)
+0x028 DriverSection ──────────┐ (直接指针,不经过链表)
▼
KLDR_DATA_TABLE_ENTRY (模块描述结构)
+0x000 InLoadOrderLinks ◄─────── 双向链表节点,所有内核模块串成 PsLoadedModuleList
+0x030 DllBase
+0x040 SizeOfImage
+0x048 FullDllName
+0x058 BaseDllName
...
PsLoadedModuleList = 链表头(LIST_ENTRY)
A. NtQuerySystemInformation(SystemModuleInformation) |
B. 内核里 DriverObject->DriverSection 当锚点再遍历 InLoadOrderLinks |
C. 枚举 \Driver 对象目录,逐个读 DriverObject->DriverSection |
|
|---|---|---|---|
| 数据来源 | 全局链表头 PsLoadedModuleList |
同一条链表,从某个成员节点切入 | 对象管理器里的 DRIVER_OBJECT |
| 是否依赖链表链接 | 是 | 是 | 否 (DriverSection 是直接指针) |
| 覆盖面 | 所有内核映像:ntoskrnl、hal、win32k、启动驱动、普通驱动 | 同 A,全覆盖 | 只有经 I/O 管理器创建、有 DRIVER_OBJECT 的驱动 |
| 调用环境 | 用户态即可(ntdll 的 NtQuerySystemInformation) |
必须内核态,且要先拿到一个 DriverObject |
必须内核态 |
| 被 DKOM 摘链后 | 看不见 | 看不见 | 仍然看得见(指针还在) |
PsLoadedModuleList 是"内核映像列表",不止驱动:ntoskrnl.exe、hal.dll、win32k.sys、ci.dll、还有不少没有 DRIVER_OBJECT 的内核模块都在里面。所以路径 A 和 B 能看到它们。
而 DRIVER_OBJECT 只属于"通过 I/O 管理器加载的驱动"(.sys 有 DriverEntry 的那些)。路径 C 漏掉 ntoskrnl/hal 这类非驱动映像------因为枚举 \Driver 目录时根本没有它们的对象。
对 DKOM 隐藏的可见性不同------这是最重要的差别:
- 路径 A、B 最终都是顺着链表指针走。rootkit 只要把目标节点的 Flink/Blink 改掉(摘链),你就遍历不到了。
- 路径 C 里 DriverObject->DriverSection 是一条硬指针,直接存着 KLDR_DATA_TABLE_ENTRY 的地址,不走链表。驱动被摘链后,只要 DRIVER_OBJECT 本身还在 \Driver 目录里,你照样能通过它读到 DllBase / SizeOfImage / FullDllName。
所以安全工具(ARK)通常两路交叉:用路径 A 拿链表视图,再用路径 C 拿对象目录视图,一比就能发现"对象还在、链表里没了"的被隐藏驱动。