[英]Allocating Memory Within A 2GB Range
我正在編寫一個函數,允許用戶在指定地址的2GB +/-內分配內存。 我正在查詢內存以找到一個免費頁面,並在那里分配。 這是用於x64 trampoline掛鈎 ,因為我正在使用相對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
與NtAllocateVirtualMemory
要么
VirtualAlloc
VirtualQuery
與NtQueryVirtualMemory
相比, VirtualQueryEx
沒有任何額外功能( NtAllocateVirtualMemory
通過ZeroBits
參數與VirtualAllocEx
相比具有額外功能)
然后,如果已經使用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
在調用之前你不需要零init mbi
- 這只是out參數,並且因為最初總是min < max
更好地使用do {} while (min < max)
loop而不是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.State == MEM_FREE
區域也不正確,則傳遞mbi.BaseAddress
。 為什么? 來自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;
嘗試下一個區域mbi.BaseAddress + mbi.RegionSize;
- 所以在00007FF787673000
地址。 你有mbi.State == MEM_FREE
,但是如果你嘗試用00007FF787673000
地址調用VirtualAlloc
- 它將這個地址向下舍入到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;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.