at first several general notes (not about erroes)
very strange from my look mix NtQueryVirtualMemory with VirtualAlloc. exist sense or use 
- NtQueryVirtualMemorywith- NtAllocateVirtualMemory
or 
- VirtualQuerywith- 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- whenmbi.State == MEM_FREE- in this
casembi.AllocationBase == 0- so you tellVirtualAllocallocate in any free space.
- min += mbi.RegionSize;- the- mbi.RegionSizeis from- mbi.BaseAddress- so add it to- minincorrect - you need use- min
= (UINT_PTR)mbi.BaseAddress + mbi.RegionSize;
- then in call VirtualAlloc(when lpAddress != 0) you must useMEM_COMMIT|MEM_RESERVEinsteadMEM_COMMITonly.
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;
}