枚举进程中的模块

在Windows中枚举进程中的模块主要是其中加载的dll,在VC上主要有2种方式,一种是解析PE文件中导入表,从导入表中获取它将要静态加载的dll,一种是利用查询进程地址空间中的模块,根据模块的句柄来得到对应的dll,最后再补充一种利用Windows中的NATIVE API获取进程内核空间中的模块,下面根据给出这些方式的具体的代码片段:

解析PE文件来获取其中的dll

在之前介绍PE文件时说过PE文件中中存在一个导入表,表中记录了程序中加载的导入dll以及这些dll中函数的信息,这个结构的定义如下:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;
        DWORD   OriginalFirstThunk;
    };
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain; 
    DWORD   Name;
    DWORD   FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;

我只需要获取这个结构并且根据RVA计算出它在文件中的偏移即可找到对应名称,利用之前PE解析器中的CPeFileInfo类来解析它即可,下面是具体的代码:

void EnumModulesByPe(LPCTSTR pszPath)
{
    CPeFileInfo peFile;
    peFile.strFilePath = pszPath;
    peFile.LoadFile();

    if (!peFile.IsPeFile())
    {
        printf("is not a pe file!\n");
        return ;
    }

    peFile.InitDataDirectoryTable();
    PIMAGE_IMPORT_DESCRIPTOR pImportTable  = peFile.GetImportDescriptor();
    while(!peFile.IsEndOfImportTable(pImportTable))
    {
        printf("%s\n", peFile.RVA2fOffset(pImportTable->Name, (DWORD)peFile.pImageBase));
        pImportTable++;
    }
}

利用之前的PE解析的类,首先给类中的文件路径赋值,然后加载到内存,并初始化它的数据目录表信息,从表中取出导入表的结构,根据结构中的Name字段的值来计算它的真实地址,即可解析出它里面的模块,这里我们只能解析出PE文件中自身保存的信息,如果dll是在程序运行之时调用LoadLibrary动态加载的,利用这个方法是找不到的。

解析进程地址空间中的模块

这个方法首先通过OpenProcess函数获取对应进程的句柄,然后调用EnumProcessModules枚举进程地址空间中当前存在的模块,这个函数会返回一个HMODULE句柄的数组,我们遍历这个数组,对其中的每个句柄调用GetModuleFileNameEx(很多模块GetModuleFileName获取不到,具体原因我没有深入研究)获取对应的文件路径。下面是具体的代码:

    HMODULE* phMods = NULL;
    HANDLE hProcess = NULL;
    DWORD dwNeeded = 0;
    DWORD i = 0;
    TCHAR szModName[MAX_PATH] = {};

    hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessId );
    if (NULL == hProcess)
    {
        printf("不能打开进程[ID:0x%x]句柄,错误码:0x%08x\n",dwProcessId);
        return;
    }

    EnumProcessModules(hProcess, NULL, 0, &dwNeeded);
    phMods = (HMODULE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwProcessId);

    if( EnumProcessModules(hProcess, phMods, dwNeeded, &dwNeeded))
    {
        for ( i = 0; i < (dwNeeded / sizeof(HMODULE)); i++ )
        {
            ZeroMemory(szModName,MAX_PATH*sizeof(TCHAR));
            //在这如果使用GetModuleFileName,有的模块名称获取不到,函数返回无法找到该模块的错误
            if ( GetModuleFileNameEx(hProcess, phMods[i], szModName,MAX_PATH))
            {
                printf("%ws\n", szModName);
            }
        }
    }

    HeapFree(GetProcessHeap(), 0, phMods);
    CloseHandle( hProcess );

由于静态加载的dll在进程启动之时就已经被加载到内存中,所以利用这个方法自然可以获取静态加载的dll,但是由于它是获取进程地址空间中加载的dll,所以要求进程要正在运行,毕竟进程如果没有运行,那么也就不存在地址空间,也就无法获取其中加载的dll,另外它只能获取当前进程地址空间中的dll,有的dll这个时候还没有被加载的话,它自然也获取不到。所以这个方法也不是能获取所有加载的dll

获取内核地址空间中的模块

