在 Windows 的 32 位进程中强制加载超过 2GB (0x80000000) 的 DLL

Force a DLL to be loaded above 2GB (0x80000000) in a 32-bit process on Windows

为了在我们的调试器中测试极端情况,我需要想出一个程序,其中加载了超过 2GB 的 DLL (0x80000000)。当前的测试用例是一个多 GB 的游戏,它加载了 >700 个 DLL,我想要一些更简单、更小的东西。有没有办法在没有太多摆弄的情况下可靠地实现它?我假设我需要使用 /LARGEADDRESSAWARE 并以某种方式消耗足够的 VA space 来将新的 DLL 增加到 2GB 以上,但我对细节不太清楚...

好的,我尝试了几次,但我设法想出了一些可行的方法。

// cl /MT /Ox test.cpp /link /LARGEADDRESSAWARE
// occupy the 2 gigabytes!
#define ALLOCSIZE (64*1024)
#define TWOGB (2*1024ull*1024*1024)

#include <windows.h>
#include <stdio.h>

int main()
{
  int nallocs = TWOGB/ALLOCSIZE;
  for ( int i = 0; i < nallocs+200; i++ )
  {
   void * p = VirtualAlloc(NULL, ALLOCSIZE, MEM_RESERVE, PAGE_NOACCESS);
   if ( i%100 == 0)
   {
     if ( p != NULL )
       printf("%d: %p\n", i, p);
     else
     {
       printf("%d: failed!\n", i);
       break;
     }
   }
  }
  printf("finished VirtualAlloc. Loading  a DLL.\n");
  //getchar();
  HMODULE hDll = LoadLibrary("winhttp");

  printf("DLL base: %p.\n", hDll);
  //getchar();
  FreeLibrary(hDll);
}

在 Win10 x64 上产生:

0: 00D80000
100: 03950000
200: 03F90000
[...]
31800: 7FBC0000
31900: 00220000
32000: 00860000
32100: 80140000
32200: 80780000
32300: 80DC0000
32400: 81400000
32500: 81A40000
32600: 82080000
32700: 826C0000
32800: 82D00000

32900: 83340000
finished VirtualAlloc. Loading  a DLL.
DLL base: 83780000.

对于您自己的 DLL,您需要设置 3 个链接器选项:

请注意,对于 32 位图像,link.exe 仅允许图像完整位于 3GB (0xC0000000) 以下。换句话说,他想要 ImageBase + ImageSize <= 0xC0000000 所以说 /BASE:0xB0000000 没问题,/BASE:0xBFFF0000 只有当你的图像大小 <= 0x10000 并且对于 /BASE:0xC0000000 和更高我们总是得到错误 LNK1249 - image exceeds maximum extent with base地址地址和大小size

also EXE 强制必须也有 /LARGEADDRESSAWARE,因为所有 4GB space 都可用于仅基于 EXE[ 的 wow64 进程=75=] 选项。

如果我们想为外部 DLL 执行此操作 - 这里的问题更难。首先 - 这个 DLL 是否可以正确处理这种情况(加载基础 > 0x80000000)?行。让我们测试一下。任何api(包括最低级别LdrLoadDll)不让指定基地址,用于DLL加载。这里只存在钩子解决方案。

加载库时,内部总是调用 ZwMapViewOfSection 并且它的第 3 个参数 BaseAddress - 指向接收视图基地址的变量的指针。如果我们把这个变量设置为0——系统自己select加载地址。如果我们将其设置为特定地址 - 系统地图视图(DLL 图像在我们的例子中)仅在此地址,或 return 错误 STATUS_CONFLICTING_ADDRESSES

工作解决方案 - 挂钩调用 ZwMapViewOfSection 并替换变量的值,指向 BaseAddress。对于查找地址 > 0x80000000,我们可以使用 VirtualAllocMEM_TOP_DOWN 选项。注意 - 尽管 ZwMapViewOfSection 也允许在 AllocationType 参数中使用 MEM_TOP_DOWN,但在这里它不需要效果 - 部分无论如何将按首选地址或自上而下从 0x7FFFFFFF 不是来自 0xFFFFFFFF。但是对于 VirtualAlloc,如果进程使用 4Gb 用户 space,则 MEM_TOP_DOWN0xFFFFFFFF 开始搜索。知道 - 部分需要多少内存 - 我们可以用 SectionBasicInformation 调用 ZwQuerySection - 尽管这没有记录 - 用于调试和测试 - 这没问题。

