强制在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,我们可以使用VirtualAlloc
和MEM_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