首页 > 技术文章 > 驱动中遍历模块的几种方式

iBinary 2019-10-17 17:42 原文

驱动中遍历模块

一丶简介

1.1 基于 PsLoadModuleList 方法

简介:

进入内核了.遍历内核中使用的模块该怎么办. 其实在驱动中.我们的DriverEntry入口位置.
提供了两个参数. 一个是DrIverObject另一个则是注册表路径.

其实遍历模块的技巧就在这个DriverObject中.

众所周知在Ring3下遍历模块可以通过TEB PEB遍历. 我们会接触一个结构体叫做LDR_DATA_TABLE_ENTRY的结构体.
内核中也会使用这个结构体. 看下DriverObject对象所代表的含义.

typedef struct _DRIVER_OBJECT {
    CSHORT Type;
    CSHORT Size;

    PDEVICE_OBJECT DeviceObject;                //驱动对象
    ULONG Flags;                                //驱动的标志


    PVOID DriverStart;                          //驱动的起始位置
    ULONG DriverSize;                           //驱动的大小
    PVOID DriverSection;                        //指向驱动程序映像的内存区对象
    PDRIVER_EXTENSION DriverExtension;          //驱动的扩展空间

    UNICODE_STRING DriverName;                  //驱动名字

    PUNICODE_STRING HardwareDatabase;


    PFAST_IO_DISPATCH FastIoDispatch;


    PDRIVER_INITIALIZE DriverInit;
    PDRIVER_STARTIO DriverStartIo;
    PDRIVER_UNLOAD DriverUnload;
    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];

} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT; 

在这里主要使用 DriverSection这个成员 这个成员则可以解释为LDR_DATA_TABLE_ENTRY结构体.
里面存放着我们的所有模块.

typedef struct _LDR_DATA_TABLE_ENTRY {
    LIST_ENTRY InLoadOrderLinks;               //链表存储,指向下一个LDR_DATA_TABLE_ENTRY结构
    LIST_ENTRY InMemoryOrderLinks;
    LIST_ENTRY InInitializationOrderLinks;
    PVOID DllBase;                             //基址
    PVOID EntryPoint;
    ULONG SizeOfImage;
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;                //存放着驱动模块名
    ULONG Flags;
    USHORT LoadCount;
    USHORT TlsIndex;
    union {
        LIST_ENTRY HashLinks;
        struct {
            PVOID SectionPointer;
            ULONG CheckSum;
        };
    };
    union {
        struct {
            ULONG TimeDateStamp;
        };
        struct {
            PVOID LoadedImports;
        };
    };
}LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

所以代码就很简单了.直接遍历自身结构体的 DriverSection成员即可.解释为(LDR_DATA_TABLE_ENTRY)结构.

1.2 代码以及演示.

#include "Driver.h" //这个替换为自己的. 包含Ntddk.h即可.
#include <wdm.h>

//KLDR_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;
    ULONG Flags;
    USHORT LoadCount;
    USHORT TlsIndex;
    union {
        LIST_ENTRY HashLinks;
        struct {
            PVOID SectionPointer;
            ULONG CheckSum;
        };
    };
    union {
        struct {
            ULONG TimeDateStamp;
        };
        struct {
            PVOID LoadedImports;
        };
    };
}LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegPath)
{
    ULONG iCount = 0;
    NTSTATUS ntStatus;
    pDriverObj->DriverUnload = DriverUnLoad;

    KdBreakPoint();

    /*
    主要是遍历DriverObject中的 DriverSection 它可以解释为LDR_DATA_TABLE_ENTRY结构体
    */


    PLDR_DATA_TABLE_ENTRY pLdr = NULL;
    PLIST_ENTRY pListEntry = NULL;
    PLIST_ENTRY pCurrentListEntry = NULL;

    PLDR_DATA_TABLE_ENTRY pCurrentModule = NULL;
    pLdr = (PLDR_DATA_TABLE_ENTRY)pDriverObj->DriverSection;
    pListEntry = pLdr->InLoadOrderLinks.Flink;
    pCurrentListEntry = pListEntry->Flink;

    while (pCurrentListEntry != pListEntry) //前后不相等
    {
        //获取LDR_DATA_TABLE_ENTRY结构
        pCurrentModule = CONTAINING_RECORD(pCurrentListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);

            if (pCurrentModule->BaseDllName.Buffer != 0)
            {
                DbgPrint("ModuleName = %wZ ModuleBase = %p \r\n", 
                    pCurrentModule->BaseDllName,
                 pCurrentModule->DllBase);
            }
            pCurrentListEntry = pCurrentListEntry->Flink;
    }
    return STATUS_SUCCESS;
}

