Reputation: 1039
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
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:
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;
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