前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >枚举进程中的模块

枚举进程中的模块

作者头像
Masimaro
发布2018-08-31 15:49:03
1.6K0
发布2018-08-31 15:49:03
举报

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

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

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

代码语言:javascript
复制
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;
        DWORD   OriginalFirstThunk;
    };
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain; 
    DWORD   Name;
    DWORD   FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;

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

代码语言:javascript
复制
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获取不到,具体原因我没有深入研究)获取对应的文件路径。下面是具体的代码:

代码语言:javascript
复制
    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中导出,两个函数的参数用法完全相同,只是一个是比较上层一个比较底层而已。在这主要说明一个,另一个完全一样:

代码语言:javascript
复制
NTSTATUS WINAPI ZwQuerySystemInformation(
  __in       SYSTEM_INFORMATION_CLASS SystemInformationClass,
  __inout    PVOID SystemInformation,
  __in       ULONG SystemInformationLength,
  __out_opt  PULONG ReturnLength
);

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

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

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

代码语言:javascript
复制
#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中找到。这个结构也需要自己定义,它的定义如下:
代码语言:javascript
复制
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;

下面就是这个的代码:

代码语言:javascript
复制
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);
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-10-24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 解析PE文件来获取其中的dll
  • 解析进程地址空间中的模块
    • 获取内核地址空间中的模块
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档