本文介绍了在 2GB 范围内分配内存的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个函数,允许用户在指定地址的 2GB +/- 范围内分配内存.我正在查询内存以找到一个空闲页面,并在那里分配.这是用于 x64 蹦床挂钩,因为我使用的是相对 jmp 指令.

I'm writing a function that will allow the user to allocate memory within 2GB +/- of a specified address. I'm querying the memory to find a free page, and allocating there. This is for x64 trampoline hooking, since I'm using a relative jmp instruction.

我的问题是 NtQueryVirtualMemorySTATUS_ACCESS_VIOLATION 错误而失败,因此总是返回 0.我对为什么会发生这种情况感到困惑,因为 min(可能的最低地址)在我签入 Process Explorer 时似乎是免费的.

My issue is that NtQueryVirtualMemory fails with a STATUS_ACCESS_VIOLATION error, thus always returns 0. I'm confused on why this is happening, because min(the lowest possible address) appears to be free when I check in 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;
    }
}

推荐答案

首先是几个一般性的说明(不是关于错误)

at first several general notes (not about erroes)

我看起来很奇怪将NtQueryVirtualMemoryVirtualAlloc 混合在一起.存在意义或使用

very strange from my look mix NtQueryVirtualMemory with VirtualAlloc. exist sense or use

  • NtQueryVirtualMemoryNtAllocateVirtualMemory

  • VirtualQueryVirtualAlloc

NtQueryVirtualMemoryVirtualQueryEx 相比没有任何额外的功能(NtAllocateVirtualMemoryVirtualAllocEx 相比具有额外的功能)通过 ZeroBits 参数)

the NtQueryVirtualMemory have no any extra functionality compare to VirtualQueryEx (the NtAllocateVirtualMemory have extra functionality compare to VirtualAllocEx via ZeroBits parameter)

如果已经使用 NtQueryVirtualMemory 不需要 GetProcAddress - 你可以使用 ntdll.libntdllp.lib 的静态链接> 来自 wdk - 这个 api 曾经、存在并将从 ntdll.dll 导出,就像从 kernel32.dll 导出的 VirtualQueryem> 并与 kernel32.lib 链接并且如果您无论如何想要使用 GetProcAddress - 存在意义不要每次调用 Allocate2GBRange 时都这样做,而是一次.和调用的主要检查结果 - 可能是 GetProcAddress return 0 ?如果你确定 GetProcAddress 永远不会失败——你确定 NtQueryVirtualMemory 总是从 ntdll.dll 导出——那么使用 ntdll[p].lib

then if already use NtQueryVirtualMemory not need GetProcAddress - you can use static linking with ntdll.lib or ntdllp.lib from wdk - this api was, exist and will be exported from ntdll.dll like VirtualQuery exported from kernel32.dll and you link with kernel32.liband if you anyway want use GetProcAddress - exist sense do this not every time when Allocate2GBRange is called, but once. and main check result of call - may be GetProcAddress return 0 ? if you sure that GetProcAddress never fail - you sure that NtQueryVirtualMemory always exported from ntdll.dll - so use static linking with ntdll[p].lib

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

then INVALID_HANDLE_VALUE in place ProcessHandle look very not native, despite correct. better use NtCurrentProcess() macro here or GetCurrentProcess(). but anyway, because you use kernel32 api - no any reason use NtQueryVirtualMemory instead VirtualQuery here

在调用之前不需要零 init mbi - 这只是参数,因为最初总是 min 最好使用 do {} while (min < max) 循环代替 while(min < max) {}

you not need zero init mbi before calls - this is out only parameter, and because initially always min < max better use do {} while (min < max) loop instead while(min < max) {}

现在关于代码中的严重错误:

now about critical errors in your code:

  • 使用 mbi.AllocationBase - 当 mbi.State == MEM_FREE - 在这个case 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.
  • use mbi.AllocationBase - when mbi.State == MEM_FREE - in thiscase mbi.AllocationBase == 0 - so you tell VirtualAllocallocate in any free space.
  • min += mbi.RegionSize; - the mbi.RegionSize is frommbi.BaseAddress - so add it to min incorrect - you need use min= (UINT_PTR)mbi.BaseAddress + mbi.RegionSize;
  • then in call VirtualAlloc (when lpAddress != 0) you must useMEM_COMMIT|MEM_RESERVE instead MEM_COMMIT only.

以及关于传递给 VirtualAlloc 的地址 - 传递 mbi.AllocationBase (简单地为 0)不正确.但是通过 mbi.BaseAddress 以防我们发现 mbi.State == MEM_FREE 区域也不正确.为什么 ?来自 VirtualAlloc

and about address which pass to VirtualAlloc - pass mbi.AllocationBase (simply 0) incorrect. but pass mbi.BaseAddress in case we found mbi.State == MEM_FREE region also not correct. why ? from VirtualAlloc

如果内存被保留,则指定地址四舍五入到最接近的分配粒度倍数.

这意味着 VirtualAlloc 确实尝试不是从传递的 mbi.BaseAddress 而是从 mbi.BaseAddress & 分配内存.~(dwAllocationGranularity - 1) - 更小的地址.但是这个地址可能已经很忙,结果你得到了 STATUS_CONFLICTING_ADDRESSES (ERROR_INVALID_ADDRESS win32 错误) 状态.

this mean that VirtualAlloc really try allocate memory not from passed mbi.BaseAddress but from mbi.BaseAddress & ~(dwAllocationGranularity - 1) - smaller address. but this address can be already busy, as result you got STATUS_CONFLICTING_ADDRESSES (ERROR_INVALID_ADDRESS win32 error) status.

具体的例子 - 让你有

for concrete example - let you have

[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) 繁忙的内存区域 - 最终结果 VirtualAlloc 失败,STATUS_CONFLICTING_DDRICTING_ADDRERROR_INVALID_ADDRESS).

and initially your min will be in [00007FF787650000, 00007FF787673000) busy memory region - as result you got mbi.BaseAddress == 0x00007FF787650000 and mbi.RegionSize == 0x23000, because region is busy - you will try next region at mbi.BaseAddress + mbi.RegionSize; - so at 00007FF787673000 address. you got mbi.State == MEM_FREE for it, but if you try call VirtualAlloc with 00007FF787673000 address - it rounded down this address to the 00007FF787670000 because now allocation granularity is 0x10000. but 00007FF787670000 is belong to the [00007FF787650000, 00007FF787673000) busy memory region - al result VirtualAlloc fail with STATUS_CONFLICTING_ADDRESSES (ERROR_INVALID_ADDRESS).

所以你需要使用 (BaseAddress + (AllocationGranularity-1)) &~(AllocationGranularity-1) 真的 - 地址四舍五入向上到最接近的分配粒度倍数.

so you need use (BaseAddress + (AllocationGranularity-1)) & ~(AllocationGranularity-1) really - address rounded up to the nearest multiple of the allocation granularity.

所有代码都可以看起来像:

all code can look like:

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

这篇关于在 2GB 范围内分配内存的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-14 06:13