简体   繁体   English

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

[英]How do I reserve memory regions before Windows maps my program's DLLs?

My Windows program needs to use very specific regions of memory. 我的Windows程序需要使用非常特定的内存区域。 Unfortunately, Windows loads quite a few DLLs in memory and because of ASLR, their locations are not predictable, so they could end up being mapped into regions that my program needs to use. 不幸的是,Windows在内存中加载了相当多的DLL,并且由于ASLR,它们的位置是不可预测的,因此它们可能最终被映射到我的程序需要使用的区域。 On Linux, Wine solves this problem by using a preloader application which reserves memory regions and then manually loads and executes the actual image and dynamic linker. 在Linux上,Wine通过使用预加载器应用程序来解决此问题,该应用程序保留内存区域,然后手动加载并执行实际图像和动态链接器。 I assume that specific method is not possible on Windows, but is there another way to get reserved regions of memory that are guaranteed to not be used by DLLs or the process heap? 我假设在Windows上不可能使用特定的方法,但是有另一种方法可以获得保证不被DLL或进程堆使用的内存保留区域吗?

If it helps, the memory regions are fixed and known at compile time. 如果有帮助,内存区域是固定的并且在编译时是已知的。 Also, I'm aware that ASLR can be disabled system-wide in the registry or per-process using the Enhanced Mitigation Experience Toolkit, but I don't want to require my users to do that. 另外,我知道可以使用增强型缓解体验工具包在注册表或每个进程中在系统范围内禁用ASLR,但我不想要求我的用户这样做。

I think I finally got it using a method similar to what dxiv suggested in the comments. 我想我终于使用了类似于dxiv在评论中建议的方法。 Instead of using a dummy DLL, I build a basic executable that loads at the beginning of my reserved region using the /FIXED and /BASE compiler flags. 我没有使用虚拟DLL,而是使用/FIXED/BASE编译器标志构建一个基本可执行文件,该可执行文件在保留区域的开头加载。 The code for the executable contains an uninitialized array that ensures the image covers the needed addresses in memory, but doesn't take up any extra space in the file: 可执行文件的代码包含一个未初始化的数组,可确保图像覆盖内存中所需的地址,但不会占用文件中的任何额外空间:

unsigned char Reserved[4194304]; // 4MB

At runtime, the executable copies itself to a new location in memory and updates a couple of fields in the Process Environment Block to point to it. 在运行时,可执行文件将自身复制到内存中的新位置,并更新Process Environment Block中的几个字段以指向它。 Without updating the fields, calling certain functions like FormatMessage would cause a crash. 在不更新字段的情况下,调用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;
    }
}

I built the executable with /NODEFAULTLIB just to reduce its size and the number of DLLs loaded at runtime, hence the use of the __movsb intrinsic. 我使用/NODEFAULTLIB构建可执行文件只是为了减小它的大小和运行时加载的DLL的数量,因此使用了__movsb内在函数。 You could probably get away with linking to MSVCRT if you wanted to and then replace __movsb with memcpy . 如果你愿意,你可以放弃链接到MSVCRT,然后用memcpy替换__movsb You can also import memcpy from ntdll.dll or write your own. 您也可以从ntdll.dll导入memcpy或自己编写。

Once the executable is moved out of the way, I call a function in a DLL that contains the rest of my code. 一旦将可执行文件移开,我就会调用包含其余代码的DLL中的函数。 The DLL uses UnmapViewOfFile to get rid of the original PE image, which gives me a nice 4MB+ chunk of memory to work with, guaranteed not to contain mapped files, thread stacks, or heaps. DLL使用UnmapViewOfFile来摆脱原始的PE映像,这为我提供了一个很好的4MB +内存块,保证不包含映射文件,线程堆栈或堆。

A few things to keep in mind with this technique: 使用这种技术时要记住以下几点:

  1. This is a huge hack. 这是一个巨大的黑客。 I felt dirty writing it and it very well could fall apart in future versions of Windows. 我觉得写它很脏,很可能在未来的Windows版本中崩溃。 I also haven't tested this on anything other than Windows 7. 除了Windows 7之外,我还没有测试过这个。 This code works on Windows 7 and Windows 10, at least. 此代码至少适用于Windows 7和Windows 10。
  2. Since the executable is built with /FIXED /BASE , its code is not position-independent and you can't just jump to the relocated executable. 由于可执行文件是使用/FIXED /BASE构建的,因此其代码与位置无关,您不能只跳转到重定位的可执行文件。
  3. If the DLL function that calls UnmapViewOfFile returns, the program will crash because the code section we called from doesn't exist anymore. 如果调用UnmapViewOfFile的DLL函数返回,程序将崩溃,因为我们调用的代码部分不再存在。 I use ExitProcess to ensure the function never returns. 我使用ExitProcess来确保函数永远不会返回。
  4. Some sections in the relocated PE image like those containing code can be released using VirtualFree to free up some physical memory. 可以使用VirtualFree重定位的PE映像中的某些部分(如包含代码的部分)以释放一些物理内存。
  5. My code doesn't bother re-sorting the loader data table entries. 我的代码不打扰重新排序加载器数据表条目。 It seems to work fine that way, but it could break if something were to depend on the entries being ordered by image address. 它似乎工作正常,但如果某些东西依赖于图像地址排序的条目,它可能会破坏。
  6. Some anti-virus programs might get suspicious about this stuff. 一些反病毒程序可能会对这些东西产生怀疑。 Microsoft Security Essentials didn't complain, at least. 至少,Microsoft Security Essentials没有抱怨。
  7. In hindsight, dxiv's dummy DLL method may have been easier, because I wouldn't need to mess with the PEB. 事后看来,dxiv的虚拟DLL方法可能更容易,因为我不需要弄乱PEB。 But I stuck with this technique because the executable is more likely to be loaded at its desired base address. 但我坚持使用这种技术,因为可执行文件更有可能被加载到其所需的基址。 The dummy DLL method didn't work for me. 虚拟DLL方法对我不起作用。 DLLs are loaded by Ntdll after Windows has already reserved regions of memory that I need. 在Windows已经保留了我需要的内存区域之后,Ntdll会加载DLL。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM