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



为了测试调试器中的极端情况,我需要想出一个加载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: %pn", 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个链接器选项:

  • /LARGEADDRESSAWARE
  • /DYNAMICBASE:NO
  • /BASE:"0x********"

请注意,该链接.exe仅允许32位图像的全位置低于3GB(0xC0000000(。 换句话说,他想要那个ImageBase + ImageSize <= 0xC0000000所以说/BASE:0xB0000000没问题,/BASE:0xBFFF0000只有当您的图像大小 <= 0x10000 并且对于/BASE:0xC0000000及更高,我们总是得到错误LNK1249 - 图像超过基址地址和大小大小的最大范围

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

如果我们想为外部DLL执行此操作 - 这里的问题更难。 首先 - 这个DLL可以正确处理这种情况(负载基础>0x80000000(吗? 好的,让我们测试一下。 任何 API(包括大多数低级LdrLoadDll(都不允许为DLL加载指定基址。 这里只存在钩子解决方案。

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

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

对于钩子当然可以使用一些绕道库,但可能使用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;
}

最新更新