实例游戏内存修改器----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 // 辅助测试程序------类比于游戏程序
#include <stdio.h> int g_nNum; // 全局变量测试 int main()
{
int i = ; // 局部变量测试
g_nNum = ; while ()
{
printf("i = %d, address = %08lX; g_nNum = %d, address = %08lX\n", ++i, &i, ++g_nNum, &g_nNum);
getchar(); // 按回车可以使两个变量值递增变化一,但是地址是不变的,为的是在MemRepair中正确找到该变量的唯一地址。
}
return ;
}
MemRepair.cpp
1 #include <windows.h>
#include <stdio.h> HANDLE g_hProcess;
int g_nListCnt;
int g_arList[]; /*
Windows 采用了分页机制来管理内存,每页的大小是 4KB(在 x86 处理器上) 。也就是说
Windows 是以 4KB 为单位来为应用程序分配内存的。所以可以按页来搜索目标内存,以提高
搜索效率。
*/
//读取一页内存
BOOL CompareAPage(DWORD dwBaseAddress, DWORD dwValue)
{
BYTE arBytes[];
if (!::ReadProcessMemory(g_hProcess, (LPVOID)dwBaseAddress, arBytes, , NULL))
{
return FALSE; // 此页不可读
}
// 在这一页内存中查找
DWORD* pdw;
for (int i = ; i < *-; ++i)
{
pdw = (DWORD*)&arBytes[i];
if (pdw[] == dwValue) // 等于要查找的值
{
if (g_nListCnt >= )
{
return FALSE;
}
// 添加到全局变量中
g_arList[g_nListCnt++] = dwBaseAddress + i;
}
}
return TRUE;
} /*
应该在目标进程的整个用户地址空间进行搜索。在进程的整个 4GB(即2^32) 地址中,Windows 98
系列的操作系统为应用程序预留的是 4MB 到 2GB 部分,Windows 2000 系列的操作系统预留
的是 64KB 到 2GB 部分,
*/ // FindFirst 函数将所有符合条件的内存地址都记录到了全局数组 g_arList 中。
BOOL FindFirst(DWORD dwValue)
{
const DWORD dwOneGB = * * ; // 1GB
const DWORD dwOnePage = * ; // 4KB
if (g_hProcess == NULL)
{
return FALSE;
} // 查看操作系统类型,以决定开始地址
DWORD dwBase;
OSVERSIONINFO vi = {sizeof(vi)};
::GetVersionEx(&vi);
if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
{
dwBase = * * ; // Windows 98 系列,4MB
}
else
{
dwBase = * ; // Windows NT 系列,64KB
} // 在开始地址到2GB的地址空间进行查找
for (; dwBase < * dwOneGB; dwBase += dwOnePage)
{
// 比较一页的内存
CompareAPage(dwBase, dwValue);
}
return TRUE;
} BOOL FindNext(DWORD dwValue)
{
// 保存m_arList数组中有效地址的个数,初始化新的m_nListCnt值
int nOrgCnt = g_nListCnt;
g_nListCnt = ; // 在m_arList数组记录的地址处查找
BOOL bRet = FALSE;
DWORD dwReadValue;
for (int i = ; i < nOrgCnt; ++i)
{
if (::ReadProcessMemory(g_hProcess, (LPVOID)g_arList[i], &dwReadValue, sizeof(DWORD), NULL))
{
if (dwReadValue == dwValue)
{
g_arList[g_nListCnt++] = g_arList[i];
bRet = TRUE;
}
}
}
return bRet;
} // 打印出搜索到的地址
void ShowList()
{
for (int i = ; i < g_nListCnt; ++i)
{
printf("%08lX\n", g_arList[i]);
}
} // 重写指定地址的值
BOOL WriteMemory(DWORD dwAddr, DWORD dwValue)
{
return::WriteProcessMemory(g_hProcess, (LPVOID)dwAddr, &dwValue, sizeof(DWORD), NULL);
} int main()
{
// 启动TestExe.exe进程
char szFileName[] = "..\\TestExe\\Debug\\TestExe.exe";
STARTUPINFO si = {sizeof(si)};
PROCESS_INFORMATION pi;
::CreateProcess(NULL, szFileName, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); // 关闭线程句柄,既然我们不使用
::CloseHandle(pi.hThread);
g_hProcess = pi.hProcess; // 输入要修改的值
int iVal;
printf("Input value = ");
scanf("%d", &iVal); // 进行第一次查找
FindFirst(iVal); // 打印出搜索的结果
ShowList(); while(g_nListCnt > )
{
printf("Input val = ");
scanf("%d", &iVal); // 进行下次搜索
FindNext(iVal);
// 显示搜索结果
ShowList();
} if (g_nListCnt == ) // 已经锁定了要修改的地址
{
// 设置新值
printf("New value = ");
scanf("%d", &iVal);
// 写入新值
if (WriteMemory(g_arList[], iVal))
{
printf("Write data successfully\n");
}
}
::CloseHandle(g_hProcess);
return ;
}
运行结果:
(1)在MemRepair.exe中输入值199查找,发现地址不唯一,有一百多个;
(2)在TestExe中打回车,使变量变为200,在MemRepair.exe中输入200查找,发现唯一一个地址,并且与TestExe中的地址相同;
(3)在MEMRepair.exe中输入希望修改的新值300,回车之后,在TestExe.exe中再打回车,发现变量已经变为301而不是201了。
截图如下:
实例游戏内存修改器----CUI版本模拟的更多相关文章
- 只需要一点点C++基础,新手也可以制作单机游戏内存修改器
声明:本文只是为了初学C++的,能够做出一些实用的东西,跳出管理系统的束缚,提升学习的兴趣,在这里选取了单机游戏,请不要尝试在线游戏,违发而已未必可行.序:首先我们需要一个Qt+VS环境Qt从http ...
- ce游戏内存修改器(Cheat Engine)
ce修改器(Cheat Engine)一款专门修改内存修改编辑的游戏工具它包括16进制编辑,反汇编程序,内存查找工具新版6.1 版的CE与6.0 最大的区别就是添加了修改器制作工具,比之前 5.6.1 ...
- UWP游戏防内存修改器的方法
最近我一直在编写适用于Windows 10商店的游戏.这款游戏比较怕玩家用修改器改金钱,因为这种修改会导致某些内购失效并且损害公平性.于是我把自己见过的三种反修改器的方法给网友们介绍一下. 首先说明一 ...
- Android For JNI(二)——C语言中的数据类型,输出,输入函数以及操作内存地址,内存修改器
Android For JNI(二)--C语言中的数据类型,输出,输入函数以及操作内存地址,内存修改器 当我们把Hello World写完之后,我们就可以迈入C的大门了,今天就来讲讲基本的一些数据类型 ...
- C++内存修改器开源代码
我们玩单机游戏时,游戏难度可能过大, 或者游戏已经比较熟练,想要增加游戏的玩法,这时候可以使用修改器. 内存式游戏修改器主要对游戏内存修改 修改时有两种方式,一是定时对内存数值进行修改.实现类似锁定的 ...
- 防止apk反编译的技术分析浅谈--内存修改器篇
声明: 1.本帖转载自http://jingyan.baidu.com/article/a24b33cd509eb719fe002b94.html,仅供自用,勿喷 Apk反编译修改器有很多.拿其中的比 ...
- Steam游戏《Nine Parchments(九张羊皮纸)》修改器制作-[先使用CE写,之后有时间的话改用C#](2020年寒假小目标02)
日期:2020.01.09 博客期:122 星期四 [温馨提示]: 只是想要修改器的网友,可以直接点击此链接下载: 只是想要部分CT文件的网友,可以直接点击此链接下载: 没有博客园账号的网友,可以将页 ...
- Steam游戏《Northgard(北境之地)》修改器制作
日期:2021.06.07 博客期:181 星期一 [温馨提示]: 我现在把资源先放到开头,不想研究学习的就直接取用.如果修改器失效了,你们可以在博客园本页直接评论,也可以给我发邮件告诉我,就是不要到 ...
- oracle11g rac asm 实例内存修改
ASM实例内存修改 memory_max_target(它为静态参数,修改完成后需要重启实例) memory_target(它为动态参数,不需要重启实例) SQL> select name,is ...
随机推荐
- BZOJ3616 : War
对每个点维护一个bitset,记录哪些点可以攻击它. 可以通过kd-tree+标记永久化实现. 对于一个阵营,它在m轮之后防御系统全部完好的概率为$(1-\frac{攻击它的点数}{n})^m$. 时 ...
- 7、Redis中对ZSet类型的操作命令
写在前面的话:读书破万卷,编码如有神 -------------------------------------------------------------------- ---------- ...
- dwz(jui)刷新当前dialog的方法
做了个查看日志的功能,需要刷新查看当前的dialog,方法如下: 1 <script type="text/javascript"> 2 $("#bt_ref ...
- delphi Ribbon 111
Ribbon上包含以下一些元素,如图所示: 元素对应API: Element Ribbon API Quick Access Toolbar RibbonControl.ToolbarRibbonQu ...
- 对json数据key进行替换
原文:https://blog.csdn.net/qq_39750658/article/details/83411897 import java.util.HashMap; import java. ...
- eval json ajax
在JS中将JSON的字符串解析成JSON数据格式,一般有两种方式: 1.一种为使用eval()函数. 2. 使用Function对象来进行返回解析. 使用eval函数来解析,并且使用jquery的ea ...
- netty 支持多种通讯协议
通讯协议,指的是把Netty通讯管道中的二进制流转换为对象.把对象转换成二进制流的过程.转换过程追根究底还是ChannelInboundHandler.ChannelOutboundHandler的实 ...
- Oracle JDBC连接服务名、SID和tnsnames.ora配置的多种方式
昨天,领导安排去新服务器上部署项目,给了我数据库地址,服务名称,端口,用户名和密码.结果数据库一直连接不上,日志中的错误提示是监听未找到SID,我才明白原来我jdbc.properties中需要的是S ...
- spring mvc改动配置文件路径
1.1. Classpath project文件夹 在web.xml文件例如以下配置: <!-- 配置spring mvc 的核心servlet --> <servlet> ...
- 【C++】STL常用容器总结之五:双端队列deque
6.双端队列deque 所谓的deque是”double ended queue”的缩写,双端队列不论在尾部或头部插入元素,都十分迅速.而在中间插入元素则会比较费时,因为必须移动中间其他的元素.双端队 ...