C-在2GB范围内分配内存



我正在编写一个函数,该功能允许用户在指定地址的2GB /-内分配内存。我正在查询内存以找到免费页面,并在那里分配。这是针对X64蹦床钩的,因为我正在使用相对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混合。存在感或使用

  • NtQueryVirtualMemory with NtAllocateVirtualMemory

  • VirtualQuery with VirtualAlloc

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

然后,如果已经使用了NtQueryVirtualMemory不需要GetProcAddress-您可以使用 ntdll.lib ntdllp.lib wdk - 这API已存在并且将从 ntdll.dll.dll 中导出,例如 VirtualQuery kernel32.dll ,您与 kernel32.lib 链接而且,如果您无论如何都想使用GetProcAddress-存在的感觉,并非每次都称为Allocate2GBRange,而是一次。呼叫的主要检查结果 - 可能是GetProcAddress返回0?如果确定GetProcAddress永远不会失败 - 您确定NtQueryVirtualMemory总是从 ntdll.dll.dll - 因此,请使用静态链接使用 ntdll [p] .lib

然后INVALID_HANDLE_VALUE在适当的位置ProcessHandle尽管正确,但看起来并不是本地的。更好地在此处或GetCurrentProcess()使用NtCurrentProcess()宏。但是无论如何,因为您使用kernel32 api-没有任何理由使用 NtQueryVirtualMemory而不是VirtualQuery

您不需要零init init 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_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,但是如果尝试使用00007FF787673000地址调用VirtualAlloc,则将此地址舍入00007FF787670000,因为现在分配粒度为0x10000。但是00007FF787670000属于[00007FF787650000, 00007FF787673000)繁忙的内存区域-AL结果VirtualAlloc STATUS_CONFLICTING_ADDRESSESERROR_INVALID_ADDRESS)失败。

因此,您需要真正使用(BaseAddress + (AllocationGranularity-1)) & ~(AllocationGranularity-1) - 地址圆形 up 到分配粒度的最接近的倍数。

所有代码看起来都可以:

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;
}

最新更新