PE 文件格式:如何在正在运行的 WinNT 程序中找到堆内存空间?

逆向工程 视窗 记忆 聚乙烯
2021-06-16 21:32:33

我一直在研究便携式可执行文件格式,我一直在阅读的一项伟大著作是“ARTeam PE 文件格式教程”,它是 Michael J'OLeary、Randy Kath、Matt Pietrek 等人的研究成果的集合。

但是,我注意到的一件事是为程序分配堆的方式和位置。它是在 9 个标准部分之一中,.bss还是.data在运行时由加载程序附加到所有部分的末尾?

我假设,因为它是动态内存的堆,它没有在实际 PE 中的任何地方指定,但是如果我想扫描堆中的数据,我将如何获得内存地址空间?

具体来说,我想回答以下问题:

  1. 为程序分配堆的方式和位置?
  2. 如果我想扫描堆中的数据,我将如何获得内存地址空间?
1个回答

简短回答:堆、堆栈和其他PE 加载器相关任务不是 PE 标准或定义的一部分。

一般信息

PE 文件没有描述可执行文件的整个内存空间。它只包含执行程序所需的数据,操作系统保留在用户不知情的情况下添加额外区域的权利。进程运行和操作所需的堆、堆栈和其他内部存储器区域之类的东西不是 PE 文件(或任何可执行文件)的责任。

PE 不定义堆,它请求从操作系统为它分配一个堆(这AllocateHeap是一个 Windows API,这样做)。没有必要为 PE 文件中的堆“占位符”实际占用空间。进程拥有的堆栈、PEB 和其他内存对象也是如此。

此外,用户(即程序员)通常甚至不需要调用AllocateHeap它的进程来拥有一个堆。操作系统通常在加载进程时为其分配一个默认堆(通过加载器本身或通过操作系统在将控制权交给 PE 的入口点之前运行的启动代码)。其他时候,编译器使用分配堆的代码作为代码的前缀。

类似地,堆栈是作为进程创建的一部分分配的,而不是 PE 的一部分或由它定义。这是强制性的,因为没有堆栈就不能存在进程(尽管 PE 可以设置分配堆栈大小)。

如果您有兴趣了解 NT Loader(和 PE 文件格式),建议您查看以下文章和资源:

您可能已经阅读了其中的一些内容,但我将其中的大部分内容包括在内以供将来参考。

有关堆和其他类型内存的大量信息是 windows 内存管理(相对较大)主题的一部分,您可能还想深入了解,这里有一些关于堆和内存管理的文章:

技术问题

  1. 为程序分配堆的方式和位置?

堆是在创建(堆)时保留提交的内存页操作系统分配指定的页面,这些页面位于实际 RAM 和/或页面文件上

正如我之前提到的,一个进程可以有多个堆(但总是至少有一个,默认为堆)。进程在运行时分配和释放额外的堆,根据运行时逻辑,进程可以具有不同数量的堆。

有关如何创建默认堆(或更准确地说是第一个堆,因为进程可能会将其默认堆更改为稍后分配的堆)的简短描述,请参见上文。

  1. 如果我想扫描堆中的数据,我将如何获得内存地址空间?

这在一定程度上取决于您要扫描的数据的目的和具体原因以及类型。如果您想扫描进程内存中任何位置的数据,您应该使用VirtualQueryVirtualQueryEx所有提交的内存页面。这不仅可以让您扫描所有可用的堆,还可以让您扫描堆栈、PE 部分和程序使用的其他内存(例如,直接使用 分配的页面VirtualAlloc)。

如果要获取特定(或多个堆)的地址范围,则需要使用一些Heap 内存函数,例如msdnGetProcessHeap GetProcessHeaps