不管是解析PE文件还是调用EnumProcessModules都只能获取用户层地址空间中的模块,但是进程不光有用户空间,还有内核空间,所以在这再提供一种枚举内核地址空间的模块的方法。 枚举内核地址空间主要使用函数ZwQuerySystemInformation(也可以使用NtQuerySystemInformation)在msdn中明确指出,这两个函数未来可能不在使用,不推荐使用,但是至少现在是仍然支持的,并且可以很好的完成任务。 这两个函数主要在ntdll.dll中导出,两个函数的参数用法完全相同,只是一个是比较上层一个比较底层而已。在这主要说明一个,另一个完全一样:

NTSTATUS WINAPI ZwQuerySystemInformation(
  __in       SYSTEM_INFORMATION_CLASS SystemInformationClass,
  __inout    PVOID SystemInformation,
  __in       ULONG SystemInformationLength,
  __out_opt  PULONG ReturnLength
);

函数的第一个参数是一个枚举类型,用来表示我们将要调用此函数来获取系统哪方面的信息,第二个参数是一个缓冲区,用来存储该函数输出的值,第三个参数是缓冲区的长度,第四个参数是实际需要缓冲区的长度,说到这应该很快就可以反应过来,我们可以第一次调用这个函数传入一个NULL缓冲,缓冲长度给0,让他返回具体的长度,然后根据这个长度,动态分配一块内存,再次调用传入正确的缓冲和长度,获取数据。 在调用这个函数时需要注意下面几点: 1. 这个函数是未导出的,所以在微软的开发环境中是没有它的定义的,要使用它需要我们自己定义,定义的代码如下:

//这个NTSTATUS结构在应用层有定义,直接使用即可
typedef NTSTATUS(WINAPI *ZWQUERYSYSTEMINFORMATION)(
    __in       SYSTEM_INFORMATION_CLASS SystemInformationClass,
    __inout    PVOID SystemInformation,
    __in       ULONG SystemInformationLength,
    __out_opt  PULONG ReturnLength
    );

这个函数使用的一些结构是在内核开发环境DDK中定义的,在应用层中可能没有它的定义,所以在这我们也需要对它们进行定义:

#define NT_SUCCESS(status)      ((NTSTATUS)(status)>=0)

typedef enum _SYSTEM_INFORMATION_CLASS {
    SystemBasicInformation,
    SystemProcessorInformation,
    SystemPerformanceInformation,
    SystemTimeOfDayInformation,
    SystemPathInformation,
    SystemProcessInformation,
    SystemCallCountInformation,
    SystemDeviceInformation,
    SystemProcessorPerformanceInformation,
    SystemFlagsInformation,
    SystemCallTimeInformation,
    SystemModuleInformation,
    SystemLocksInformation,
    SystemStackTraceInformation,
    SystemPagedPoolInformation,
    SystemNonPagedPoolInformation,
    SystemHandleInformation,
    SystemObjectInformation,
    SystemPageFileInformation,
    SystemVdmInstemulInformation,
    SystemVdmBopInformation,
    SystemFileCacheInformation,
    SystemPoolTagInformation,
    SystemInterruptInformation,
    SystemDpcBehaviorInformation,
    SystemFullMemoryInformation,
    SystemLoadGdiDriverInformation,
    SystemUnloadGdiDriverInformation,
    SystemTimeAdjustmentInformation,
    SystemSummaryMemoryInformation,
    SystemNextEventIdInformation,
    SystemEventIdsInformation,
    SystemCrashDumpInformation,
    SystemExceptionInformation,
    SystemCrashDumpStateInformation,
    SystemKernelDebuggerInformation,
    SystemContextSwitchInformation,
    SystemRegistryQuotaInformation,
    SystemExtendServiceTableInformation,
    SystemPrioritySeperation,
    SystemPlugPlayBusInformation,
    SystemDockInformation,
    SystemProcessorSpeedInformation,
    SystemCurrentTimeZoneInformation,
    SystemLookasideInformation
} SYSTEM_INFORMATION_CLASS, *PSYSTEM_INFORMATION_CLASS;
  1. 缓冲区中存储的数据是一个表示返回数组中元素个数的DWORD类型的数据和一个对应结构体的数组,在MSDN上对这个缓冲进行解释时说这个缓冲区的头4个字节存储了对应数组的元素个数,而后面的存储的是对应结构的数组,所以在获取这个结构的数组时需要向后偏移4个字节。这个结构与我们传入的枚举值有关,比如我们在这获取的是进程内核空间中加载的模块信息,即传入的枚举值是SystemModuleInformation,它对应的结构应该是SYSTEM_MODULE_INFORMATION,它们之间的对应关系可以在MSDN中找到。这个结构也需要自己定义,它的定义如下:
