前言

因为游戏的某些数据是有可能存放到本地的,就导致可以通过一些工具检索出内存然后修改。
这也是之前单机游戏歪瓜来的方式之一。


引用

软件汉化文件也在官网download下面,有个ch_cn的中文简单,不会的用谷歌浏览器或者edge翻译
软件本身安装的时候有个教程,挺好的可以学一下。


正文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>

int diamond = 2500;

int main(){

while (true){
std::cout << "钻石数量:" << diamond;
system("pause");
diamond -= 10;
}

return 0;
}

随便写个,就是让钻石每次减少,反正就是变量自减的问题去模拟消费的时候。

效果大概就是这样,每次回车减10。

然后打开ce工具。去找到这个执行的exe程序


问题

我的开发环境

  1. win11
  2. vs2022
  3. 系统用户默认是管理员

所以当你以vs2022编译执行.exe的时候,权限太低了,ce抓到了也读不出什么东西。
只能打开当前项目文件夹路径找到debug里面的.exe,然后右击管理员执行。这样才能被ce抓到。

昨晚试了半天,还奇怪软件都官网下的还能有这事。。。一开始还以为是我下的windows terminal的问题。


测试

以管理员运行exe之后,用ce找到那个exe打开,检索值,因为int类型占用四字节,就不做改变

点击首次扫描之后的确看到了一个2500的值,为了印证,我们按任意键减少这个值看看是否发生变化。

存在变化,就说明这个地址是我需要的。

双击左边栏里的选项,在下面会多出一块,能看到地址和我们手动打印出来的一样。

这里手动打印是因为之前一直找不到,像手动试试也不行,可能就是权限太低了。

然后因为已经找到了地址,我们可以直接改数值。双击下面栏里的数值选项

随便修改成多少都行,然后跑cmd里按任意键触发

看到变成4990了,是因为触发的时候5000-10了。

当然这种操作都是本地数据,网络数据会被服务器校正,那些就不是靠这个简单的手法能改的了。

我们继续右击下面栏的那条内容,找到是什么修改了这个地址

然后在cmd按回车触发。

当我们触发的时候,里面就有了值。就是简单的汇编代码,点击替换选项能看到mov指令,因为值已经计算好放到eax寄存器了。
然后关闭,我们打开显示反汇编程序

看到mov [gameCE.diamond],eax之前有一句sub eax,0A,就是eax-10的意思,我们直接将其修改为add eax,0A

先停止调试器然后推出,在cmd里继续触发

看到值果然开始递增了。


不过还是那句话,这些事本地的数据,最常见就是单机的游戏,所以单机游戏的歪瓜会好做一些,但是肯定是吃一堑长一智,本地化的数据其实也不会这么透明,很有可能会有加壳之类的操作。

以int为例,四字节的数据可以拆成四个一字节的数据,然后打乱或者按某种规则排。
也可以混淆这个值,比如正常的时候2500,每次运行的时候+500 -500混淆一下。

构建一个hint类,然后要完成一些加减法重载。

1
2
3
4
5
6
7
8
9
10
11
12
#pragma once
class hint{
private:
char *mem[4];
public:
hint(int _val = 0); //构造函数

operator int(); //重载int转换
void operator=(int _val); //重载赋值

~hint();
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include "hint.h"

hint::hint(int _val){
//1. 1 1 1 1
//2. +500 -500

mem[0] = new char;
mem[1] = new char;
mem[2] = new char;
mem[3] = new char;

*this = _val;
}

hint::operator int(){
//逆运算,主要是反构造函数中的打散情况
int _val=0;
char *read = (char *)&_val;
read[3] = mem[0][0];
read[1] = mem[1][0];
read[0] = mem[2][0];
read[2] = mem[3][0];

return _val;
}

void hint::operator=(int _val){
//四字节拆分打散
char *read = (char *)&_val;
mem[0][0] = read[3];
mem[1][0] = read[1];
mem[2][0] = read[0];
mem[3][0] = read[2];
}

hint::~hint(){
delete mem[0];
delete mem[1];
delete mem[2];
delete mem[3];
}

基本设计就这样,然后跑一下代码也ok的。

再次到文件夹目录用管理员打开.exe,然后ce去检索

这一次发现2500搜不到了。再往下减也搜不到

但是,其实有个范围的值还是能搜到,就是char的范围,只要这个数减小到255以内,就会被检索到了。

这里犯了个错误,我用的四字节去搜一字节的内容是不对的。

重新跑一次

当我检索到230的时候发现有一堆值,那就再减一次,果然看到了一个变化的地址。

双击选中,看数值是存在变化,说明这个字节的内容就是打散的四个之一。准确来说应该是mem[0];

因为是mem[0]被发现了,那么给他单独异或操作,那么ce就无法检索出正常值了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hint::operator int(){
//逆运算,主要是反构造函数中的打散情况
int _val=0;
char *read = (char *)&_val;
read[3] = mem[0][0];
read[1] = mem[1][0];
read[0] = mem[2][0]^0x23;
read[2] = mem[3][0];

return _val;
}

void hint::operator=(int _val){
//四字节拆分打散
char *read = (char *)&_val;
mem[0][0] = read[3];
mem[1][0] = read[1];
mem[2][0] = read[0]^0x23;
mem[3][0] = read[2];
}

修改完之后编译器先重新编译运行一下看看是否正常
然后在管理员运行,用ce捕获

老规矩用字节来搜,这里几百个值不管他,不是想要的,挨个减,然后搜

大致到了后面其实就找不到了。说明异或保护成功了。


结语

安全措施到位,破解起来花费的时间和精力就更多了。
这里的例子比较浅~~后面有时间搞逆向再说,先搞正向就业。