Принудительная загрузка библиотеки DLL размером более 2 ГБ (0x80000000) в 32-разрядном процессе в Windows
Чтобы проверить угловой пример в нашем отладчике, мне нужно найти программу, в которой DLL загружена более 2 ГБ (0x80000000
). Текущий тестовый пример - игра с несколькими ГБ, которая загружает>700 DLL, и я хотел бы иметь что-то попроще и меньше. Есть ли способ добиться этого надежно без особых хлопот? Я предполагаю, что мне нужно использовать /LARGEADDRESSAWARE
и каким-то образом потреблять достаточно пространства VA, чтобы поднять новые библиотеки DLL больше 2 ГБ, но я не совсем уверен в деталях...
2 ответа
Для вашей собственной DLL вам нужно установить 3 линкера:
/LARGEADDRESSAWARE
/DYNAMICBASE:NO
/BASE:"0x********"
обратите внимание, что link.exe разрешает только полное изображение ниже 3GB (0xC0000000
) для 32-битного изображения. другими словами, он хочет этого ImageBase + ImageSize <= 0xC0000000
так сказать /BASE:0xB0000000
будет в порядке, /BASE:0xBFFF0000
только если размер вашего изображения <= 0x10000 и для /BASE:0xC0000000
и выше мы всегда получаем ошибку LNK1249 - изображение превышает максимальный размер с базовым адресом и размером размера
также EXE обязательно должен иметь /LARGEADDRESSAWARE
также, потому что все 4 ГБ пространства доступны для процесса wow64, основываясь только на параметрах EXE.
если мы хотим сделать это для внешней DLL - здесь вопрос сложнее. Прежде всего - может ли эта DLL исправить ситуацию? (загрузить базу> 0x80000000
) Хорошо. давай проверим это. любой API (включая самый низкий уровень LdrLoadDll
) не позволяйте указывать базовый адрес, для загрузки DLL. здесь существует только хук-решение.
когда библиотека загружена, внутренний всегда вызывается ZwMapViewOfSection
и это 3-й параметр BaseAddress
- Указатель на переменную, которая получает базовый адрес представления. если мы установим эту переменную в 0 - система сама выберет загруженный адрес. если мы установим это на конкретный адрес - представление карты системы (в нашем случае это изображение DLL) только по этому адресу, или вернем ошибку STATUS_CONFLICTING_ADDRESSES
,
рабочее решение - перехватить вызов ZwMapViewOfSection
и заменить значение переменной, до какой точки BaseAddress
, для поиска адреса> 0x80000000 мы можем использовать VirtualAlloc
с MEM_TOP_DOWN
вариант. примечание - несмотря на ZwMapViewOfSection
также разрешить использование MEM_TOP_DOWN
в AllocationType
параметр, здесь это не будет иметь никакого эффекта - секция в любом случае будет загружена по предпочтительному адресу или сверху вниз из 0x7FFFFFFF
не из 0xFFFFFFFF
, но с VirtualAlloc
MEM_TOP_DOWN
начать поиск с 0xFFFFFFFF
если процесс использовал 4Gb пользовательского пространства. для знать - сколько памяти нужно для раздела - мы можем позвонить ZwQuerySection
с SectionBasicInformation
- несмотря на то, что это недокументировано - для отладки и тестирования - это нормально.
для ловушки, конечно, можно использовать некоторый обходной путь, но можно сделать ловушку с точкой останова DRx - установите для некоторого регистра Drx NtMapViewOfSection
адрес. и установить AddVectoredExceptionHandler
которые обрабатывают исключение. это будет идеальная работа, если процесс не под отладчиком. но при отладчике он ломается - большинство отладчиков всегда останавливаются при одноэтапном исключении и обычно ни один вариант не обрабатывает их, а переходит к приложению. Конечно, мы можем запустить программу не под отладчиком, а прикрепить ее позже - после загрузки dll. или возможно сделать эту задачу в отдельном потоке и скрыть этот поток от отладчика. недостаток здесь - в этом случае отладчик не получает уведомления о загрузке dll и не загружает символы для этого. однако для внешнего (системного dll), для которого у вас нет кода src - это в большинстве случаев может быть не большой проблемой. поэтому решение выйдет, если мы сможем его реализовать). возможный код:
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;
}
Хорошо, это заняло у меня несколько попыток, но мне удалось придумать что-то работающее.
// 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.