枚举windows进程模块的几种方法—PEB内核结构详解

1. 引言在诸多的场景中(例如软件测试,软件安全研究等领域)经常需要分析在目标进程中具体加载了哪些模块(DLL),以及所加载的模块的信息(如模块基地址,映射文件大小等)。获取这windows进程加载的模块信息,曾经有一个行之有效又很便捷的方法,使用windows提供PSAPI(psapi.dll,windows进程状态信息接口)提供的相关的接口就可以快捷的获取进程及进程加载的模块信息。有关PSAPI接口可以参考psapi.h或者微软的官方文档Psapi.h heade...
摘要由CSDN通过智能技术生成

1. 引言

        在诸多的场景中(例如软件测试,软件安全研究等领域)经常需要分析在目标进程中        具体加载了哪些模块(DLL),以及所加载的模块的信息(如模块基地址,映射文件大小等)。获取这windows进程加载的模块信息,曾经有一个行之有效又很便捷的方法,使用windows提供PSAPI(psapi.dll,windows进程状态信息接口)提供的相关的接口就可以快捷的获取进程及进程加载的模块信息。有关PSAPI接口可以参考psapi.h或者微软的官方文档Psapi.h header - Win32 apps | Microsoft Docs。在这里笔者说一句与本文主题无关的题外话,研究windows内核尽量参考一手的windows官方文档,笔者有一个习惯,在对内核相关的研读时都是尽可能的阅读英文版的windows官方文档和使用windows的原版的工具(例如windbg),孜孜不倦,唯求其真。

 图1-PSAPI微软官方文档

        然而,在这个win64操作系统已经普及,32位程序尚未没落,依然大行其道的时代里,使用PSAPI获取进程中的模块信息已经不再那么有效。在win64的wow64环境中运行的32位程序获取64位进程中模块信息,就无比尴尬,因为windows的PSAPI是不支持的。

       我们可以在微软的官方文档中关于EnumProcessModules函数的描述(https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodules) 一探究竟,如图-2和图-3官方文档中所描述。其大意是说在64位的程序中枚举其它进程中的模块信息可以调用EnumProcessModuleEx接口。在wow64环境运行的32位程序中获取64位进程中的模块信息会返回错误代码299(GetLastError),其对应的错误信息为“仅完成部分的 ReadProcessMemory 或 WriteProcessMemory 请求。”。

 图-2 64位程序使用EnumProcessModulesEx接口

 图-3 EnumProcessModules不支持32位程序中枚举64位程序模块

        既然问题摆在我们面前,那么是否有破解之道呢?这正是笔者写这篇文章的目的,在本文中,会详细阐述一种更底层的,放之四海而皆准的,获取windows进程中模块的方法。该方法适用32为程序和64位程序枚举其它进程(包括32位和64位)中的模块。“授之于鱼不如授之于渔”,然而,笔者写本文的目的不仅仅是分享如何获取进程中的模块信息,而是尽量描述清楚解决该类问题的思路,希望能起到抛砖引玉的作用,能启发读者朋友解决类似的问题。由于笔者技术水平有限,如若有错误或不当欢迎各位业界同仁不吝赐教()。

       熟悉windows内核的朋友都知道,PEB(Process Environment Block,进程环境块)是一个重要的内核数据结构,windows的每个运行的进程中都维护一个PEB数据块其中记录着进程相关的各种信息。在PEB有一个PEB_LDR_DATA的数据,该数据记录着进程已经加载的模块信息。其实windows的PSAPI中的EnumProcessModules的底层实现也是通过PEB_LDE_DATA来遍历进程加载的模块的。“山穷水尽疑无路,柳岸花明又一村”,那么,问题的解决方案似乎近在咫尺了。

2. PEB数据结构

