我正在编写一个函数,该函数将允许用户在指定地址的2GB +/-内分配内存。我正在查询内存以找到一个空闲页,然后在此处进行分配。这是针对x64 trampoline hooking的,因为我使用的是相对的jmp指令。
我的问题是NtQueryVirtualMemory
失败,并出现STATUS_ACCESS_VIOLATION
错误,因此始终返回0。我对此感到困惑,因为当我在Process Explorer中 checkin 时min
(可能的最低地址)似乎是免费的。
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;
}
}
最佳答案
最初有几条一般性说明(与错误无关)
从我的外观来看,很奇怪,将NtQueryVirtualMemory
与VirtualAlloc
混合使用。存在感或使用
NtQueryVirtualMemory
与NtAllocateVirtualMemory
或者
VirtualQuery
与VirtualAlloc
NtQueryVirtualMemory
与VirtualQueryEx
相比没有任何其他功能(NtAllocateVirtualMemory
通过VirtualAllocEx
参数与ZeroBits
相比没有额外的功能)然后,如果已经使用
NtQueryVirtualMemory
不需要GetProcAddress
-您可以使用与wdk中的ntdll.lib或ntdllp.lib进行静态链接-此API曾经存在,并且将从ntdll.dll中导出,就像从kernel32.dll导出的VirtualQuery
一样,您可以与kernel32链接.lib并且如果您仍然想使用
GetProcAddress
-存在常识,请不要在每次调用Allocate2GBRange
时执行此操作,而是每次执行一次。和调用的主要检查结果-可能是GetProcAddress
返回0?如果您确定GetProcAddress
永不失败-您确保NtQueryVirtualMemory
始终从ntdll.dll导出-那么请使用与ntdll [p] .lib的静态链接那么就位的
INVALID_HANDLE_VALUE
尽管正确,但看起来却不是本地的ProcessHandle
。最好在这里使用NtCurrentProcess()
宏或GetCurrentProcess()
。但无论如何,因为您使用kernel32 api-没有任何理由在这里使用NtQueryVirtualMemory
而不是VirtualQuery
您在调用之前不需要零个初始化
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
,因为region繁忙-您将在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;
}
关于c - 在2GB范围内分配内存,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/54729401/