【问题标题】:Get specific process memory space获取特定进程内存空间
【发布时间】:2023-03-06 01:07:01
【问题描述】:

我有一个指向函数的指针 (void *),我想知道这个函数属于哪个进程。我不知道该怎么做,但我认为使用某种形式的VirtualQuery 诡计是可能的。任何帮助将不胜感激。

提前致谢,

澄清:“属于进程”是指函数所在的进程。例如: 假设内存中加载了一个可执行文件 (test.exe)。这个可执行文件包含一个名为SayHello 的函数,它位于内存中的 0xDEADBEEF。在一个完全不同的过程中,我怎么知道 0xDEADBEEF 在 test.exe 的内存空间中。

希望能解决问题。

澄清 2: 我确定您熟悉“VTable 挂钩”,即外部模块在单独的进程中更改 VTable 指针以指向不同的函数。因此,每当调用钩子成员时,它都会传递给外部模块。

为了防止这种情况(反作弊),我希望能够检查 VTable 的所有方法是否都指向它们所在的模块。

解决方案代码:

template<class T>
inline void **GetVTableArray(T *pClass, int *pSize)
{
    void **ppVTable = *(void ***)pClass;

    if(pSize)
    {
        *pSize = 0;

        while(!IsBadReadPtr(ppVTable[*pSize], sizeof(UINT_PTR)))
            (*pSize)++;
    }

    return ppVTable;
}

bool AllVTableMembersPointToCurrentModule(void *pClass)
{
    DWORD dwOldProtect;
    HANDLE hModuleSnap = INVALID_HANDLE_VALUE;
    MODULEENTRY32 moduleEntry;

    // Take a snapshot of all modules in the specified process
    hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
    if(hModuleSnap == INVALID_HANDLE_VALUE)
        return false;

    // Set the size of the structure before using it
    moduleEntry.dwSize = sizeof(MODULEENTRY32);

    // Retrieve information about the first module (current process)
    if(!Module32First(hModuleSnap, &moduleEntry))
    {
        CloseHandle(hModuleSnap);
        return false;
    }

    // Grab the base address and size of our module (the address range where
    // the VTable can validly point to)
    UINT_PTR ulBaseAddress = reinterpret_cast<UINT_PTR>(moduleEntry.modBaseAddr);
    UINT_PTR ulBaseSize = moduleEntry.modBaseSize;

    // Get the VTable array and VTable member count
    int nMethods;
    void **ppVTable = GetVTableArray(pClass, &nMethods);

#ifdef VTABLE_FAKING
    // Allow patching
    VirtualProtect(ppVTable, nMethods * sizeof(UINT_PTR), PAGE_EXECUTE_READWRITE, &dwOldProtect);

    // Now take the next module and set the first VTable pointer to point to an
    // invalid address, outside of the current module's address range
    Module32Next(hModuleSnap, &moduleEntry);
    ppVTable[0] = moduleEntry.modBaseAddr;
#endif

    // Don't allow people to overwrite VTables (can easily be bypassed, so make
    // sure you check the VirtualProtect status of the VTable regularly with
    // VirtualQuery)
    VirtualProtect(ppVTable, nMethods * sizeof(UINT_PTR), PAGE_EXECUTE, &dwOldProtect);

    // Clean up the snapshot object
    CloseHandle(hModuleSnap);

    // Ensure all VTable pointers are in our current module's address range
    for(int i = 0; i < nMethods; ++i)
    {
        // Get address of the method this VTable pointer points to
        UINT_PTR ulFuncAddress = reinterpret_cast<UINT_PTR>(ppVTable[i]);

        // Check the address is within our current module range
        if(ulFuncAddress < ulBaseAddress || ulFuncAddress > ulBaseAddress + ulBaseSize)
            return false;
    }

    return true;
}

【问题讨论】:

  • 我不确定我是否理解这个问题 - 函数在什么意义上属于进程?
  • 你是如何获得那个 void* 的?
  • @Carl Norum:已编辑以清除问题。 @nos: VTable 指针

标签: c++ windows winapi


【解决方案1】:

每个进程都有自己的地址空间。这意味着相同的地址将包含不同进程的不同内容,因此无法按照您的要求进行操作。

如果这个指针指向当前程序中的一个函数(即你当前可以调用的函数),那么答案很简单:它属于当前进程。

进一步澄清:除非您已经知道它属于哪个进程,否则指针本身是没有意义的。进程#1001 可能在地址 0x12345678 处具有函数sayHello,而进程#1002 在地址 0x12345678 处具有函数sayGoodbye,并且进程#1003 在同一地址包含一些数据。无法知道指针来自哪个进程。

