我的Windows程序需要使用非常特定的内存区域。不幸的是,Windows 在内存中加载了相当多的 DLL,并且由于 ASLR,它们的位置是不可预测的,因此它们最终可能会映射到我的程序需要使用的区域。在 Linux 上,Wine 通过使用预加载器应用程序来解决此问题,该应用程序保留内存区域,然后手动加载并执行实际的图像和动态链接器。我假设特定方法在 Windows 上是不可能的,但是有没有另一种方法可以获取保证不会被 DLL 或进程堆使用的保留内存区域?
如果有帮助,则内存区域是固定的,并且在编译时是已知的。此外,我知道可以使用增强的缓解体验工具包在注册表或每个进程中在系统范围内禁用 ASLR,但我不想要求我的用户这样做。
我想我终于使用类似于 dxiv 在评论中建议的方法得到了它。我没有使用虚拟 DLL,而是生成一个基本的可执行文件,该可执行文件使用 /FIXED
和 /BASE
编译器标志在保留区域的开头加载。可执行文件的代码包含一个未初始化的数组,该数组确保映像覆盖内存中所需的地址,但不占用文件中的任何额外空间:
unsigned char Reserved[4194304]; // 4MB
在运行时,可执行文件将自身复制到内存中的新位置,并更新流程环境块中的几个字段以指向它。如果不更新字段,调用某些函数(如 FormatMessage
)会导致崩溃。
#include <intrin.h>
#include <windows.h>
#include <winternl.h>
#pragma intrinsic(__movsb)
void Relocate() {
void *Base, *NewBase;
ULONG SizeOfImage;
PEB *Peb;
LIST_ENTRY *ModuleList, *NextEntry;
/* Get info about the PE image. */
Base = GetModuleHandleW(NULL);
SizeOfImage = ((IMAGE_NT_HEADERS *)(((ULONG_PTR)Base) +
((IMAGE_DOS_HEADER *)Base)->e_lfanew))->OptionalHeader.SizeOfImage;
/* Allocate memory to hold a copy of the PE image. */
NewBase = VirtualAlloc(NULL, SizeOfImage, MEM_COMMIT, PAGE_READWRITE);
if (!NewBase) {
ExitProcess(GetLastError());
}
/* Copy the PE image to the new location using __movsb since we don't have
a C library. */
__movsb(NewBase, Base, SizeOfImage);
/* Locate the Process Environment Block. */
Peb = (PEB *)__readfsdword(0x30);
/* Update the ImageBaseAddress field of the PEB. */
*((PVOID *)((ULONG_PTR)Peb + 0x08)) = NewBase;
/* Update the base address in the PEB's loader data table. */
ModuleList = &Peb->Ldr->InMemoryOrderModuleList;
NextEntry = ModuleList->Flink;
while (NextEntry != ModuleList) {
LDR_DATA_TABLE_ENTRY *LdrEntry = CONTAINING_RECORD(
NextEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if (LdrEntry->DllBase == Base) {
LdrEntry->DllBase = NewBase;
break;
}
NextEntry = NextEntry->Flink;
}
}
我用/NODEFAULTLIB
构建可执行文件只是为了减小它的大小和运行时加载的 DLL 数量,因此使用__movsb
内在的。如果您愿意,您可能可以链接到 MSVCRT,然后用 memcpy
替换__movsb
。您还可以从ntdll.dll
导入memcpy
或编写自己的。
将可执行文件移开后,我在包含其余代码的 DLL 中调用一个函数。DLL 使用UnmapViewOfFile
来摆脱原始 PE 映像,这为我提供了一个不错的 4MB+ 内存块,保证不包含映射文件、线程堆栈或堆。
使用此技术时要记住的几件事:
- 这是一个巨大的黑客。我觉得写它很脏,它很可能在未来版本的 Windows 中分崩离析。
- 由于可执行文件是用
/FIXED /BASE
构建的,因此其代码与位置无关,您不能只是跳转到重新定位的可执行文件。 - 如果调用
UnmapViewOfFile
的 DLL 函数返回,程序将崩溃,因为我们从中调用的代码部分不再存在。我使用ExitProcess
来确保函数永远不会返回。 - 重新定位的 PE 映像中的某些部分(如包含代码的部分)可以使用
VirtualFree
释放一些物理内存。 - 我的代码不会打扰重新排序加载程序数据表条目。它似乎以这种方式工作正常,但如果某些内容取决于按图像地址排序的条目,它可能会中断。
- 一些防病毒程序可能会对此内容产生怀疑。Microsoft Security Essentials至少没有抱怨。