【转】ZwQuerySystemInformation的分析(加载模块)

原帖地址: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 邓志

时间: 2024-08-27 16:11:30

【转】ZwQuerySystemInformation的分析(加载模块)的相关文章

Unity加载模块深度解析(网格篇)

在上一篇 加载模块深度解析(一)中,我们重点讨论了纹理资源的加载性能.这次,我们再来为你揭开其他主流资源的加载效率. 这是侑虎科技第53篇原创文章,欢迎转发分享,未经作者授权请勿转载.同时如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨.(QQ群465082844) 资源加载性能测试代码 与上篇所提出的测试代码一样,我们对于其他资源的加载性能分析同样使用该测试代码.我们将每种资源均制作成一定大小的AssetBundle文件,并逐一通过以下代码在不同设备上进行加载,以期得到不同硬件设备上的资

Unity加载模块深度解析(Shader)

作者:张鑫链接:https://zhuanlan.zhihu.com/p/21949663来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 接上一篇 加载模块深度解析(二),我们重点讨论了网格资源的加载性能.今天,我们再来为你揭开Shader资源的加载效率. 这是侑虎科技第59篇原创文章,欢迎转发分享,未经作者授权请勿转载.同时如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨.(QQ群465082844) 资源加载性能测试代码 与上篇所提出的测试代码一样,我们

nginx php动态编译加载模块.

#Nginx动态编译加载模块步骤 #查看目前Nginx版本及编译模块 #[[email protected] ~]# /opt/app/lnmp/nginx-1.12.0/sbin/nginx -V #nginx version: nginx/1.12.0 #built by gcc 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC) #built with OpenSSL 1.0.2k  26 Jan 2017 #TLS SNI support enabled #c

Python动态加载模块

需求:实现一个简单的pyton程序,接收两个参数:plugin_name, data,根据不同的plugin_name定位到不同的逻辑处理模块并进行输出. 实现方案: 使用python的库函数:load_source,将插件模块加载到一个dict中key为模块名称,value为类的实例,核心代码: def load_plugins(): global plugin_dict # 遍历插件目录加载所有py结尾的模块 for root, dirs, files in os.walk(module_p

AngularJs 动态加载模块和依赖注入

最近项目比较忙额,白天要上班,晚上回来还需要做Angular知识点的ppt给同事,毕竟年底要辞职了,项目的后续开发还是需要有人接手的,所以就占用了晚上学习的时间.本来一直不打算写这些第三方的学习笔记,不过觉得按需加载模块并且成功使用这个确实是个好处,还是记录下来吧.基于本兽没怎么深入的使用requireJs,所以本兽不知道这个和requireJs有什么区别,也不能清晰的说明这到底算不算Angular的按需加载. 为了实现这篇学习笔记知识点的效果,我们需要用到: angular:https://g

第四十天:编译可加载模块

linux刚刚开始的时候仅仅支持intel 386 ,后来不断的被移植到越来越多的平台上,包括ARM ,POWERPC,所有的代码设备驱动代码都编译到内核中,这明显不现实,这时候就需要通过内核模块的形式来加载驱动.当然模块不一定是驱动,也可以是为驱动提供某种功能. 现在先编写一个简单的linux模块. 1 #include <linux/init.h> 2 #include <linux/module.h> 3 4 MODULE_LICENSE("GPL");

Python自动重新加载模块(autoreload module)

守护进程模式 使用python开发后台服务程序的时候,每次修改代码之后都需要重启服务才能生效比较麻烦.看了一下Python开源的Web框架(Django.Flask等)都有自己的自动加载模块功能(autoreload.py),都是通过subprocess模式创建子进程,主进程作为守护进程,子进程中一个线程负责检测文件是否发生变化,如果发生变化则退出,主进程检查子进程的退出码(exist code)如果与约定的退出码一致,则重新启动一个子进程继续工作. 自动重新加载模块代码如下: #!/usr/b

AngularJS中多个ng-app(手动加载模块)

1.当有多个ng-app时:(首先是要加载angularJS) <div ng-app=""> <p>姓名:<input type="text" ng-model="name" placeholder="请输入姓名" /></p> <p> {{name}} </p> </div> <div ng-app="">

python如何重新加载模块

Python如何重新加载模块?Python教程(http://www.maiziedu.com/course/python/)中重新加载模块的方法有哪些呢?在python开发中,我们为了防止两个模块互相导入的问题,Python默认所有的模块都只导入一次,可以在开发时,我们会需要重新导入模块,那么怎么办呢,下面一起看看python重新加载模块的几种方法吧: Python2.7可以直接用reload(),Python3可以用下面几种方法: 方法一:基本方法 from imp import reloa