Meme Machine
Meme Machine

Reputation: 1039

Allocating Memory Within A 2GB Range

I'm writing a function that will allow the user to allocate memory within 2GB +/- of a specified address. I'm querying the memory to find a free page, and allocating there. This is for x64 trampoline hooking, since I'm using a relative jmp instruction.

My issue is that NtQueryVirtualMemory fails with a STATUS_ACCESS_VIOLATION error, thus always returns 0. I'm confused on why this is happening, because min(the lowest possible address) appears to be free when I check in 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;
    }
}

Upvotes: 2

Views: 1223

Answers (1)

RbMm
RbMm

Reputation: 33804

at first several general notes (not about erroes)

very strange from my look mix NtQueryVirtualMemory with VirtualAlloc. exist sense or use

  • NtQueryVirtualMemory with NtAllocateVirtualMemory

or

  • VirtualQuery with VirtualAlloc

the NtQueryVirtualMemory have no any extra functionality compare to VirtualQueryEx (the NtAllocateVirtualMemory have extra functionality compare to VirtualAllocEx via ZeroBits parameter)

then if already use NtQueryVirtualMemory not need GetProcAddress - you can use static linking with ntdll.lib or ntdllp.lib from wdk - this api was, exist and will be exported from ntdll.dll like VirtualQuery exported from kernel32.dll and you link with kernel32.lib and if you anyway want use GetProcAddress - exist sense do this not every time when Allocate2GBRange is called, but once. and main check result of call - may be GetProcAddress return 0 ? if you sure that GetProcAddress never fail - you sure that NtQueryVirtualMemory always exported from ntdll.dll - so use static linking with ntdll[p].lib

then INVALID_HANDLE_VALUE in place ProcessHandle look very not native, despite correct. better use NtCurrentProcess() macro here or GetCurrentProcess(). but anyway, because you use kernel32 api - no any reason use NtQueryVirtualMemory instead VirtualQuery here

you not need zero init mbi before calls - this is out only parameter, and because initially always min < max better use do {} while (min < max) loop instead while(min < max) {}


now about critical errors in your code:

  • use mbi.AllocationBase - when mbi.State == MEM_FREE - in this case mbi.AllocationBase == 0 - so you tell VirtualAlloc allocate in any free space.
  • min += mbi.RegionSize; - the mbi.RegionSize is from mbi.BaseAddress - so add it to min incorrect - you need use min = (UINT_PTR)mbi.BaseAddress + mbi.RegionSize;
  • then in call VirtualAlloc (when lpAddress != 0) you must use MEM_COMMIT|MEM_RESERVE instead MEM_COMMIT only.

and about address which pass to VirtualAlloc - pass mbi.AllocationBase (simply 0) incorrect. but pass mbi.BaseAddress in case we found mbi.State == MEM_FREE region also not correct. why ? from VirtualAlloc

If the memory is being reserved, the specified address is rounded down to the nearest multiple of the allocation granularity.

this mean that VirtualAlloc really try allocate memory not from passed mbi.BaseAddress but from mbi.BaseAddress & ~(dwAllocationGranularity - 1) - smaller address. but this address can be already busy, as result you got STATUS_CONFLICTING_ADDRESSES (ERROR_INVALID_ADDRESS win32 error) status.

for concrete example - let you have

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

and initially your min will be in [00007FF787650000, 00007FF787673000) busy memory region - as result you got mbi.BaseAddress == 0x00007FF787650000 and mbi.RegionSize == 0x23000, because region is busy - you will try next region at mbi.BaseAddress + mbi.RegionSize; - so at 00007FF787673000 address. you got mbi.State == MEM_FREE for it, but if you try call VirtualAlloc with 00007FF787673000 address - it rounded down this address to the 00007FF787670000 because now allocation granularity is 0x10000. but 00007FF787670000 is belong to the [00007FF787650000, 00007FF787673000) busy memory region - al result VirtualAlloc fail with STATUS_CONFLICTING_ADDRESSES (ERROR_INVALID_ADDRESS).

so you need use (BaseAddress + (AllocationGranularity-1)) & ~(AllocationGranularity-1) really - address rounded up to the nearest multiple of the allocation granularity.

all code can look like:

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;
}

Upvotes: 2

Related Questions