强制在Windows上以32位进程加载DLL以上2GB(0x80000000)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了强制在Windows上以32位进程加载DLL以上2GB(0x80000000)相关的知识,希望对你有一定的参考价值。

要在我们的调试器中测试一个角落情况,我需要提出一个程序,其DLL加载超过2GB(0x80000000)。目前的测试用例是一个多GB游戏,它加载了超过700个DLL,我希望有更简单和更小的东西。有没有办法在没有太多摆弄的情况下可靠地实现它?我假设我需要使用/LARGEADDRESSAWARE并以某种方式消耗足够的VA空间来使新的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
", i, p);
     else
     {
       printf("%d: failed!
", i);
       break;
     }
   }
  }
  printf("finished VirtualAlloc. Loading  a DLL.
");
  //getchar();
  HMODULE hDll = LoadLibrary("winhttp");

  printf("DLL base: %p.
", 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个链接器选项:

请注意,link.exe仅允许图像完全位于3GB(0xC0000000)以下的32位图像。换句话说,他希望ImageBase + ImageSize <= 0xC0000000所以说/BASE:0xB0000000会好的,/BASE:0xBFFF0000只有你的图像大小<= 0x10000和/BASE:0xC0000000和更高我们总是得到错误LNK1249 - 图像超出最大范围与基地址和大小

EXE强制也必须有/LARGEADDRESSAWARE,因为所有4GB空间仅适用于基于EXE选项的wow64进程。

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

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

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

for hook当然可以用一些detour lib,但可以用DRx断点挂钩 - 将一些Drx寄存器设置为NtMapViewOfSection地址。并设置AddVectoredExceptionHandler - 处理异常。如果进程不在调试器下,这将是完美的工作。但是在调试器下它会中断 - 大多数调试器都会在单步异常下停止,并且通常没有选项不处理它而是传递给应用程序。当然我们可以在调试器下启动程序,并在以后加载它 - 在dll加载之后。或者可以在单独的线程中执行此任务并从调试器中隐藏此线程。这里的缺点 - 调试器在这种情况下没有得到关于dll加载的通知而没有为此加载符号。但对于你没有src代码的外部(系统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;
}

以上是关于强制在Windows上以32位进程加载DLL以上2GB(0x80000000)的主要内容,如果未能解决你的问题,请参考以下文章

Win32 32 位进程加载 64 位 kernel32.dll

如何从WOW64进程注入x86 DLL到x64进程

在 64 位操作系统上以 32 位和 64 位运行 IIS 的优缺点是啥?

使用 C++ 从 32 位进程访问 64 位 dll

64位系统下注册32位dll文件

通过 COM 在 64 位进程中使用 32 位 DLL