结果.

代码简单改一下.还可以获得指定的模块的基址以及结束地址.

代码如下

VOID GetModuleBaseByName(PDRIVER_OBJECT pDriverObj,UNICODE_STRING ModuleName)
{
    PLDR_DATA_TABLE_ENTRY pLdr = NULL;
    PLIST_ENTRY pListEntry = NULL;
    PLIST_ENTRY pCurrentListEntry = NULL;

    PLDR_DATA_TABLE_ENTRY pCurrentModule = NULL;
    pLdr = (PLDR_DATA_TABLE_ENTRY)pDriverObj->DriverSection;
    pListEntry = pLdr->InLoadOrderLinks.Flink;
    pCurrentListEntry = pListEntry->Flink;

    while (pCurrentListEntry != pListEntry) //前后不相等
    {
        //获取LDR_DATA_TABLE_ENTRY结构
        pCurrentModule = CONTAINING_RECORD(pCurrentListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);

        if (pCurrentModule->BaseDllName.Buffer != 0)
        {

            if (RtlCompareUnicodeString(&pCurrentModule->BaseDllName, &ModuleName, TRUE) == 0)
            {
                DbgPrint("ModuleName = %wZ ModuleBase = %p ModuleEndBase = %p\r\n",
                    pCurrentModule->BaseDllName,
                    pCurrentModule->DllBase,
                    (LONGLONG)pCurrentModule->DllBase + pCurrentModule->SizeOfImage);
            }

        }
        pCurrentListEntry = pCurrentListEntry->Flink;
    }
}

二丶 基于ZwQuerySystemInformation 方法

2.1 简介

在内核中我们还可以使用未公开函数 ZwQuerySystemInformation 来进行模块的遍历。

函数原型如下:

NTSTATUS WINAPI ZwQuerySystemInformation(
  _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
  _Inout_   PVOID                    SystemInformation,
  _In_      ULONG                    SystemInformationLength,
  _Out_opt_ PULONG                   ReturnLength
);

参数1: 要检索的系统信息的类型。此参数可以是枚举类型SYSTEM_INFORMATION_CLASS下列值之一。

参数2: 根据参数一 你所要提供的一个缓冲区

参数3: 参数2的缓冲区大小

参数4: 返回长度

这种API我们都可以调用两次。第一次来确定所需要的空间。 第二次再来真正的遍历。

所以最主要的就是参数1 我们要填写什么了。

这个API在ring3同样也有。 基本一样。

所以我先引用一下之前写过的文章 然后在写一下实际代码应用。

ZwQuerySystemInfoMation函数使用 - iBinary - 博客园 (cnblogs.com)

其中我们要在内核遍历模块只需要使用 _SYSTEM_INFORMATION_CLASS 结构中的 SystemModuleInformation 号即可。此号就是第 11号功能

如果使用此号 代表我们的Buffer 要传递一个

PSYSTEM_MODULE_INFORMATION 结构 此结构如下:

    typedef struct _SYSTEM_MODULE_INFORMATION
    {
        ULONG ulModuleCount;
        SYSTEM_MODULE Modules[1];
    } SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;

参数1 代表当前模块计数或者个数

参数2 是一个数组代表当前模块。

参数2 又是一个新的类型 新类型如下

结构如下

    typedef struct _SYSTEM_MODULE
    {
        ULONG_PTR Reserved[2];
        PVOID Base;
        ULONG Size;
        ULONG Flags;
        USHORT Index;
        USHORT Unknown;
        USHORT LoadCount;
        USHORT ModuleNameOffset;
        CHAR ImageName[256];
    } SYSTEM_MODULE, * PSYSTEM_MODULE;