【讨论】:

  • 我不知道我是否正确理解了您的说明,但我认为指针是绝对的,而不是相对于进程分配基础。
  • @Saul:指针不是相对的。问题是它们不直接映射到物理内存,而是映射到每个进程唯一的虚拟地址空间。见en.wikipedia.org/wiki/Virtual_address_space
  • 感谢 interjay 的澄清,我已经澄清了我正在尝试做的事情。
【解决方案2】:

VTable 中被劫持的函数指针只能在您的进程中,正如其他人已经回答的那样。内存地址仅对您的进程有意义。如果有人要覆盖您的 VTable 点之一,那么他们首先必须挂钩您的进程,这意味着在您的进程中运行代码。存在大量提供 hooking 的 win API。

请参阅EnumProcessModule 以了解您流程中的所有模块。请参阅this about modules info,包括模块的基地址。然后你必须检查你的 VTables 以确保那些被寻址的存在于你的模块的地址范围内。

首先要防止 VTable 劫持?我不知道该怎么做,除了尝试Microsoft's Detours library,理论上它可以用来绕过你进程中的任何钩子API调用。

【讨论】:

  • 谢谢,这正是我想要的。
【解决方案3】:

如果您有模块句柄,您可以检查映像头以确保 vtable 指针位于该模块的虚拟地址空间中。

【讨论】:

    【解决方案4】:

    在从 Windows NT 衍生而来的任何 Windows 操作系统中(因此,出于所有意图和目的,包括 XP 和之后,以及在 NT 4 和 NT 3.51 之前),每个进程都有自己的地址空间。在合理的范围内,任何指针地址在系统中的每个进程中都可能不同,因为它们都有一个0xDEADBEEF 地址,并且它可能包含也可能不包含与其他进程相同的东西。这与 Windows 3.0、3.1、95、98 和 ME(它们有一个所有进程共享的地址空间)不同,您的问题可能更有意义。

    因此,如果没有处理指针地址的进程句柄,该地址对您来说几乎毫无用处。使用进程句柄,您可以(可能)通过遍历您导入的 DLL 的导入表来计算出您想要的内容...如果该函数不是导入的函数,那么您不太可能计算出您想要的内容知道。

    请注意,如果地址是来自“标准”系统 DLL 的函数,那么您可以通过找出它在进程地址空间中代表的函数来确定它的位置,因为存在DLL 很可能会在您的进程中映射到与其他所有进程中相同的基地址。

    为什么不告诉我们更多关于你真正想要做什么的事情呢?

    编辑:

    好吧,正如我上面所描述的,您的建议是不可能的,除非在非常旧的 Windows 版本上。可能的是,您可以将代码注入进程以替换应该执行的代码。此注入代码的地址在目标进程的地址空间中有效,并且包含您(黑客进程)创建的代码。您可以通过在远程进程中使用VirtualAllocEx() (1) 分配内存,然后使用WriteProcessMemory() (2) 将代码写入其中来完成此操作。您现在拥有在目标进程中编写的代码。然后,您可以对其进行修补,以便调用它而不是应该调用的代码。

    执行此操作的常用方法是IAT hooking(导入地址表挂钩),这使您可以替换从 DLL 中导入的函数。要检测这一点,您需要从磁盘上的 DLL 映像中扫描 DLL 的导入地址表,确定函数在内存中的位置,然后扫描内存中的 IAT 以检查函数是否在它们应该在的位置;如果不是,那么它们可能已被修补。

    您暗示有人正在替换任意 C++ vTable 条目。使用相同的技术可以做到这一点,但更难,因为没有方便的名称到地址表,您可以使用这些表来确定修补的位置。无论如何,假设坏人可以找到正确的地址来修补他可以使用与上述相同的技术在您的进程中创建自己的函数。

    由于缺少名称到地址查找,检测 vTable 问题变得更加复杂,但如果您处于被黑客入侵的过程中,您只需编写代码,在启动时获取相关函数的地址。将其存储在某个地方并稍后进行比较。但是,您可能最好在内存中复制整个函数本身并与之进行比较,因为您可能会发现坏人只是寻找一些可识别的函数签名字节并将跳转到他们自己的代码的某个地方,或者只是跳过你的。

    祝你好运,给自己找一本好书,比如 Jeffrey Richter 的一本书,它会比我更好地解释其中的大部分内容。

    【讨论】:

    • 感谢您的信息,我已经澄清了我正在尝试做的事情。
    【解决方案5】:

    我不太明白你的问题,所以我将在黑暗中试一试,回答我认为你在问的问题。

    您在问如何从函数指针中找出它属于哪个模块。 解决方案理论上比较简单,在内存中向后扫描找到header,然后享受使用GetModuleFileName这个函数。

    由于您的问题措辞不当,因此您不会得到措辞得当的答案。

    【讨论】:

      猜你喜欢
      • 2020-05-21
      • 2011-09-08
      • 2016-12-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-05
      • 1970-01-01
      相关资源
      最近更新 更多