Windows内核驱动开发:手把手教你用DriverObject遍历所有已加载模块(含完整代码)

张开发
2026/6/10 2:14:04 15 分钟阅读
Windows内核驱动开发:手把手教你用DriverObject遍历所有已加载模块(含完整代码)
Windows内核驱动开发实战深入解析DriverObject模块遍历技术在Windows内核开发领域理解系统模块管理机制是构建安全工具、性能监控软件和调试器插件的关键基础。想象一下当你需要分析某个可疑驱动的行为或者监控系统中加载的第三方模块时如何从自己的驱动出发获取这些信息这正是我们今天要探讨的核心技术——通过DriverObject遍历所有已加载驱动模块。1. 内核模块管理机制解析Windows内核使用一套精巧的链表结构来管理系统中的所有已加载模块无论是驱动程序、可执行文件还是动态链接库。这套机制的核心在于_LDR_DATA_TABLE_ENTRY结构体和DriverObject之间的关联。每个加载到内存中的模块都会有一个对应的_LDR_DATA_TABLE_ENTRY结构体实例它包含了模块的关键信息typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; // ...其他成员省略 } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;对于驱动程序而言其DriverObject结构中的DriverSection成员直接指向该驱动对应的_LDR_DATA_TABLE_ENTRY结构。这种设计使得我们可以从一个已知点当前驱动的DriverObject出发遍历整个系统模块链表。关键点对比特性DriverObject_LDR_DATA_TABLE_ENTRY主要用途表示驱动对象描述加载的模块信息获取方式通过DriverEntry参数传入通过DriverObject-DriverSection获取包含信息驱动调度例程、设备对象等模块名称、基址、大小等链表关系独立存在通过LIST_ENTRY连接所有模块2. 模块遍历的实现原理遍历系统模块的核心思路可以简化为三个步骤获取当前驱动的_LDR_DATA_TABLE_ENTRY结构通过其链表指针找到下一个模块的_LDR_DATA_TABLE_ENTRY重复步骤2直到回到起始点这个过程看似简单但在内核环境下需要考虑诸多安全因素链表完整性检查确保不会因损坏的链表导致系统崩溃指针有效性验证所有指针解引用前必须检查是否为NULL循环终止条件正确处理环形链表的遍历逻辑以下是遍历实现的关键代码片段PLDR_DATA_TABLE_ENTRY first (PLDR_DATA_TABLE_ENTRY)DriverObject-DriverSection; if (first NULL) { DbgPrint(DriverSection is NULL, cannot traverse modules\n); return STATUS_UNSUCCESSFUL; } PLIST_ENTRY head first-InLoadOrderLinks; PLIST_ENTRY current head-Flink; int moduleCount 0; while (current ! head) { PLDR_DATA_TABLE_ENTRY entry CONTAINING_RECORD( current, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks ); if (entry-FullDllName.Length 0) { DbgPrint([%d] %wZ\n, moduleCount, entry-FullDllName); } current current-Flink; }常见陷阱与解决方案空指针解引用问题DriverSection可能为NULL解决添加NULL检查并优雅处理链表损坏检测问题Flink/Blink指针可能无效解决使用MmIsAddressValid验证指针版本兼容性问题不同Windows版本结构体可能有差异解决使用条件编译或运行时检测3. 完整实现与调试技巧一个健壮的模块遍历实现需要考虑生产环境中的各种边界情况。下面是一个增强版的实现方案NTSTATUS EnumerateLoadedDrivers(PDRIVER_OBJECT DriverObject) { if (DriverObject NULL) { return STATUS_INVALID_PARAMETER; } PLDR_DATA_TABLE_ENTRY firstEntry (PLDR_DATA_TABLE_ENTRY)DriverObject-DriverSection; if (firstEntry NULL) { DbgPrint([ERROR] DriverSection is NULL\n); return STATUS_NOT_FOUND; } PLIST_ENTRY listHead firstEntry-InLoadOrderLinks; PLIST_ENTRY currentEntry listHead-Flink; ULONG moduleIndex 0; DbgPrint( Loaded Modules List \n); while (currentEntry ! listHead) { if (!MmIsAddressValid(currentEntry)) { DbgPrint([WARN] Invalid list entry at 0x%p\n, currentEntry); break; } PLDR_DATA_TABLE_ENTRY moduleEntry CONTAINING_RECORD( currentEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks ); if (MmIsAddressValid(moduleEntry) moduleEntry-FullDllName.Length 0 MmIsAddressValid(moduleEntry-FullDllName.Buffer)) { DbgPrint([%04d] %wZ\n, moduleIndex, moduleEntry-FullDllName); } currentEntry currentEntry-Flink; } DbgPrint( Total %d modules listed \n, moduleIndex); return STATUS_SUCCESS; }调试与验证技巧使用WinDbg验证加载驱动后在WinDbg中使用lm命令对比输出检查DbgView或内核调试器输出是否一致压力测试方法在系统高负载时运行遍历代码模拟内存不足情况测试稳定性版本兼容性测试矩阵Windows版本测试结果注意事项Windows 10 1809正常基本结构一致Windows 11 22H2正常新增字段不影响遍历Windows Server 2019正常企业版验证通过4. 高级应用场景与性能优化掌握了基础遍历技术后我们可以将其应用于更复杂的场景安全监控实时检测新加载的驱动模块与已知恶意驱动特征库比对性能分析统计各驱动模块的内存占用监控特定驱动的加载/卸载频率调试辅助自动化收集环境信息构建模块依赖关系图性能优化建议缓存策略对不常变化的信息进行缓存异步处理将耗时操作放到工作线程增量遍历只处理新增的模块变化// 示例增量遍历实现 NTSTATUS EnumerateNewDrivers(PLIST_ENTRY lastKnownEntry) { PLDR_DATA_TABLE_ENTRY lastEntry CONTAINING_RECORD( lastKnownEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks ); PLIST_ENTRY current lastKnownEntry-Flink; while (current ! lastEntry-InLoadOrderLinks) { PLDR_DATA_TABLE_ENTRY newEntry CONTAINING_RECORD( current, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks ); ProcessNewModule(newEntry); current current-Flink; } return STATUS_SUCCESS; }实际项目中的经验在反作弊系统中我们使用这种技术检测非法注入的驱动模块某次性能分析项目中发现一个第三方驱动频繁加载/卸载导致系统卡顿通过模块遍历结合内存分析定位了一个长期存在的内存泄漏问题

更多文章