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

Dav*_*own 5 c c++ memory windows winapi

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

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

Dav*_*own 2

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

unsigned char Reserved[4194304]; // 4MB
Run Code Online (Sandbox Code Playgroud)

在运行时,可执行文件将自身复制到内存中的新位置,并更新进程环境块中的几个字段以指向它。如果不更新字段,调用某些函数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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我构建可执行文件/NODEFAULTLIB只是为了减少其大小和运行时加载的 DLL 数量,因此使用了__movsb内部函数。如果您愿意,您可能可以链接到 MSVCRT,然后替换__movsbmemcpy. 您还可以导入memcpyntdll.dll编写自己的。

一旦可执行文件移开,我就会调用 DLL 中包含其余代码的函数。DLL 用于UnmapViewOfFile删除原始 PE 映像,这为我提供了 4MB 以上的不错的内存块可供使用,并保证不包含映射文件、线程堆栈或堆。

使用此技术需要记住以下几点:

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