我正在编写一个函数,该函数将允许用户在指定地址的2GB +/-内分配内存。我正在查询内存以找到一个空闲页,然后在此处进行分配。这是针对x64 trampoline hooking的,因为我使用的是相对的jmp指令。
我的问题是NtQueryVirtualMemory
失败,并出现一个STATUS_ACCESS_VIOLATION
错误,因此总是返回0。我对为什么会这样感到困惑,因为出现了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;
}
}
答案 0 :(得分:1)
最初有几条一般性说明(与错误无关)
从我的外观来看,NtQueryVirtualMemory
与VirtualAlloc
混在一起很奇怪。存在意义或使用
NtQueryVirtualMemory
与NtAllocateVirtualMemory
或
VirtualQuery
与VirtualAlloc
NtQueryVirtualMemory
与VirtualQueryEx
相比没有任何其他功能(NtAllocateVirtualMemory
通过VirtualAllocEx
参数与ZeroBits
相比没有额外的功能)
然后如果已经使用NtQueryVirtualMemory
则不需要GetProcAddress
-您可以将静态链接与 ntdll.lib 或 ntdllp.lib > wdk -此api已经存在,并且将从 ntdll.dll 导出,就像从 kernel32.dll 导出的VirtualQuery
一样,您可以通过< em> kernel32.lib
并且如果您仍然想使用GetProcAddress
-存在的常识,则不是每次调用Allocate2GBRange
时都这样做,而是一次。并且呼叫的主要检查结果-可能为GetProcAddress
返回0?如果您确定GetProcAddress
永不失败-您确保NtQueryVirtualMemory
始终从 ntdll.dll 导出,那么请对 ntdll [p] .lib使用静态链接
然后将INVALID_HANDLE_VALUE
放在ProcessHandle
的位置,尽管正确,但看起来却不是原生的。最好在此处或使用NtCurrentProcess()
使用GetCurrentProcess()
宏。但无论如何,因为您使用kernel32 api-此处没有任何理由使用NtQueryVirtualMemory
代替VirtualQuery
在调用之前您不需要零初始化mbi
-这只是out参数,并且因为一开始总是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;
}