在2GB范围内分配内存

时间:2019-02-17 01:46:03

标签: c windows winapi

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

我的问题是NtQueryVirtualMemory失败,并出现一个STATUS_ACCESS_VIOLATION错误,因此总是返回0。我对为什么会这样感到困惑,因为出现了min(可能的最低地址)在签入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;
    }
}

1 个答案:

答案 0 :(得分:1)

最初有几条一般性说明(与错误无关)

从我的外观来看,NtQueryVirtualMemoryVirtualAlloc混在一起很奇怪。存在意义或使用

  • NtQueryVirtualMemoryNtAllocateVirtualMemory

  • VirtualQueryVirtualAlloc

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

然后如果已经使用NtQueryVirtualMemory则不需要GetProcAddress-您可以将静态链接与 ntdll.lib ntdllp.lib > wdk -此api已经存在,并且将从 ntdll.dll 导出,就像从 kernel32.dll 导出的VirtualQuery一样,您可以通过< em> kernel32.lib 并且如果您仍然想使用GetProcAddress-存在的常识,则不是每次调用Allocate2GBRange时都这样做,而是一次。并且呼叫的主要检查结果-可能为GetProcAddress返回0?如果您确定GetProcAddress永不失败-您确保NtQueryVirtualMemory始终从 ntdll.dll 导出,那么请对 ntdll [p] .lib使用静态链接

然后将INVALID_HANDLE_VALUE放在ProcessHandle的位置,尽管正确,但看起来却不是原生的。最好在此处或使用NtCurrentProcess()使用GetCurrentProcess()宏。但无论如何,因为您使用kernel32 api-此处没有任何理由使用NtQueryVirtualMemory代替VirtualQuery

在调用之前您不需要零初始化mbi-这只是out参数,并且因为一开始总是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_ADDRESSESERROR_INVALID_ADDRESS win32错误)。

作为具体示例-让您拥有

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

,最初,您的min将位于[00007FF787650000, 00007FF787673000)繁忙的内存区域中-结果您获得了mbi.BaseAddress == 0x00007FF787650000mbi.RegionSize == 0x23000,因为该区域处于繁忙状态-您将在mbi.BaseAddress + mbi.RegionSize;-在00007FF787673000地址。您得到了mbi.State == MEM_FREE,但是如果您尝试使用VirtualAlloc地址呼叫00007FF787673000-它会将这个地址四舍五入到00007FF787670000,因为现在分配粒度为0x10000 。但00007FF787670000属于[00007FF787650000, 00007FF787673000)繁忙的内存区域-结果VirtualAlloc失败,并出现STATUS_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;
}