请注意此结构分为32位和64位。唯一不同的则是第一个成员。

如果是64位下编译使用 请修改第一个成员类型为: ULONG_PTR 或者 __int64

如果是32位下请修改第一个成员为 ULONG

而其实我们只需要将第一个成员修改为 ULONG_PTR 即可。这样如果编译的时候是编译64位的话 ULONG_PTR 则 占八个字节。 否则反之。

2.2 实战代码演示

原理了解了其实代码很简单了

1.首先定义需要的结构

#ifdef __cplusplus
extern "C"
{
#endif

    typedef struct _SYSTEM_MODULE
    {
        ULONG_PTR Reserved[2];
        PVOID Base;
        ULONG Size;
        ULONG Flags;
        USHORT Index;
        USHORT Unknown;
        USHORT LoadCount;
        USHORT ModuleNameOffset;
        CHAR ImageName[256];
    } SYSTEM_MODULE, * PSYSTEM_MODULE;

    typedef struct _SYSTEM_MODULE_INFORMATION
    {
        ULONG ulModuleCount;
        SYSTEM_MODULE Modules[1];
    } SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;
    NTSTATUS
        NTAPI
        ZwQuerySystemInformation(
            DWORD32 systemInformationClass,
            PVOID systemInformation,
            ULONG systemInformationLength,
            PULONG returnLength);

#ifdef __cplusplus
}
#endif

2.使用代码进行获取

#ifdef _AMD64_
bool getModuleBase(const char *name, unsigned long long &addr, ULONG &size)
#else
bool getModuleBase(const char *name, ULONG &addr, ULONG &size)
#endif // _AMD64_
{
    ULONG uNeedSize = 0;
    ULONG uIndex = 0;
    NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
    bool bRet = false;
    PSYSTEM_MODULE_INFORMATION pSysInfo = NULL;
    ntStatus = ZwQuerySystemInformation(SystemModuleInformation, &uNeedSize, 0, &uNeedSize);
    if (ntStatus != STATUS_INFO_LENGTH_MISMATCH)
    {
        bRet = false;
        goto Done;
    }
    if (uNeedSize <= 0)
    {
        bRet = false;
        goto Done;
    }
    pSysInfo = (PSYSTEM_MODULE_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, uNeedSize, 'aaaa');
    if (pSysInfo == NULL)
    {
        bRet = false;
        goto Done;
    }
    ntStatus = ZwQuerySystemInformation(SystemModuleInformation, pSysInfo, uNeedSize, &uNeedSize);
    if (!NT_SUCCESS(ntStatus))
    {
        bRet = false;
        goto Done;
    }
    for (uIndex = 0; uIndex < pSysInfo->ulModuleCount; uIndex++)
    {
        PSYSTEM_MODULE pModuleInfo = &pSysInfo->Modules[uIndex];
        if (MmIsAddressValid(pModuleInfo) && pModuleInfo != NULL)
        {
            if (MmIsAddressValid(pModuleInfo->ImageName) && pModuleInfo->ImageName != NULL)
            {
                //                 if (strstr(pModuleInfo->ImageName, name))
                //                 {
                // #ifdef _AMD64_
                //                     addr = (unsigned long long)pModuleInfo->Base;
                //                     size = (unsigned long long)pModuleInfo->Size;
                // #else
                //                     addr = (ULONG)pModuleInfo->Base;
                //                     size = (ULONG)pModuleInfo->Size;
                // #endif
                //                     bRet = true;
                //                     break;

                //    }

                DbgPrint("[Module]: %s \t\t[Base] %p \t\t[Name]: %s \r\n",
                         pModuleInfo->ImageName,
                         pModuleInfo->Base,
                         pModuleInfo->ImageName + pModuleInfo->ModuleNameOffset);
            }
        }
    }

Done:
    if (pSysInfo != NULL)
    {
        ExFreePoolWithTag(pSysInfo, 'aaaa');
        pSysInfo = NULL;
    }
    return bRet;
}
  1. 调用示例

    extern "C" NTSTATUS DriverEntry(
     IN PDRIVER_OBJECT pRootDriverObj,
     IN PUNICODE_STRING pRegPath)
    {
     NTSTATUS status = STATUS_UNSUCCESSFUL;
     pRootDriverObj->DriverUnload = DriverUnload;
     // KdBreakPoint();
     ULONG_PTR addr = 0;
     ULONG uSize = 0;
     getModuleBase("win32kfull.sys", addr, uSize);
     // KdBreakPoint();
     return status;
    }
    

