• 枚举进程中的模块


    在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!
    ");
            return ;
        }
    
        peFile.InitDataDirectoryTable();
        PIMAGE_IMPORT_DESCRIPTOR pImportTable  = peFile.GetImportDescriptor();
        while(!peFile.IsEndOfImportTable(pImportTable))
        {
            printf("%s
    ", 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
    ",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
    ", 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失败
    ");
            return ;
        }
    
        ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll, "ZwQuerySystemInformation");
        if (NULL == ZwQuerySystemInformation)
        {
            printf("导出函数失败
    ");
            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
    ", *pBuffInfo);
        printf("%d
    ", dwSize / sizeof(SYSTEM_MODULE_INFORMATION));
        PSYSTEM_MODULE_INFORMATION pModuleInfo = (PSYSTEM_MODULE_INFORMATION)((ULONG)pBuffInfo + 4);
        for (int i = 0; i < *pBuffInfo; i++)
        {
            printf("%s
    ", pModuleInfo[i].ImageName);
        }
    }
  • 相关阅读:
    redisredis客户端windows下安装
    SpringBootSpringBoot自动配置原理
    redis启动遇到的几个错误
    【服务器数据恢复】Linux环境下RAID6中磁盘被重组为raid5的数据恢复案例
    【服务器数据恢复】raid0数据恢复案例&如何通过网络快速回迁数据
    【服务器数据恢复】RAID5磁盘离线但热备盘未激活导致RAID5崩溃的数据恢复案例
    【服务器虚拟化数据恢复】Xen Server虚拟磁盘被破坏的数据恢复案例
    【服务器虚拟化数据恢复】ESXI虚拟机快照被误还原导致数据丢失的数据恢复案例
    【服务器数据恢复】服务器reiserfs文件系统损坏的数据恢复案例
    【服务器数据恢复】执行fsck后Ext4文件挂载报错的数据恢复案例
  • 原文地址:https://www.cnblogs.com/lanuage/p/7725691.html
Copyright © 2020-2023  润新知