C++ 仅读取进程范围内的内存地址

Posted

技术标签:

【中文标题】C++ 仅读取进程范围内的内存地址【英文标题】:C++ read memory addresses within process range only 【发布时间】:2020-04-24 23:37:20 【问题描述】:

所以我一直在尝试使用 C++ 进行内存读取,我认为一个很酷的项目是读取进程正在使用的所有地址(类似于 Cheat Engine 的工作方式)。

我从阅读开始

链接1: Read Memory of Process C++

链接2: Read memory of 64bit process address

Link3: http://www.cplusplus.com/forum/general/42132/

我还在 youtube 上观看了一个教程,他解释了一个进程(游戏)如何处理地址。 YouTube 视频链接: https://www.youtube.com/watch?v=wiX5LmdD5yk

这导致我做了三种不同的方法:

DWORD GetProcId(const wchar_t* procName) 
    DWORD pid = 0;
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if(hSnap != INVALID_HANDLE_VALUE) 
        PROCESSENTRY32 procEntry;
        procEntry.dwSize = sizeof(procEntry);
        if(Process32First(hSnap, &procEntry)) 
            do 
                if(!_wcsicmp(procEntry.szExeFile, procName)) 
                    pid = procEntry.th32ProcessID;
                    break;
                
             while (Process32Next(hSnap, &procEntry));
        
    
    CloseHandle(hSnap);
    return pid;

此方法是获取进程 ID,我也可以通过在任务管理器中找到相同的 PID(稍后给我相同的基地址)手动输入。

uintptr_t GetModuleBaseAddress(DWORD procId, const wchar_t* modName) 
    uintptr_t modBaseAddr = 0;
//I use 0x10 instead of TH32CS_SNAPMODULE32 since it didnt work and according to documentation 
// this is the value it should have.
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | 0x10, procId); 
    if(hSnap != INVALID_HANDLE_VALUE) 
        MODULEENTRY32 modEntry;
        modEntry.dwSize = sizeof(modEntry);
        if(Module32First(hSnap, &modEntry)) 
            do 
                if(!_wcsicmp(modEntry.szModule, modName)) 
                    modBaseAddr = (uintptr_t)modEntry.modBaseAddr;
                    break;
                
             while(Module32Next(hSnap, &modEntry));
        
    
    CloseHandle(hSnap);
    return modBaseAddr;

这个方法(我假设)将返回进程的基地址。例如,在我的代码中,我试图找到 discord.exe 进程的基地址。当 discord.exe 没有运行时,我得到了 0,而当它运行时,我得到了一个地址(我相信这是正确的基地址,如果我错了,请纠正我)。

还有我的main 方法:

int main() 
    DWORD procId = GetProcId(L"Discord.exe");
    uintptr_t moduleBase = GetModuleBaseAddress(procId, L"Discord.exe");

    HANDLE hProcess = 0;
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, NULL, procId);

    uintptr_t dynamicPtrBaseAddr = moduleBase;

    std::cout << "Dynamic: " << dynamicPtrBaseAddr << std::endl;

    int value = 0;
    int arr [10000] = ;
    for (int i = 0; i < 100000; i++) 
        ReadProcessMemory(hProcess, (BYTE*)dynamicPtrBaseAddr, &value, sizeof(value),0);
        dynamicPtrBaseAddr += 1;
        arr[i] = value;
    

我尝试将所有 100000 个地址的所有值放入一个数组中。

所以我的问题是:

我是否正确检索了进程的基地址? 为了读取其他地址,我只需将 dynamicPtrBaseAddr 增加 1,是否有更好的方法来实现偏移?或者这是正确的方法? 现在我将基地址增加100000。我可以找到进程的最后一个地址吗?

我使用g++ main.cpp -o test -lpsapi -DUNICODE (MinGW) 编译。

【问题讨论】:

非常好的问题,特别是考虑到大多数读取过程记忆问题的代码和假设让我想把我的大脑挖出来。即使是实际提出的三个问题也足够相关,可以在一个答案中理解。 @user4581301 我试图包含尽可能多的信息,因为我理解你的意思,如果我假设太多,社区很难提供帮助。您认为我可以提供更多信息吗? 您没有在main() 中进行任何错误处理,请始终检查错误。此外,向OpenProcess() 索取PROCESS_ALL_ACCESS 的权利只是要求太多,ReadProcessMemory() 只需要PROCESS_VM_READ 的权利。不要要求比你实际需要更多的权利。此外,进程内存的组织是复杂的。从moduleBase 开始读取10000 个ints 是相当没用的。此外,您以 1 字节为增量递增 dynamicPtrBaseAddr,但以 4 字节为增量读取。最后,MODULEENTRY32 为您提供每个模块的大小,将该大小添加到基地址 "我应该在每个增量中添加模块的大小吗?" - 不。我的意思是将模块的大小添加到其基地址以了解模块的最后一个地址是什么,即您无法读取过去的地址。在您的循环中,如果您要读取整个ints,那么您应该将dynamicPtrBaseAddr 增加整个ints,而不是单个字节。 您的应用程序的位数需要与我认为的目标位数相匹配。 【参考方案1】:

您必须以管理员身份运行,并且必须编译为与目标进程相同的位数。

您不应该一次读取 1 个字节,尤其是在外部 hack 中。

您应该使用 VirtualQueryEx() 正确地循环仅通过正确的内存区域:

    DWORD procid = GetProcId("ac_client.exe");

    unsigned char* addr = 0;

    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procid);

    MEMORY_BASIC_INFORMATION mbi;

    while (VirtualQueryEx(hProc, addr, &mbi, sizeof(mbi)))
    
        if (mbi.State == MEM_COMMIT && mbi.Protect != PAGE_NOACCESS && mbi.Protect != PAGE_GUARD)
        
            std::cout << "base : 0x" << std::hex << mbi.BaseAddress << " end : 0x" << std::hex << (uintptr_t)mbi.BaseAddress + mbi.RegionSize << "\n";
        
        addr += mbi.RegionSize;
    

这可确保您只读取正确的内存。接下来,您将一次性将每个区域到达本地缓冲区,这将大大提高速度,因为您不会有为每个字节调用 API 的开销。

看起来你正在尝试做的是模式扫描。您可以在找到您所遵循的原始教程的同一位置找到我们的模式扫描教程。

【讨论】:

我不明白的是,在我的应用程序中我分配了一个变量number 值 500。然后我打印了number 的地址,它似乎存储在6421852 而你的方法给了我140728537382912140737488289792 之间的地址。那么我如何才能读取number 的值呢? @darclader 你似乎缺乏对你正在做什么的基本知识,所以我建议你在尝试做这样复杂的事情之前回到基础知识。从长远来看,你会做得更好,建立一个知识基础,你可以用它来超越下一步。我使用 char* 来表示地址,而您使用的是整数类型,这会导致您出现此问题。 我尝试将它们从十六进制转换为十进制,以便我更清楚地看到差异。您是指内存寻址的基础知识吗?因为我有一个类似的问题,我能够读取一些内存但无法找到特定值。他们建议我研究阅读堆/堆栈,这就是我问的原因。 @GuidedHacking

以上是关于C++ 仅读取进程范围内的内存地址的主要内容,如果未能解决你的问题,请参考以下文章

线程安全问题分析

c++如何找到进程中的输出表内存地址(OD的bp)

C++或者C#如何读取指定内存地址的值?

使用变量地址读取另一个进程的内存

内存对齐原理

linux进程内存相关