typedef struct _SYSTEM_MODULE_INFORMATION  // Information Class 11
{
    ULONG  Reserved[2];
    PVOID  pBase;
    ULONG  Size;
    ULONG  Flags;
    USHORT Index;
    USHORT Unknown;
    USHORT LoadCount;
    USHORT ModuleNameOffset;
    CHAR   ImageName[256];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;

下面就是这个的代码:

void EnumKernelModules()
{
    HMODULE hNtDll = LoadLibrary(_T("ntdll.dll"));
    if (INVALID_HANDLE_VALUE == hNtDll)
    {
        printf("加载ntdll.dll失败\n");
        return ;
    }

    ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll, "ZwQuerySystemInformation");
    if (NULL == ZwQuerySystemInformation)
    {
        printf("导出函数失败\n");
        return;
    }

    PULONG pBuffInfo = NULL;
    DWORD dwSize = 0;
    ZwQuerySystemInformation(SystemModuleInformation, pBuffInfo, 0, &dwSize);
    pBuffInfo = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSize);
    NTSTATUS status = ZwQuerySystemInformation(SystemModuleInformation, pBuffInfo, dwSize, &dwSize);
    if (!NT_SUCCESS(status))
    {
        return;
    }
    //在这为了验证之前说的,通过这两句输出发现他们的结果相同
    printf("%d\n", *pBuffInfo);
    printf("%d\n", dwSize / sizeof(SYSTEM_MODULE_INFORMATION));
    PSYSTEM_MODULE_INFORMATION pModuleInfo = (PSYSTEM_MODULE_INFORMATION)((ULONG)pBuffInfo + 4);
    for (int i = 0; i < *pBuffInfo; i++)
    {
        printf("%s\n", pModuleInfo[i].ImageName);
    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏jeremy的技术点滴

SSM项目脚手架

4554
来自专栏DT乱“码”

JSP+ajax+springMVC+MayBatis处理excel上传导入

jsp <div class="subtext1"> <div cla...

2829
来自专栏风中追风

redis 实现分布式锁的演进

比如说:每分钟要执行关闭未支付订单的定时任务,在集群的环境下,如果不做处理,每台服务器都会去执行这个定时任务,显然每个时间段的定时任务只需要执行一次,并不需要每...

7156
来自专栏lgp20151222

整理代码,将一些曾经用过的功能整合进一个spring-boot

由于本人的码云太多太乱了,于是决定一个一个的整合到一个springboot项目里面。

2183
来自专栏jeremy的技术点滴

mybatis-generator使用备忘

3774
来自专栏后端沉思录

mybatis拦截器分表

mybatis提供了拦截器插件用来处理被拦截的方法的某些逻辑.下面会通过创建8张表,当用户注册时,根据对手机号取余入不同的表.

6343
来自专栏Lambda

spring AOP日志管理

Spring AOP 完成日志记录 SpringAOPAspectJsecurity日志记录 Spring AOP 完成日志记录 1、技术目标 掌握S...

3416
来自专栏AhDung

【C#】分享基于Win32 API的服务操作类(解决ManagedInstallerClass.InstallHelper不能带参数安装的问题)

------------------201508250915更新------------------

1452
来自专栏Java3y

图书管理系统【部署开发环境、解决分类、图书、前台页面模块】

前言 巩固Servlet+JSP开发模式,做一个比较完整的小项目. 成果图 该项目包含了两个部分,前台和后台。 前台用于显示 ? 后台用于管理 ? 该项目可分为...

6374
来自专栏我的小碗汤

19 个很有用的 ElasticSearch 查询语句 篇一

为了演示不同类型的 ElasticSearch 的查询,我们将使用书文档信息的集合(有以下字段:title(标题), authors(作者), summary(...

2K5

扫码关注云+社区

领取腾讯云代金券