我正在编写一个函数,该函数将允许用户在指定地址的2GB +/-内分配内存。我正在查询内存以找到一个空闲页,然后在此处进行分配。这是针对x64 trampoline hooking的,因为我使用的是相对的jmp指令。

我的问题是NtQueryVirtualMemory失败,并出现STATUS_ACCESS_VIOLATION错误,因此始终返回0。我对此感到困惑,因为当我在Process Explorer中 checkin 时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

  • 或者
  • VirtualQueryVirtualAlloc
  • NtQueryVirtualMemoryVirtualQueryEx相比没有任何其他功能(NtAllocateVirtualMemory通过VirtualAllocEx参数与ZeroBits相比没有额外的功能)

    然后,如果已经使用 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
    您在调用之前不需要零个初始化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 == 0x00007FF787650000mbi.RegionSize == 0x23000,因为region繁忙-您将在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;
    }
    

    关于c - 在2GB范围内分配内存,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/54729401/

    10-11 19:40