我们的驱动在被系统加载的同时,内存中会出现一个描述我们驱动信息的对象:DRIVER OBJECT,而这个对象的地址,其实就保存在我们驱动的入口函数 Driver Entry 的第1个参数中。
在 DriverObject 对象中,有一个 Driver Section 成员,它所指向的是一个名叫 _LDR_DATA_TABLE_ENTRY 的结构体,该结构体每个驱动模块都有一份,在这个结构体中就保存着一个驱动模块的所有信息。
要想遍历系统中所有的驱动模块,这个结构体就是我们要重点关注的地方。先来看下该结构体的结构(看下第1个和第7个就行):
1 typedef struct _LDR_DATA_TABLE_ENTRY { 2 LIST_ENTRY InLoadOrderLinks;//这个成员把系统所有加载(可能是停止没被卸载)已经读取到内存中 我们关系第一个 我们要遍历链表 双链表 不管中间哪个节点都可以遍历整个链表 本驱动的驱动对象就是一个节点 3 LIST_ENTRY InMemoryOrderLinks;//系统已经启动 没有被初始化 没有调用DriverEntry这个历程的时候 通过这个链表进程串接起来 4 LIST_ENTRY InInitializationOrderLinks;//已经调用DriverEntry这个函数的所有驱动程序 5 PVOID DllBase; 6 PVOID EntryPoint;//驱动的进入点 DriverEntry 7 ULONG SizeOfImage; 8 UNICODE_STRING FullDllName;//驱动的满路径 9 UNICODE_STRING BaseDllName;//不带路径的驱动名字 10 ULONG Flags; 11 USHORT LoadCount; 12 USHORT TlsIndex; 13 union { 14 LIST_ENTRY HashLinks; 15 struct { 16 PVOID SectionPointer; 17 ULONG CheckSum; 18 }; 19 }; 20 union { 21 struct { 22 ULONG TimeDateStamp; 23 }; 24 struct { 25 PVOID LoadedImports; 26 }; 27 }; 28 } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
我们唯一需要关注的就是这个结构体的第一个成员:InLoadOrderLinks,它也是个结构体,有两个成员:Flink 和 Blink。看到这两个东西估计你已经能窥探出一些端倪了,没错,这是个链表。
系统中有一个双向链表,其中每一个节点都保存着一个驱动模块的所有信息,而 InLoadOrderLinks 就是该链表中的节点类型,Flink 指向下一个驱动对象的 _LDR_DATA_TABLE_ENTRY,Blink 指向上一个驱动对象的 _LDR_DATA_TABLE_ENTRY。
因此,我们只要遍历这个 InLoadOrderLinks ,就能获取系统中所有驱动的模块信息。例如,使用以下代码就能完成遍历所有驱动的操作:
1 #include <ntddk.h> 2 3 typedef struct _LDR_DATA_TABLE_ENTRY { 4 LIST_ENTRY InLoadOrderLinks;//这个成员把系统所有加载(可能是停止没被卸载)已经读取到内存中 我们关系第一个 我们要遍历链表 双链表 不管中间哪个节点都可以遍历整个链表 本驱动的驱动对象就是一个节点 5 LIST_ENTRY InMemoryOrderLinks;//系统已经启动 没有被初始化 没有调用DriverEntry这个历程的时候 通过这个链表进程串接起来 6 LIST_ENTRY InInitializationOrderLinks;//已经调用DriverEntry这个函数的所有驱动程序 7 PVOID DllBase; 8 PVOID EntryPoint;//驱动的进入点 DriverEntry 9 ULONG SizeOfImage; 10 UNICODE_STRING FullDllName;//驱动的满路径 11 UNICODE_STRING BaseDllName;//不带路径的驱动名字 12 ULONG Flags; 13 USHORT LoadCount; 14 USHORT TlsIndex; 15 union { 16 LIST_ENTRY HashLinks; 17 struct { 18 PVOID SectionPointer; 19 ULONG CheckSum; 20 }; 21 }; 22 union { 23 struct { 24 ULONG TimeDateStamp; 25 }; 26 struct { 27 PVOID LoadedImports; 28 }; 29 }; 30 } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; 31 32 VOID DriverUnload(PDRIVER_OBJECT pDriverObject) { 33 DbgPrint("Unloaded."); 34 } 35 36 NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) { 37 38 DbgPrint("Start Running..."); 39 pDriverObject->DriverUnload = DriverUnload; 40 DbgPrint("对象基址:%x",pDriverObject); 41 DbgPrint("驱动名:%ws",pDriverObject->DriverName.Buffer); 42 DbgPrint("该驱动包含如下模块:"); 43 PLDR_DATA_TABLE_ENTRY p = (PLDR_DATA_TABLE_ENTRY)pDriverObject->DriverSection; 44 DbgPrint("DriverSection:%x",p); 45 PLDR_DATA_TABLE_ENTRY item = (PLDR_DATA_TABLE_ENTRY)p->InLoadOrderLinks.Flink; 46 DbgPrint("First LDR TABLE:%x",item); 47 PLDR_DATA_TABLE_ENTRY first = item; 48 while(item != NULL) 49 { 50 DbgPrint("%ws",item->FullDllName.Buffer); 51 item = (PLDR_DATA_TABLE_ENTRY)item->InLoadOrderLinks.Flink; 52 if(item == first) 53 break; 54 } 55 return STATUS_SUCCESS; 56 }
这段代码唯一需要注意的是 “判断是否遍历结束”,因为是循环链表(即末尾节点的Flink指向起始节点),如果不加以判断将进入死循环。
解决方法很简单,就如上面的代码中写的那样,将遍历的第一个节点的地址保存下来,然后开始遍历,只要遍历到的节点又和这个节点的地址相同,就说明已经遍历完成。