我正在编写一个函数,该功能允许用户在指定地址的2GB /-内分配内存。我正在查询内存以找到免费页面,并在那里分配。这是针对X64蹦床钩的,因为我正在使用相对JMP指令。
我的问题是NtQueryVirtualMemory
失败了STATUS_ACCESS_VIOLATION
错误,因此总是返回0。我对为什么会发生这种情况感到困惑,因为当我检查Process Explorer时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
withNtAllocateVirtualMemory
或
-
VirtualQuery
withVirtualAlloc
NtQueryVirtualMemory
与VirtualQueryEx
相比没有任何额外功能(NtAllocateVirtualMemory
具有额外功能与VirtualAllocEx
通过ZeroBits
参数相比)
然后,如果已经使用了NtQueryVirtualMemory
不需要GetProcAddress
-您可以使用 ntdll.lib 或 ntdllp.lib 从 wdk - 这API已存在并且将从 ntdll.dll.dll 中导出,例如 VirtualQuery
kernel32.dll ,您与 kernel32.lib 链接而且,如果您无论如何都想使用GetProcAddress
-存在的感觉,并非每次都称为Allocate2GBRange
,而是一次。呼叫的主要检查结果 - 可能是GetProcAddress
返回0?如果确定GetProcAddress
永远不会失败 - 您确定NtQueryVirtualMemory
总是从 ntdll.dll.dll - 因此,请使用静态链接使用 ntdll [p] .lib
然后INVALID_HANDLE_VALUE
在适当的位置ProcessHandle
尽管正确,但看起来并不是本地的。更好地在此处或GetCurrentProcess()
使用NtCurrentProcess()
宏。但是无论如何,因为您使用kernel32 api-没有任何理由使用 NtQueryVirtualMemory
而不是VirtualQuery
您不需要零init init 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
,因为区域很忙 - 您将在mbi.BaseAddress + mbi.RegionSize;
上尝试下一个区域 - 因此,在00007FF787673000
地址。您可以使用mbi.State == MEM_FREE
,但是如果尝试使用00007FF787673000
地址调用VirtualAlloc
,则将此地址舍入00007FF787670000
,因为现在分配粒度为0x10000
。但是00007FF787670000
属于[00007FF787650000, 00007FF787673000)
繁忙的内存区域-AL结果VirtualAlloc
STATUS_CONFLICTING_ADDRESSES
(ERROR_INVALID_ADDRESS
)失败。
因此,您需要真正使用(BaseAddress + (AllocationGranularity-1)) & ~(AllocationGranularity-1)
- 地址圆形 up 到分配粒度的最接近的倍数。
所有代码看起来都可以:
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;
}