2.1 MSDN文档中的PEB数据结构

       PEB的数据结构会随着操作系统的版本而有差异,可以在微软官方文档(https://docs.microsoft.com/en-us/windows/win32/api/winternl)查看PEB极其相关的数据结构定义。

图4-PEB及相关的数据结构

    如上图所示的数据结构是在微软官方文档上摘录的,在PEB,PEB_LDR_DATA及LDR_DATA_TABLE_ENTRY结构体中有诸多的Reserved(保留)的数据项,表明这些数据项的定义可能会随着数据版本的变化而有差异,这些数据项尽量不要使用,如果在迫不得已的情况下使用,要做好操作系统不同版本间的兼容处理。

    在PEB结构体中的偏移0x0C处指向进程已加载的模块结构体PEB_LDR_DATA指针,关于PEB_LDR_DATA结构体在微软的官宣文档上是如此介绍的”Contains information about the loaded modules for the process.”。关于PEB结构体中其它数据项笔者在此不再赘述,有兴趣的朋友可自行查阅微软官宣文档,或者联系笔者做进一步的沟通交流()。

     在PEB_LDR_DATA结构体中,InMemoryOrderModuleList是一个双向链表节点(LIST_ENTRY结构体),其FLink和Blink均指向已加载的模块信息的结构体LDR_DATA_TABLE_ENTRY的头部。在微软的官方文档中关于InMemoryOrderModuleList是这样描述的“The head of a doubly-linked list that contains the loaded modules

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
PEB(Process Environment Block)是Windows操作系统中的一个重要结构体,它包含了当前进程的环境信息。通过PEB,我们可以获取当前进程模块信息,包括模块的基地址、模块的名称等。 在x64和wow64下,PEB结构体的定义并没有变化,但是由于x64和wow64的指针长度不同,所以在使用PEB时需要注意指针长度的问题。 以下是通过PEB遍历进程模块的代码示例: ```c++ #include <windows.h> #include <winternl.h> #include <iostream> typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA, *PPEB_LDR_DATA; 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 { ULONG TimeDateStamp; PVOID LoadedImports; }; PVOID EntryPointActivationContext; PVOID PatchInformation; LIST_ENTRY ForwarderLinks; LIST_ENTRY ServiceTagLinks; LIST_ENTRY StaticLinks; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; typedef struct _PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; BOOLEAN Spare; HANDLE Mutant; PVOID ImageBaseAddress; PPEB_LDR_DATA LoaderData; PVOID ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PVOID FastPebLock; PVOID AtlThunkSListPtr; PVOID IFEOKey; PVOID CrossProcessFlags; PVOID UserSharedInfoPtr; ULONG SystemReserved; ULONG AtlThunkSListPtr32; PVOID ApiSetMap; ULONG TlsExpansionCounter; PVOID TlsBitmap; ULONG TlsBitmapBits[2]; PVOID ReadOnlySharedMemoryBase; PVOID HotpatchInformation; PVOID ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; ULONG NumberOfProcessors; ULONG NtGlobalFlag; LARGE_INTEGER CriticalSectionTimeout; ULONG_PTR HeapSegmentReserve; ULONG_PTR HeapSegmentCommit; ULONG_PTR HeapDeCommitTotalFreeThreshold; ULONG_PTR HeapDeCommitFreeBlockThreshold; ULONG_PTR NumberOfHeaps; ULONG_PTR MaximumNumberOfHeaps; PVOID ProcessHeaps; PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; PVOID GdiDCAttributeList; PVOID LoaderLock; ULONG OSMajorVersion; ULONG OSMinorVersion; USHORT OSBuildNumber; USHORT OSCSDVersion; ULONG OSPlatformId; ULONG ImageSubsystem; ULONG ImageSubsystemMajorVersion; ULONG ImageSubsystemMinorVersion; ULONG_PTR ImageProcessAffinityMask; ULONG_PTR GdiHandleBuffer[34]; PVOID PostProcessInitRoutine; PVOID TlsExpansionBitmap; ULONG TlsExpansionBitmapBits[32]; ULONG SessionId; ULARGE_INTEGER AppCompatFlags; ULARGE_INTEGER AppCompatFlagsUser; PVOID pShimData; PVOID AppCompatInfo; UNICODE_STRING CSDVersion; PVOID ActivationContextData; PVOID ProcessAssemblyStorageMap; PVOID SystemDefaultActivationContextData; PVOID SystemAssemblyStorageMap; ULONG_PTR MinimumStackCommit; } PEB, *PPEB; void EnumerateProcessModules(HANDLE hProcess) { PEB peb; ZeroMemory(&peb, sizeof(PEB)); // 获取PEB地址 BOOL bRet = ReadProcessMemory(hProcess, &NtCurrentTeb()->ProcessEnvironmentBlock, &peb, sizeof(PEB), NULL); if (!bRet) { std::cout << "ReadProcessMemory failed!" << std::endl; return; } // 遍历模块列表 PPEB_LDR_DATA pLdrData = peb.LoaderData; if (pLdrData == NULL) { std::cout << "LoaderData is NULL!" << std::endl; return; } PLIST_ENTRY pListHead = &pLdrData->InMemoryOrderModuleList; PLIST_ENTRY pListEntry = pListHead->Flink; while (pListEntry != pListHead) { PLDR_DATA_TABLE_ENTRY pLdrEntry = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); if (pLdrEntry->DllBase != NULL) { // 获取模块基地址和名称 wchar_t szModule[MAX_PATH] = { 0 }; bRet = ReadProcessMemory(hProcess, pLdrEntry->FullDllName.Buffer, szModule, pLdrEntry->FullDllName.Length, NULL); if (bRet) { std::wcout << L"Module Base: " << pLdrEntry->DllBase << L", Module Name: " << szModule << std::endl; } } pListEntry = pListEntry->Flink; } } int main(int argc, char* argv[]) { DWORD dwProcessId = 0; if (argc > 1) { dwProcessId = atoi(argv[1]); } HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (hProcess == NULL) { std::cout << "OpenProcess failed!" << std::endl; return 0; } EnumerateProcessModules(hProcess); CloseHandle(hProcess); return 0; } ``` 需要注意的是,以上代码中使用了一些Windows内部的结构体和函数,如`UNICODE_STRING`、`LIST_ENTRY`、`NtCurrentTeb()`等,需要包含相应的头文件,并且这些结构体和函数都不是官方公开的API,可能会在未来的操作系统版本中发生变化。因此,开发者在使用这些结构体和函数时需要特别注意。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值