2.3 效果演示

三丶公开化函数遍历 AuxKlibQueryModuleInformation

3.1 简介

AuxKlibQueryModuleInformation 此函数也可以用于遍历系统内核模块。

但是使用此函数之前我们 首先要包含 aux_klib.h 并且还要包含静态库 Aux_Klib

对于 WDK7600来说 我们包含库直接 采用下列方式即可。

TARGETLIBS = $(DDK_LIB_PATH)/Aux_Klib.lib\

AuxKlibQueryModuleInformation 函数原型如下:

NTSTATUS AuxKlibQueryModuleInformation(
  [in, out]       PULONG BufferSize,
  [in]            ULONG  ElementSize,
  [out, optional] PVOID  QueryInfo
);

你可以将其理解为 简化版本的 ZwQuerySystemInformation

参数1: BufferSize 是一个传入传出参数,当我们的参数3要传递的Buffer为NULL的时候 那么调用此函数则会将你所需要的Buffer缓冲的大小写入到参数1里面。这样我们就得到了Buffer的大小了。

参数2: 参数二 必须是 sizeof(AUX_MODULE_BASIC_INFO) 或者 sizeof(AUX_MODULE_EXTENDED_INFO)
参数2的意思是 QueryInfo(参数3)中的数组的每个元素的大小。

参数3: 参数三是一个Buffer,虽然是PVOID 但是它的结构是 AUX_MODULE_BASIC_INFO 或者 AUX_MODULE_EXTENDED_INFO

如果遍历模块我们会使用 AUX_MODULE_EXTENDED_INFO 此结构

此结构如下:

typedef struct _AUX_MODULE_EXTENDED_INFO {
  AUX_MODULE_BASIC_INFO BasicInfo;
  ULONG                 ImageSize;
  USHORT                FileNameOffset;
  UCHAR                 FullPathName[AUX_KLIB_MODULE_PATH_LEN];
} AUX_MODULE_EXTENDED_INFO, *PAUX_MODULE_EXTENDED_INFO;

此结构里面 存储着 FullPathName 也就是文件的路径 也存储着 FileNameOffset 文件名的偏移。 FullPathName+FileNameOffset则可以得到文件名

而参数1的 BasicInfo也是一个结构

typedef struct _AUX_MODULE_BASIC_INFO {
  PVOID ImageBase;
} AUX_MODULE_BASIC_INFO, *PAUX_MODULE_BASIC_INFO;

此结构就一个成员 就是 ImageBase

所以说它是对第二种方法的简化。

3.2 使用例子代码

代码很简单