for hook 当然可以使用一些迂回的库,但可以使用 DRx 断点进行 hook - 将一些 Drx 寄存器设置为 NtMapViewOfSection 地址。并设置 AddVectoredExceptionHandler - 处理异常。如果进程不在调试器下,这将是完美的工作。但在调试器下它会中断——大多数调试器总是在单步异常下停止,通常没有选项不处理它而是传递给应用程序。当然我们可以不在调试器下启动程序,然后在加载 dll 之后附加它。或者可能在单独的线程中执行此任务并将此线程从调试器中隐藏。这里的缺点 - 在这种情况下,调试器没有得到有关 dll 加载的通知,也没有为此加载符号。然而,对于您没有源代码的外部(系统 dll)——这在大多数情况下可能不是一个大问题。所以解决方案退出,如果我们可以实施它)。可能的代码:

PVOID pvNtMapViewOfSection;

LONG NTAPI OnVex(::PEXCEPTION_POINTERS ExceptionInfo)
{
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP &&
        ExceptionInfo->ExceptionRecord->ExceptionAddress == pvNtMapViewOfSection)
    {
        struct MapViewOfSection_stack 
        {
            PVOID ReturnAddress;
            HANDLE SectionHandle;
            HANDLE ProcessHandle;
            PVOID *BaseAddress;
            ULONG_PTR ZeroBits;
            SIZE_T CommitSize;
            PLARGE_INTEGER SectionOffset;
            PSIZE_T ViewSize;
            SECTION_INHERIT InheritDisposition;
            ULONG AllocationType;
            ULONG Win32Protect;
        } * stack = (MapViewOfSection_stack*)(ULONG_PTR)ExceptionInfo->ContextRecord->Esp;

        if (stack->ProcessHandle == NtCurrentProcess())
        {
            SECTION_BASIC_INFORMATION sbi;

            if (0 <= ZwQuerySection(stack->SectionHandle, SectionBasicInformation, &sbi, sizeof(sbi), 0))
            {
                if (PVOID pv = VirtualAlloc(0, (SIZE_T)sbi.Size.QuadPart, MEM_RESERVE|MEM_TOP_DOWN, PAGE_NOACCESS))
                {
                    if (VirtualFree(pv, 0, MEM_RELEASE))
                    {
                        *stack->BaseAddress = pv;
                    }
                }
            }
        }

        // RESUME_FLAG ( 0x10000) not supported by xp, but anyway not exist 64bit xp
        ExceptionInfo->ContextRecord->EFlags |= RESUME_FLAG;
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

struct LOAD_DATA {
    PCWSTR lpLibFileName;
    HMODULE hmod;
    ULONG dwError;
};

ULONG WINAPI HideFromDebuggerThread(LOAD_DATA* pld)
{
    NtSetInformationThread(NtCurrentThread(), ThreadHideFromDebugger, 0, 0);

    ULONG dwError = 0;

    HMODULE hmod = 0;

    if (PVOID pv = AddVectoredExceptionHandler(TRUE, OnVex))
    {
        ::CONTEXT ctx = {};
        ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
        ctx.Dr7 = 0x404;
        ctx.Dr1 = (ULONG_PTR)pvNtMapViewOfSection;
        if (SetThreadContext(GetCurrentThread(), &ctx))
        {
            if (hmod = LoadLibraryW(pld->lpLibFileName))
            {
                pld->hmod = hmod;
            }
            else
            {
                dwError = GetLastError();
            }

            ctx.Dr7 = 0x400;
            ctx.Dr1 = 0;

            SetThreadContext(GetCurrentThread(), &ctx);
        }
        else
        {
            dwError = GetLastError();
        }
        RemoveVectoredExceptionHandler(pv);
    }
    else
    {
        dwError = GetLastError();
    }

    pld->dwError = dwError;

    return dwError;
}

HMODULE LoadLibHigh(PCWSTR lpLibFileName)
{
    BOOL bWow;
    HMODULE hmod = 0;
    if (IsWow64Process(GetCurrentProcess(), &bWow) && bWow)
    {
        if (pvNtMapViewOfSection = GetProcAddress(GetModuleHandle(L"ntdll"), "NtMapViewOfSection"))
        {
            LOAD_DATA ld = { lpLibFileName };

            if (IsDebuggerPresent())
            {
                if (HANDLE hThread = CreateThread(0, 0, (PTHREAD_START_ROUTINE)HideFromDebuggerThread, &ld, 0, 0))
                {
                    WaitForSingleObject(hThread, INFINITE);
                    CloseHandle(hThread);
                }
            }
            else
            {
                HideFromDebuggerThread(&ld);
            }

            if (!(hmod = ld.hmod))
            {
                SetLastError(ld.dwError);
            }
        }
    }
    else
    {
        hmod = LoadLibrary(lpLibFileName);
    }

    return hmod;
}