如何在 Windows PE 32 位可执行文件中为我的代码洞穴创建 space

How do I make space for my code cave in a Windows PE 32bit executable

所以我想在 minesweeper.exe(典型的 Windows XP 扫雷游戏,link: Minesweeper)中为我的代码洞穴制作一个 space。所以我通过 CFF Explorer 修改了文件的 PE 头以增加 .text 部分的大小。

我尝试将 .text 段的原始大小增加 1000h(新大小为 3B58),但 Windows 无法找到入口点并且游戏无法启动。然后我尝试增加 .rsrc 部分的大小,添加一个新部分,增加图像大小,但是 none 这些尝试是成功的,Windows 说 "This is not x32 executable" .

所以问题来了:如何为我的代码洞穴制作 space?我不想搜索编译器留下的空 space,我希望我的代码有漂亮干净的 1000h 字节。一个教程和一个关于如何在不破坏游戏的情况下做到这一点的详细解释会很棒! (是的,我实际上是在破解扫雷器)

您不能增加一个部分的大小而不会使后面的部分无效(通常是因为它会使这些部分中的偏移量和地址无效)。这仍然是可能的,但它非常容易出错,并且当您有更简单的解决方案时不值得麻烦。

通常,您只需要在 PE 的末尾 添加一个部分 并从代码部分跳转到那里。代码部分(代码洞穴)的末尾通常有一点 space,因此您可以将 JMP(或一些代码存根)放在那里以重定向到新部分。您还可以为数据或新资源或任何您想要的内容添加其他新部分。


注意:我使用了两个工具:CFF explorer 作为 PE 浏览器;十六进制编辑器。

这个文件比较特殊,所以比平时添加新的部分要难一些。

开始吧!

下面是 IMAGE_SECTION_HEADER:

数组的十六进制视图

通常有一些空间可以添加一个新的部分,但在这种特殊情况下,有 none...最后一个部分 header 紧接着是一些东西。

从内容来看,这很可能是一个绑定导入目录,在CFF explorer中确认(绑定目录偏移量为0x248):

绑定的导入目录今天没有用,尤其是对于 ASLR,因此我们可以将整个目录清零(如前一个屏幕截图所示,其大小为 0xA8 字节):

您也可以将数据目录中的绑定导入目录 RVA 清零,尽管这不是严格要求的:

现在,是时候添加新的部分了。

添加新部分

扫雷默认有 3 个部分,因此将部分数量从 3 增加到 4:

转到 header 部分并添加一个新部分(您可以直接在 CFF 资源管理器中进行;我将我的命名为 .foobar,注意部分名称是 最多 8个字符并且不需要以NULL字节结尾):

您需要选择两个号码:

  • 新部分的原始大小(我选择了 0x400);它 必须 FileAlignment 的倍数(在本例中为 0x200)。

  • 新节的虚拟大小(我选的是0x1000);它 必须 SectionAlignement 的倍数(对于这个二进制文件是 0x1000)。

现在我们需要计算另外两个成员,Virtual AddressRaw Address

虚拟地址

让我们以第一部分和第二部分为例。

第一部分从虚拟地址 0x1000 开始,虚拟大小为 0x3A56。下一段虚拟地址必须对齐SectionAlignement(0x1000)所以计算是(这里使用python):

>>> def round_up_multiple_of(number, multiple):
    num = number + (multiple - 1)
    return num - (num % multiple)

>>> hex(round_up_multiple_of(0x1000 + 0x3a56, 0x1000))
'0x5000'

给出正确的 0x5000(.data 部分从虚拟地址 0x5000 开始)。

现在,我们的最后一部分应该从哪里开始?

.rsrc 部分从 0x6000 开始,大小为 0x19160:

>>> hex(round_up_multiple_of(0x6000 + 0x19160, 0x1000))
'0x20000'

所以它必须从虚拟地址 0x20000 开始。将该数字放入 Virtual Address.

原始地址

(通常不需要这样做,因为所有部分都已经对齐,最后一部分必须从文件末尾开始,但我们会这样做)。

提醒一下,原始地址是文件中的地址(不是内存中的地址)。

让我们从一个例子开始(第一和第二部分):

第一节原始地址为0x400,原始大小为0x3c00。 FileAlignement 是 0x200,因此:

>>> hex(round_up_multiple_of(0x400 + 0x3c00, 0x200))
'0x4000'

第二部分应该从文件(它的 Raw address)的 0x4000 处开始,这是正确的。

因此对于我们的新部分,计算是:

  • .rsrc 部分从文件的 0x4200
  • 处开始
  • .rsrc 文件的部分大小为 0x19200
  • FileAligment 是 0x200

计算如下:

>>> hex(round_up_multiple_of(0x4200 + 0x19200, 0x200))
 '0x1d400'

我们的最后一节从文件中的原始地址 0x1d400 开始,并用十六进制编辑器确认:

最后的步骤

需要最后一步,计算可选 header 中的 SizeOfImage 字段。根据 PE 规范,该字段为:

The size (in bytes) of the image, including all headers, as the image is loaded in memory. It must be a multiple of SectionAlignment.

因此计算可以简化为:VirtualAddress + 最后一段的VirtualSize,对齐[=29​​=] (0x1000):

>>> hex(round_up_multiple_of(0x20000 + 0x1000, 0x1000))
'0x21000'

现在,在 CFF 资源管理器中保存所有修改并退出。

为新部分添加空间

最后一步是为最后一节添加所需的字节。当我选择 0x400 的 Raw size 时,我使用十六进制编辑器在 Raw Address (0x1d400) 处插入 0x400 字节。

保存你的文件。如果您遵循所有步骤,它必须按原样工作(在 Win 10 上测试),您可以开始修改可执行且没有任何错误。

如果 0x400 不够,请尝试使用新部分的不同原始大小。

现在你有了一个新的空白部分,剩下的就靠你修改代码了:)