在Windows映射程序的DLL之前,如何保留内存区域?

时间:2016-03-04 00:15:57

标签: c++ c windows winapi memory

我的Windows程序需要使用非常特定的内存区域。不幸的是,Windows在内存中加载了相当多的DLL,并且由于ASLR,它们的位置是不可预测的,因此它们可能最终被映射到我的程序需要使用的区域。在Linux上,Wine通过使用预加载器应用程序来解决此问题,该应用程序保留内存区域,然后手动加载并执行实际图像和动态链接器。我假设在Windows上不可能使用特定的方法,但有没有其他方法可以保证内存的保留区域不被DLL或进程堆使用?

如果有帮助,内存区域是固定的并且在编译时是已知的。此外,我知道可以使用增强型缓解体验工具包在注册表或每个进程中在系统范围内禁用ASLR,但我不想要求我的用户这样做。

1 个答案:

答案 0 :(得分:2)

我想我终于使用了类似于dxiv在评论中建议的方法。我使用DefaultTreeModel/FIXED编译器标志构建了一个基本可执行文件,而不是使用虚拟DLL,而是在我的保留区域的开头加载。可执行文件的代码包含一个未初始化的数组,可确保图像覆盖内存中所需的地址,但不占用文件中的任何额外空间:

/BASE

在运行时,可执行文件将自身复制到内存中的新位置,并更新Process Environment Block中的几个字段以指向它。在不更新字段的情况下,调用某些函数(如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; } } 构建了可执行文件,只是为了减小它的大小和运行时加载的DLL数量,因此使用了/NODEFAULTLIB内在函数。如果您愿意,可能会链接到MSVCRT,然后将__movsb替换为__movsb。您也可以从memcpy导入memcpy或自行编写。

将可执行文件移开后,我调用包含其余代码的DLL中的函数。 DLL使用ntdll.dll来删除原始的PE映像,这为我提供了一个很好的4MB +内存块,保证不包含映射文件,线程堆栈或堆。

使用这种技术时要记住以下几点:

  1. 这是一个巨大的黑客攻击。我觉得写它很脏,很可能在未来的Windows版本中崩溃。 我还没有在Windows 7以外的任何地方测试过这个。此代码至少可以在Windows 7和Windows 10上运行。
  2. 由于可执行文件是使用UnmapViewOfFile构建的,因此其代码与位置无关,您只能跳转到重定位的可执行文件。
  3. 如果调用/FIXED /BASE的DLL函数返回,程序将崩溃,因为我们调用的代码部分不再存在。我使用UnmapViewOfFile来确保函数永远不会返回。
  4. 重新定位的PE图像中的某些部分(如包含代码的部分)可以使用ExitProcess释放,以释放一些物理内存。
  5. 我的代码不需要重新排序加载程序数据表条目。它似乎工作正常,但如果某些东西依赖于按图像地址排序的条目,它可能会破坏。
  6. 某些反病毒程序可能会对这些内容产生怀疑。至少,Microsoft Security Essentials并没有抱怨。
  7. 事后看来,dxiv的虚拟DLL方法可能更容易,因为我不需要弄乱PEB。但我坚持使用这种技术,因为可执行文件更有可能在其所需的基址上加载。虚拟DLL方法对我来说并不适用。在Windows已经保留了我需要的内存区域之后,Ntdll会加载DLL。