Выделение памяти в диапазоне 2 ГБ - PullRequest
0 голосов
/ 17 февраля 2019

Я пишу функцию, которая позволит пользователю выделять память в пределах 2 ГБ +/- от указанного адреса.Я запрашиваю память, чтобы найти свободную страницу, и размещаю там.Это для x64 прыжков на батуте , так как я использую относительную инструкцию jmp.

Моя проблема в том, что NtQueryVirtualMemory завершается с ошибкой STATUS_ACCESS_VIOLATION, поэтому всегда возвращает 0. IЯ запутался в том, почему это происходит, потому что min (самый низкий из возможных адресов) кажется свободным, когда я проверяю в Process Explorer.

LPVOID Allocate2GBRange(UINT_PTR address, SIZE_T dwSize)
{
    NtQueryVirtualMemory_t NtQueryVirtualMemory = (NtQueryVirtualMemory_t)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQueryVirtualMemory");

    UINT_PTR min, max;
    min = address >= 0x80000000 ? address - 0x80000000 : 0;
    max = address < (UINTPTR_MAX - 0x80000000) ? address + 0x80000000 : UINTPTR_MAX;

    MEMORY_BASIC_INFORMATION mbi = { 0 };
    while (min < max)
    {
        NTSTATUS a = NtQueryVirtualMemory(INVALID_HANDLE_VALUE, min, MemoryBasicInformation, &mbi, sizeof(MEMORY_BASIC_INFORMATION), NULL);
        if (a)
            return 0;

        if (mbi.State == MEM_FREE)
        {
            LPVOID addr = VirtualAlloc(mbi.AllocationBase, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
            if (addr)
                return addr;
        }

        min += mbi.RegionSize;
    }
}

1 Ответ

0 голосов
/ 17 февраля 2019

сначала несколько общих замечаний (не об ошибках)

очень странно с моей стороны смешивать NtQueryVirtualMemory с VirtualAlloc.существует смысл или использовать

  • NtQueryVirtualMemory с NtAllocateVirtualMemory

или

  • VirtualQuery с VirtualAlloc

NtQueryVirtualMemory не имеет никакой дополнительной функциональности по сравнению с VirtualQueryEx (NtAllocateVirtualMemory имеет дополнительную функциональность по сравнению с VirtualAllocEx с помощью параметра ZeroBits)

, если уже используется NtQueryVirtualMemory не нужно GetProcAddress - вы можете использовать статическое связывание с ntdll.lib или ntdllp.lib с wdk - этот API был, существуют и будут экспортированы из ntdll.dll как VirtualQuery экспортированы из kernel32.dll , и вы свяжетесь с kernel32.lib , и если вы все равно захотите использоватьGetProcAddress - существует смысл, делайте это не каждый раз, когда вызывается Allocate2GBRange, а один раз.и основной результат проверки звонка - может быть GetProcAddress вернуть 0?если вы уверены, что GetProcAddress никогда не потерпит неудачу - вы уверены, что NtQueryVirtualMemory всегда экспортируется из ntdll.dll - поэтому используйте статическую связь с ntdll [p] .lib

тогда INVALID_HANDLE_VALUE на месте ProcessHandle выглядят очень не родными, несмотря на правильность.лучше использовать макрос NtCurrentProcess() здесь или GetCurrentProcess().но в любом случае, потому что вы используете api kernel32 - без всякой причины используйте NtQueryVirtualMemory вместо VirtualQuery здесь

вам не нужно ноль init mbi перед вызовами - это единственный параметр, а потому что изначально всегда min < max лучше использовать do {} while (min < max) цикл вместо while(min < max) {}


Теперь о критических ошибках в вашем коде:

  • используйте mbi.AllocationBase - когда mbi.State == MEM_FREE - в этом случаеmbi.AllocationBase == 0 - так вы скажете VirtualAlloc выделить в любое свободное пространство.
  • min += mbi.RegionSize; - mbi.RegionSize от mbi.BaseAddress - поэтому добавьтеmin неверно - вам нужно использовать min = (UINT_PTR)mbi.BaseAddress + mbi.RegionSize;
  • , затем при вызове VirtualAlloc (когда lpAddress! = 0) вы должны использовать MEM_COMMIT|MEM_RESERVE вместо MEM_COMMIT.

и адрес, который передается на VirtualAlloc - пароль mbi.AllocationBase (просто 0) неверен.но передайте mbi.BaseAddress в случае, если мы нашли mbi.State == MEM_FREE регион также не правильно.Зачем ?с VirtualAlloc

Если память резервируется, указанный адрес округляется до ближайшего кратного гранулярности выделения.

это означает, что VirtualAllocдействительно попробуйте выделить память не из переданного mbi.BaseAddress, а из mbi.BaseAddress & ~(dwAllocationGranularity - 1) - меньшего адреса.но этот адрес может быть уже занят, в результате вы получите статус STATUS_CONFLICTING_ADDRESSES (ERROR_INVALID_ADDRESS win32).

для конкретного примера - пусть у вас есть

[00007FF787650000, 00007FF787673000) busy memory
[00007FF787673000, ****************) free memory

и изначально ваш min будет в [00007FF787650000, 00007FF787673000) занятой области памяти - в результате вы получите mbi.BaseAddress == 0x00007FF787650000 и mbi.RegionSize == 0x23000, потому что область занята - вы попытаетесь использовать следующий регион по mbi.BaseAddress + mbi.RegionSize; - так по адресу 00007FF787673000.вы получили mbi.State == MEM_FREE за это, но если вы попытаетесь позвонить VirtualAlloc с 00007FF787673000 адресом - он округлил этот адрес до 00007FF787670000, потому что теперь гранулярность распределения равна 0x10000.но 00007FF787670000 принадлежит области занятой памяти [00007FF787650000, 00007FF787673000) - в результате VirtualAlloc завершится неудачно с STATUS_CONFLICTING_ADDRESSES (ERROR_INVALID_ADDRESS).

, поэтому вам нужно использовать (BaseAddress + (AllocationGranularity-1)) & ~(AllocationGranularity-1) действительно - адрес округлен до до ближайшего кратного гранулярности выделения.

весь код может выглядеть следующим образом:

PVOID Allocate2GBRange(UINT_PTR address, SIZE_T dwSize)
{
    static ULONG dwAllocationGranularity;

    if (!dwAllocationGranularity)
    {
        SYSTEM_INFO si;
        GetSystemInfo(&si);
        dwAllocationGranularity = si.dwAllocationGranularity;
    }

    UINT_PTR min, max, addr, add = dwAllocationGranularity - 1, mask = ~add;

    min = address >= 0x80000000 ? (address - 0x80000000 + add) & mask : 0;
    max = address < (UINTPTR_MAX - 0x80000000) ? (address + 0x80000000) & mask : UINTPTR_MAX;

    ::MEMORY_BASIC_INFORMATION mbi; 
    do 
    {
        if (!VirtualQuery((void*)min, &mbi, sizeof(mbi))) return NULL;

        min = (UINT_PTR)mbi.BaseAddress + mbi.RegionSize;

        if (mbi.State == MEM_FREE)
        {
            addr = ((UINT_PTR)mbi.BaseAddress + add) & mask;

            if (addr < min && dwSize <= (min - addr))
            {
                if (addr = (UINT_PTR)VirtualAlloc((PVOID)addr, dwSize, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE))
                    return (PVOID)addr;
            }
        }


    } while (min < max);

    return NULL;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...