原帖地址:http://www.mouseos.com/windows/kernel/ZwQuerySystemInformation.html
内核模块可以使有 ZwQuerySystemInformation() 函数来获取已加载模块的信息,这个 routine 的原型定义为:
NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySystemInformation (
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__out_bcount_opt(SystemInformationLength) PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength
);
参数说明:
- SystemInformationClass: 提供查询信息的类型值。如果提供的是 SystemModuleInformation ,则查询系统已加载的模块信息。
- SystemInformation:提供接收信息的 buffer,这个是可选项。
- SystemInformationLength:提供接收 buffer 的长度值。
- ReturnLength:提供一个 ULONG 值用来接收所需的长度值,这个是可选项。
由于 SystemInformation 是可选项。因此,最初发起 ZwQuerySystemInformation() 调用时,可以提供一个 NULL 值,并且在 ReturnLength 提供一个 ULONG 值。如下用法:
ULONG RequiredLength = 0; // 用来接收所需长度 ... ... Status = ZwQuerySystemInformation( SystemModuleInformation, // 查询模块信息 NULL, // 接收 buffer 为 NULL 0, // buffer 长度为 0 &RequiredLength); // 接收所需的长度 if (NT_SUCCESS(Status) == STATUS_INFO_LENGTH_MISMATCH) { // // 如果 buffer 长度不满足条件,则返回 STATUS_INFO_LENGTH_MISMATCH 状态 // caller 则根据返回的所需长度值分配内存 // ModuleInformation = ExAllocatePoolWithTag( NonPagedPool, RequiredLength, ‘fnim‘); if (ModuleInforation != NULL) { // // 再次调用 ZwQuerySystemInformation() // Status = ZwQuerySystemInformation( SystemModuleInformation, ModuleInformation, // 提供接收 buffer RequiredLength, // 所需长度 NULL); // 不用接收返回长度 ... ... } }
第1次 ZwQuerySystemInformation() 调用目的是探测所需的 buffer 空间,第2次调用是获取模块信息。
在内核模块里调用 ZwQuerySystemInformation() 函数,将通过系统调用转而调用 NtQuerySystemInformation() 函数。
NTSTATUS NtQuerySystemInformation ( __in SYSTEM_INFORMATION_CLASS SystemInformationClass, __out_bcount_opt(SystemInformationLength) PVOID SystemInformation, __in ULONG SystemInformationLength, __out_opt PULONG ReturnLength ) { ... ... case SystemModuleInformation: KeEnterCriticalRegion(); ExAcquireResourceExclusiveLite( &PsLoadedModuleResource, TRUE ); try { Status = ExpQueryModuleInformation( &PsLoadedModuleList, &MmLoadedUserImageList, (PRTL_PROCESS_MODULES)SystemInformation, SystemInformationLength, ReturnLength ); } except(EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } ExReleaseResourceLite (&PsLoadedModuleResource); KeLeaveCriticalRegion(); break; ... .. }
NtQuerySystemInformation() 将调用内部的 ExpQueryModuleInformation() 函数来完成这个工作。实际上,它调用 ExpQueryModuleInformation() 来遍历 PsLoadedModuleList 与MmLoadedUserImageList 这两个链表。下面是 ExpQueryModuleInformation() 函数的实现:
NTSTATUS ExpQueryModuleInformation ( IN PLIST_ENTRY LoadOrderListHead, IN PLIST_ENTRY UserModeLoadOrderListHead, OUT PRTL_PROCESS_MODULES ModuleInformation, IN ULONG ModuleInformationLength, OUT PULONG ReturnLength OPTIONAL ) /*++ input: LoadOrderListHead - 提供 PsLoadedModuleList 链表 UserModeLoadOrderListHead - 提供 MmLoadedUserImageList 链表 ModuleInformation - caller 传过来接收信息的 buffer ModuleInformationLength - buffer 长度 ReturnLength - 返回长度 output: ModuleInformation - 用来接收模块信息 ReturnLength - 用来接收所需长度 如果提供的 buffer 长度不能满足所需长度时,routine 返回 STATUS_INFO_LENGTH_MISMATCH 状态 --*/ { NTSTATUS Status; ULONG RequiredLength; PLIST_ENTRY Next; PRTL_PROCESS_MODULE_INFORMATION ModuleInfo; PKLDR_DATA_TABLE_ENTRY LdrDataTableEntry; ANSI_STRING AnsiString; PCHAR s; ULONG NumberOfModules; NumberOfModules = 0; Status = STATUS_SUCCESS; RequiredLength = FIELD_OFFSET( RTL_PROCESS_MODULES, Modules ); ModuleInfo = &ModuleInformation->Modules[ 0 ]; // // 遍历 kernel 的 PsLoadedModuleList 链表 // Next = LoadOrderListHead->Flink; while ( Next != LoadOrderListHead ) { LdrDataTableEntry = CONTAINING_RECORD( Next, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks ); // // 每个 loaded 模块信息保存在一个 RTL_PROCESS_MODULE_INFORMATION 结构里 // RequiredLength 累加得到所有模块信息所需要的长度。 // RequiredLength += sizeof( RTL_PROCESS_MODULE_INFORMATION ); // // 如果提供的 buffer 长度小于必需的长度,记录为 STATUS_INFO_LENGTH_MISMATCH 状态。 // 否则保存模块信息 // if (ModuleInformationLength < RequiredLength) { Status = STATUS_INFO_LENGTH_MISMATCH; } else { // // 记录模块信息 // ModuleInfo->MappedBase = NULL; ModuleInfo->ImageBase = LdrDataTableEntry->DllBase; ModuleInfo->ImageSize = LdrDataTableEntry->SizeOfImage; ModuleInfo->Flags = LdrDataTableEntry->Flags; ModuleInfo->LoadCount = LdrDataTableEntry->LoadCount; // // LoadOrderIndex 指示模块在 PsLoadedModuleList 链中的 index 值 // InitOrderIndex 指示 index 值从 0 开始。 // ModuleInfo->LoadOrderIndex = (USHORT)(NumberOfModules); ModuleInfo->InitOrderIndex = 0; // // RTL_PROCESS_MODULE_INFORMATION 结构模块信息中的 Name 为 ANSI 串, // 而 PKLDR_DATA_TABLE_ENTRY 结构中的 Name 为 UNICODE 串。因此,将 UNICODE 串转换为 ANSI 串。 // AnsiString.Buffer = (PCHAR) ModuleInfo->FullPathName; AnsiString.Length = 0; AnsiString.MaximumLength = sizeof( ModuleInfo->FullPathName ); Status = RtlUnicodeStringToAnsiString( &AnsiString, &LdrDataTableEntry->FullDllName, FALSE ); if (NT_SUCCESS(Status) || (Status == STATUS_BUFFER_OVERFLOW)) { // // 从 FullPathName 属部开始向前查找 ‘\‘ 字符, // 用来分割出模块的 BaseName // s = AnsiString.Buffer + AnsiString.Length; while (s > AnsiString.Buffer && *--s) { if (*s == (UCHAR)OBJ_NAME_PATH_SEPARATOR) { s += 1; break; } } ModuleInfo->OffsetToFileName = (USHORT)(s - AnsiString.Buffer); // BaseName 在 FullPathName 的偏移量 } else { // // 如果 UNICODE 串转换为 ANSI 串失败,则将 FullPathName 清空。 // ModuleInfo->FullPathName[0] = ‘\0‘; ModuleInfo->OffsetToFileName = 0; } ModuleInfo += 1; } NumberOfModules += 1; Next = Next->Flink; } // // 如果提供了 MmLoadedUserImageList,则在 MmLoadedUserImageList 链表上做相同的操作 // if (ARGUMENT_PRESENT( UserModeLoadOrderListHead )) { Next = UserModeLoadOrderListHead->Flink; while ( Next != UserModeLoadOrderListHead ) { LdrDataTableEntry = CONTAINING_RECORD( Next, KLDR_DATA_TABLE_ENTRY, InLoadOrderLinks ); RequiredLength += sizeof( RTL_PROCESS_MODULE_INFORMATION ); if (ModuleInformationLength < RequiredLength) { Status = STATUS_INFO_LENGTH_MISMATCH; } else { ModuleInfo->MappedBase = NULL; ModuleInfo->ImageBase = LdrDataTableEntry->DllBase; ModuleInfo->ImageSize = LdrDataTableEntry->SizeOfImage; ModuleInfo->Flags = LdrDataTableEntry->Flags; ModuleInfo->LoadCount = LdrDataTableEntry->LoadCount; ModuleInfo->LoadOrderIndex = (USHORT)(NumberOfModules); ModuleInfo->InitOrderIndex = ModuleInfo->LoadOrderIndex; AnsiString.Buffer = (PCHAR) ModuleInfo->FullPathName; AnsiString.Length = 0; AnsiString.MaximumLength = sizeof( ModuleInfo->FullPathName ); Status = RtlUnicodeStringToAnsiString( &AnsiString, &LdrDataTableEntry->FullDllName, FALSE ); if (NT_SUCCESS (Status) || (Status == STATUS_BUFFER_OVERFLOW)) { s = AnsiString.Buffer + AnsiString.Length; while (s > AnsiString.Buffer && *--s) { if (*s == (UCHAR)OBJ_NAME_PATH_SEPARATOR) { s += 1; break; } } ModuleInfo->OffsetToFileName = (USHORT)(s - AnsiString.Buffer); } else { ModuleInfo->FullPathName[0] = ‘\0‘; ModuleInfo->OffsetToFileName = 0; } ModuleInfo += 1; } NumberOfModules += 1; Next = Next->Flink; } } // // 如果需要返回长度,则返回所需长度 // if (ARGUMENT_PRESENT(ReturnLength)) { *ReturnLength = RequiredLength; } if (ModuleInformationLength >= FIELD_OFFSET( RTL_PROCESS_MODULES, Modules )) { ModuleInformation->NumberOfModules = NumberOfModules; } else { Status = STATUS_INFO_LENGTH_MISMATCH; } return Status; }
这个函数的实现很简单,就是在 PsLoadedModuleList 与 MmLoadedUserImageList 链表里读取模块的信息,写入提供的 buffer 里。
接收的 buffer 实际上是一个 RTL_PROCESS_MODULES 结构,它的尾部是一个可变的 PRTL_PROCESS_MODULE_INFORMATION 结构数组,用来保存若干个实际的模块信息。
版权所有 ©2009 - 2014 邓志