簡體   English   中英

在2GB范圍內分配內存

[英]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;
    }
}

最初的幾個一般性說明(不是關於錯誤)

從我的外觀看, NtQueryVirtualMemoryVirtualAlloc非常奇怪。 存在意義或使用

  • NtQueryVirtualMemoryNtAllocateVirtualMemory

要么

  • VirtualAlloc VirtualQuery

NtQueryVirtualMemory相比, VirtualQueryEx沒有任何額外功能( NtAllocateVirtualMemory通過ZeroBits參數與VirtualAllocEx相比具有額外功能)

然后,如果已經使用NtQueryVirtualMemory不需要GetProcAddress - 您可以使用來自wdk的 ntdll.libntdllp.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_ADDRESSESERROR_INVALID_ADDRESS win32錯誤)狀態。

具體的例子 - 讓你有

[00007FF787650000, 00007FF787673000) busy memory
[00007FF787673000, ****************) free memory

並且最初你的min將在[00007FF787650000, 00007FF787673000)繁忙的內存區域 - 結果你得到了mbi.BaseAddress == 0x00007FF787650000mbi.RegionSize == 0x23000 ,因為區域很忙 - 你將在mbi.BaseAddress + mbi.RegionSize;嘗試下一個區域mbi.BaseAddress + mbi.RegionSize; - 所以在00007FF787673000地址。 你有mbi.State == MEM_FREE ,但是如果你嘗試用00007FF787673000地址調用VirtualAlloc - 它將這個地址向下舍入到00007FF787670000因為現在分配粒度是0x10000 但是00007FF787670000屬於[00007FF787650000, 00007FF787673000)繁忙的內存區域 - 結果VirtualAllocSTATUS_CONFLICTING_ADDRESSESERROR_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM