zoukankan      html  css  js  c++  java
  • 实例游戏内存修改器----CUI版本模拟

    实现说明:
      目标进程内存中很可能存在多个你要搜索的值, 所以在进行第一次搜索的时候, 要把搜索到的地址记录下来,然后让用户改变要搜索的值,再在记录的地址中搜索,直到搜索到的地址惟一为止。为此写两个辅助函数和 3 个全局变量。
      BOOL FindFirst(DWORD dwValue);      // 在目标进程空间进行第一次查找
      BOOL FindNext(DWORD dwValue);     // 在目标进程地址空间进行第2、3、4……次查找
      DWORD g_arList[1024];             // 地址列表
      int g_nListCnt;                   // 有效地址的个数
      HANDLE g_hProcess;              // 目标进程句柄
      上面这 5 行代码就组成了一个比较实用的搜索系统。比如游戏中显示的金钱值是 12345,首先将 12345 传给 FindFirst 函数进行第一次搜索,FindFirst 函数会将游戏进程内存中所有内容为 12345 的地址保存在 g_arList 全局数组中,将这样地址的个数记录在 g_nListCnt 变量中。FindFirst 函数返回以后,检查 g_nListCnt 的值,如果大于 1 就说明搜索到的地址多于 1 个。这时应该做一些事情改变游戏显示的金钱值。比如改变后金钱值变成了 13345,你要以 13345 为参数调用 FindNext 函数。这个函数会在 g_arList 数组记录的地址中进行查找,并更新g_arList 数组的记录, 将所有内容为 13345 的地址写到里面, 将这样地址的个数写到 g_nListCnt 变量中。FindNext 函数返回后,检查 g_nListCnt 的值,如果不等于 1 还继续改变金钱值,调用函数 FindNext,直到最终 g_nListCnt 的值为 1 为止。这时,g_arList[0]的值就是目标进程中保存金钱值的地址。

    程序运行说明:
    现在基本功能都有了,启动程序。
    (1)输入 199,发现找出的地址不惟一。
    (2)在 TestExe.exe 窗口敲下回车,改变后再进行一次查找,这样循环直到找到的地址惟
    一为止。
    (3)输入期待的值,修改成功!

     TestExe.cpp
    1
    // 辅助测试程序------类比于游戏程序 2 #include <stdio.h> 3 4 int g_nNum; // 全局变量测试 5 6 int main() 7 { 8 int i = 198; // 局部变量测试 9 g_nNum = 1003; 10 11 while (1) 12 { 13 printf("i = %d, address = %08lX; g_nNum = %d, address = %08lX ", ++i, &i, ++g_nNum, &g_nNum); 14 getchar(); // 按回车可以使两个变量值递增变化一,但是地址是不变的,为的是在MemRepair中正确找到该变量的唯一地址。 15 } 16 return 0; 17 }
      MemRepair.cpp
    1
    #include <windows.h> 2 #include <stdio.h> 3 4 HANDLE g_hProcess; 5 int g_nListCnt; 6 int g_arList[1024]; 7 8 /* 9 Windows 采用了分页机制来管理内存,每页的大小是 4KB(在 x86 处理器上) 。也就是说 10 Windows 是以 4KB 为单位来为应用程序分配内存的。所以可以按页来搜索目标内存,以提高 11 搜索效率。 12 */ 13 //读取一页内存 14 BOOL CompareAPage(DWORD dwBaseAddress, DWORD dwValue) 15 { 16 BYTE arBytes[4096]; 17 if (!::ReadProcessMemory(g_hProcess, (LPVOID)dwBaseAddress, arBytes, 4096, NULL)) 18 { 19 return FALSE; // 此页不可读 20 } 21 // 在这一页内存中查找 22 DWORD* pdw; 23 for (int i = 0; i < 4*1024-3; ++i) 24 { 25 pdw = (DWORD*)&arBytes[i]; 26 if (pdw[0] == dwValue) // 等于要查找的值 27 { 28 if (g_nListCnt >= 1024) 29 { 30 return FALSE; 31 } 32 // 添加到全局变量中 33 g_arList[g_nListCnt++] = dwBaseAddress + i; 34 } 35 } 36 return TRUE; 37 } 38 39 /* 40 应该在目标进程的整个用户地址空间进行搜索。在进程的整个 4GB(即2^32) 地址中,Windows 98 41 系列的操作系统为应用程序预留的是 4MB 到 2GB 部分,Windows 2000 系列的操作系统预留 42 的是 64KB 到 2GB 部分, 43 */ 44 45 // FindFirst 函数将所有符合条件的内存地址都记录到了全局数组 g_arList 中。 46 BOOL FindFirst(DWORD dwValue) 47 { 48 const DWORD dwOneGB = 1024 * 1024 * 1024; // 1GB 49 const DWORD dwOnePage = 4 * 1024; // 4KB 50 if (g_hProcess == NULL) 51 { 52 return FALSE; 53 } 54 55 // 查看操作系统类型,以决定开始地址 56 DWORD dwBase; 57 OSVERSIONINFO vi = {sizeof(vi)}; 58 ::GetVersionEx(&vi); 59 if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) 60 { 61 dwBase = 4 * 1024 * 1024; // Windows 98 系列,4MB 62 } 63 else 64 { 65 dwBase = 64 * 1024; // Windows NT 系列,64KB 66 } 67 68 // 在开始地址到2GB的地址空间进行查找 69 for (; dwBase < 2 * dwOneGB; dwBase += dwOnePage) 70 { 71 // 比较一页的内存 72 CompareAPage(dwBase, dwValue); 73 } 74 return TRUE; 75 } 76 77 BOOL FindNext(DWORD dwValue) 78 { 79 // 保存m_arList数组中有效地址的个数,初始化新的m_nListCnt值 80 int nOrgCnt = g_nListCnt; 81 g_nListCnt = 0; 82 83 // 在m_arList数组记录的地址处查找 84 BOOL bRet = FALSE; 85 DWORD dwReadValue; 86 for (int i = 0; i < nOrgCnt; ++i) 87 { 88 if (::ReadProcessMemory(g_hProcess, (LPVOID)g_arList[i], &dwReadValue, sizeof(DWORD), NULL)) 89 { 90 if (dwReadValue == dwValue) 91 { 92 g_arList[g_nListCnt++] = g_arList[i]; 93 bRet = TRUE; 94 } 95 } 96 } 97 return bRet; 98 } 99 100 // 打印出搜索到的地址 101 void ShowList() 102 { 103 for (int i = 0; i < g_nListCnt; ++i) 104 { 105 printf("%08lX ", g_arList[i]); 106 } 107 } 108 109 // 重写指定地址的值 110 BOOL WriteMemory(DWORD dwAddr, DWORD dwValue) 111 { 112 return::WriteProcessMemory(g_hProcess, (LPVOID)dwAddr, &dwValue, sizeof(DWORD), NULL); 113 } 114 115 int main() 116 { 117 // 启动TestExe.exe进程 118 char szFileName[] = "..\TestExe\Debug\TestExe.exe"; 119 STARTUPINFO si = {sizeof(si)}; 120 PROCESS_INFORMATION pi; 121 ::CreateProcess(NULL, szFileName, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); 122 123 // 关闭线程句柄,既然我们不使用 124 ::CloseHandle(pi.hThread); 125 g_hProcess = pi.hProcess; 126 127 // 输入要修改的值 128 int iVal; 129 printf("Input value = "); 130 scanf("%d", &iVal); 131 132 // 进行第一次查找 133 FindFirst(iVal); 134 135 // 打印出搜索的结果 136 ShowList(); 137 138 while(g_nListCnt > 1) 139 { 140 printf("Input val = "); 141 scanf("%d", &iVal); 142 143 // 进行下次搜索 144 FindNext(iVal); 145 // 显示搜索结果 146 ShowList(); 147 } 148 149 if (g_nListCnt == 1) // 已经锁定了要修改的地址 150 { 151 // 设置新值 152 printf("New value = "); 153 scanf("%d", &iVal); 154 // 写入新值 155 if (WriteMemory(g_arList[0], iVal)) 156 { 157 printf("Write data successfully "); 158 } 159 } 160 ::CloseHandle(g_hProcess); 161 return 0; 162 }

     运行结果:

    (1)在MemRepair.exe中输入值199查找,发现地址不唯一,有一百多个;

    (2)在TestExe中打回车,使变量变为200,在MemRepair.exe中输入200查找,发现唯一一个地址,并且与TestExe中的地址相同;

    (3)在MEMRepair.exe中输入希望修改的新值300,回车之后,在TestExe.exe中再打回车,发现变量已经变为301而不是201了。

    截图如下:

  • 相关阅读:
    余佳文 超级课程表创始人
    JavaScript 运行机制详解:再谈Event Loop
    koa2 async和await 实战详解
    node.js
    Mac版MySQL安装最新教程
    nw.js 打包与发布
    【514】keras Dense 层操作三维数据
    【513】keras 后端函数说明
    【512】keras 中的核心网络层
    【511】Keras 函数式 API
  • 原文地址:https://www.cnblogs.com/dongsheng/p/4195794.html
Copyright © 2011-2022 走看看