#include "aux_klib.h"
bool EnumModule()
{
    ULONG uNeedModuleSize = 0;
    ULONG uIndex = 0;
    NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
    bool bRet = false;
    PAUX_MODULE_EXTENDED_INFO pSysInfo = NULL;
    ULONG ulNumberOfModules = 0;
    ntStatus = AuxKlibInitialize();
    if (!NT_SUCCESS(ntStatus))
    {
        bRet = false;
        goto Done;
    }
    ntStatus = AuxKlibQueryModuleInformation(&uNeedModuleSize, sizeof(AUX_MODULE_EXTENDED_INFO), NULL);
    if (!NT_SUCCESS(ntStatus))
    {
        bRet = false;
        goto Done;
    }
    // if (ntStatus != STATUS_BUFFER_TOO_SMALL)
    // {
    //     bRet = false;
    //     goto Done;
    // }
    if (uNeedModuleSize <= 0)
    {
        bRet = false;
        goto Done;
    }

    ulNumberOfModules = uNeedModuleSize / sizeof(AUX_MODULE_EXTENDED_INFO);
    pSysInfo = (PAUX_MODULE_EXTENDED_INFO)ExAllocatePoolWithTag(NonPagedPool, uNeedModuleSize, 'aaaa');
    if (pSysInfo == NULL)
    {
        bRet = false;
        goto Done;
    }
    RtlZeroMemory(pSysInfo, uNeedModuleSize);
    ntStatus = AuxKlibQueryModuleInformation(&uNeedModuleSize, sizeof(AUX_MODULE_EXTENDED_INFO), pSysInfo);
    if (!NT_SUCCESS(ntStatus))
    {
        bRet = false;
        goto Done;
    }
    for (uIndex = 0; uIndex < ulNumberOfModules; uIndex++)
    {

        DbgPrint("[Module]: %s \t\t[Base] %p \t\t[Name]: %s \r\n",
                 pSysInfo[uIndex].FullPathName,
                 pSysInfo[uIndex].BasicInfo.ImageBase,
                 pSysInfo[uIndex].FullPathName + pSysInfo[uIndex].FileNameOffset);
    }

Done:
    if (pSysInfo != NULL)
    {
        ExFreePoolWithTag(pSysInfo, 'aaaa');
        pSysInfo = NULL;
    }
    return bRet;
}

3.3 效果演示

四丶 未公开函数RtlQueryModuleInformation

4.1 简介

在windows系统内核中 还存在一个未公开函数 RtlQueryModuleInformation

此函数也是遍历系统模块的。

其函数结构如下

  NTSTATUS
    RtlQueryModuleInformation(
        PULONG BufferSize,
        ULONG UnitSize,
        PVOID Buffer);

直接定义出来即可。

使用方式 和第三种 遍历模块的方式一样。

只不过需要把 AuxKlibQueryModuleInformation 换成 RtlQueryModuleInformation

代码参数 参考 AuxKlibQueryModuleInformation

4.2 代码

bool EnumModule2()
{
    ULONG uNeedModuleSize = 0;
    ULONG uIndex = 0;
    NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
    bool bRet = false;
    PAUX_MODULE_EXTENDED_INFO pSysInfo = NULL;
    ULONG ulNumberOfModules = 0;

    ntStatus = RtlQueryModuleInformation(&uNeedModuleSize, sizeof(AUX_MODULE_EXTENDED_INFO), NULL);
    if (!NT_SUCCESS(ntStatus))
    {
        bRet = false;
        goto Done;
    }
    // if (ntStatus != STATUS_BUFFER_TOO_SMALL)
    // {
    //     bRet = false;
    //     goto Done;
    // }
    if (uNeedModuleSize <= 0)
    {
        bRet = false;
        goto Done;
    }

    ulNumberOfModules = uNeedModuleSize / sizeof(AUX_MODULE_EXTENDED_INFO);
    pSysInfo = (PAUX_MODULE_EXTENDED_INFO)ExAllocatePoolWithTag(NonPagedPool, uNeedModuleSize, 'aaaa');
    if (pSysInfo == NULL)
    {
        bRet = false;
        goto Done;
    }
    RtlZeroMemory(pSysInfo, uNeedModuleSize);
    ntStatus = RtlQueryModuleInformation(&uNeedModuleSize, sizeof(AUX_MODULE_EXTENDED_INFO), pSysInfo);
    if (!NT_SUCCESS(ntStatus))
    {
        bRet = false;
        goto Done;
    }
    for (uIndex = 0; uIndex < ulNumberOfModules; uIndex++)
    {

        DbgPrint("[Module]: %s \t\t[Base] %p \t\t[Name]: %s \r\n",
                 pSysInfo[uIndex].FullPathName,
                 pSysInfo[uIndex].BasicInfo.ImageBase,
                 pSysInfo[uIndex].FullPathName + pSysInfo[uIndex].FileNameOffset);
    }

Done:
    if (pSysInfo != NULL)
    {
        ExFreePoolWithTag(pSysInfo, 'aaaa');
        pSysInfo = NULL;
    }
    return bRet;
}

效果与 第三种方式一样,这里不在列举出来了。

推荐阅读