问题描述
我正在编写一个函数,允许用户在指定地址的 2GB +/- 范围内分配内存.我正在查询内存以找到一个空闲页面,并在那里分配.这是用于 x64 蹦床挂钩,因为我使用的是相对 jmp 指令.
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.
我的问题是 NtQueryVirtualMemory
因 STATUS_ACCESS_VIOLATION
错误而失败,因此总是返回 0.我对为什么会发生这种情况感到困惑,因为 min代码>(可能的最低地址)在我签入 Process Explorer 时似乎是免费的.
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;
}
}
推荐答案
首先是几个一般性的说明(不是关于错误)
at first several general notes (not about erroes)
我看起来很奇怪将NtQueryVirtualMemory
与VirtualAlloc
混合在一起.存在意义或使用
very strange from my look mix NtQueryVirtualMemory
with VirtualAlloc
. exist sense or use
NtQueryVirtualMemory
和NtAllocateVirtualMemory
或
VirtualQuery
和VirtualAlloc
NtQueryVirtualMemory
与 VirtualQueryEx
相比没有任何额外的功能(NtAllocateVirtualMemory
与 VirtualAllocEx
相比具有额外的功能)通过 ZeroBits
参数)
the NtQueryVirtualMemory
have no any extra functionality compare to VirtualQueryEx
(the NtAllocateVirtualMemory
have extra functionality compare to VirtualAllocEx
via ZeroBits
parameter)
如果已经使用 NtQueryVirtualMemory
不需要 GetProcAddress
- 你可以使用 ntdll.lib 或 ntdllp.lib 的静态链接> 来自 wdk - 这个 api 曾经、存在并将从 ntdll.dll 导出,就像从 kernel32.dll 导出的 VirtualQuery
em> 并与 kernel32.lib 链接并且如果您无论如何想要使用 GetProcAddress
- 存在意义不要每次调用 Allocate2GBRange
时都这样做,而是一次.和调用的主要检查结果 - 可能是 GetProcAddress
return 0 ?如果你确定 GetProcAddress
永远不会失败——你确定 NtQueryVirtualMemory
总是从 ntdll.dll 导出——那么使用 ntdll[p].lib
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.liband 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
然后 INVALID_HANDLE_VALUE
就位 ProcessHandle
看起来很不原生,尽管是正确的.最好在此处使用 NtCurrentProcess()
宏或 GetCurrentProcess()
.但无论如何,因为您使用 kernel32 api - 没有任何理由使用 NtQueryVirtualMemory
代替 VirtualQuery
此处
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
在调用之前不需要零 init mbi
- 这只是参数,因为最初总是 min 最好使用
do {} while (min < max)
循环代替 while(min < max) {}
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
- 当mbi.State == MEM_FREE
- 在这个casembi.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
.
- use
mbi.AllocationBase
- whenmbi.State == MEM_FREE
- in thiscasembi.AllocationBase == 0
- so you tellVirtualAlloc
allocate in any free space. min += mbi.RegionSize;
- thembi.RegionSize
is frommbi.BaseAddress
- so add it tomin
incorrect - you need usemin= (UINT_PTR)mbi.BaseAddress + mbi.RegionSize;
- then in call
VirtualAlloc
(when lpAddress != 0) you must useMEM_COMMIT|MEM_RESERVE
insteadMEM_COMMIT
only.
以及关于传递给 VirtualAlloc
的地址 - 传递 mbi.AllocationBase
(简单地为 0)不正确.但是通过 mbi.BaseAddress
以防我们发现 mbi.State == MEM_FREE
区域也不正确.为什么 ?来自 VirtualAlloc
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
如果内存被保留,则指定地址四舍五入到最接近的分配粒度倍数.
这意味着 VirtualAlloc
确实尝试不是从传递的 mbi.BaseAddress
而是从 mbi.BaseAddress & 分配内存.~(dwAllocationGranularity - 1)
- 更小的地址.但是这个地址可能已经很忙,结果你得到了 STATUS_CONFLICTING_ADDRESSES
(ERROR_INVALID_ADDRESS
win32 错误) 状态.
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
并且最初你的 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)
繁忙的内存区域 - 最终结果 VirtualAlloc
失败,STATUS_CONFLICTING_DDRICTING_ADDR
ERROR_INVALID_ADDRESS
).
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
).
所以你需要使用 (BaseAddress + (AllocationGranularity-1)) &~(AllocationGranularity-1)
真的 - 地址四舍五入向上到最接近的分配粒度倍数.
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;
}
这篇关于在 2GB 范围内分配内存的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!