在 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,我们可以使用 VirtualAlloc
和 MEM_TOP_DOWN
选项。注意 - 尽管 ZwMapViewOfSection
也允许在 AllocationType
参数中使用 MEM_TOP_DOWN
,但在这里它不需要效果 - 部分无论如何将按首选地址或自上而下从 0x7FFFFFFF
不是来自 0xFFFFFFFF
。但是对于 VirtualAlloc
,如果进程使用 4Gb 用户 space,则 MEM_TOP_DOWN
从 0xFFFFFFFF
开始搜索。知道 - 部分需要多少内存 - 我们可以用 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;
}
为了在我们的调试器中测试极端情况,我需要想出一个程序,其中加载了超过 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,我们可以使用 VirtualAlloc
和 MEM_TOP_DOWN
选项。注意 - 尽管 ZwMapViewOfSection
也允许在 AllocationType
参数中使用 MEM_TOP_DOWN
,但在这里它不需要效果 - 部分无论如何将按首选地址或自上而下从 0x7FFFFFFF
不是来自 0xFFFFFFFF
。但是对于 VirtualAlloc
,如果进程使用 4Gb 用户 space,则 MEM_TOP_DOWN
从 0xFFFFFFFF
开始搜索。知道 - 部分需要多少内存 - 我们可以用 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;
}