一、CE的简单使用
1.CE的介绍
-
CE是一款什么软件:
-
CE修改器(Cheat Engine)是一款内存修改编辑工具,CE允许你修改游戏。它包括16进制编辑,反汇编程序,内存查找工具。与同类修改工具相比,它具有强大的反汇编功能,且自身附带了辅助工具制作工具,可以用它直接生成辅助工具
-
页面如下:
-
2.CE的简单查找及修改数据
-
假如现在你的电脑上正在运行一个exe文件,再复杂的exe文件,都是由一堆二进制数组成的
-
可以用CE搜索任何一款没有加反调试功能的exe程序,打开后可以搜索和修改此时exe文件在内存中的数据
比如现在我打开一款游戏,但是现在血量不多了,我想将血量加满,那么我们可以在游戏运行时使用CE打开游戏进程,接着可以找到血量对应内存中的数据是哪一块,可以将它修改
-
现在点击左上角的电脑图标,这里会显示电脑上的应用程序和当前正在运行的进程
如果有的软件加了反调试,这里就不会显示,打都打不开,后边中级阶段再去研究
-
如果我现在想知道nag.exe这个程序的在内存中有多少个1字节的1,就是以1字节的内存为单位找数值1。那么在CE中加载它,接着将数值类型改为"字节",十六进制数写为"1",由于搜索的是精确的数值,那么扫描类型选"精确数值",点击首次扫描
-
接着我们发现扫描出来的内存中,有两个颜色的地址中的数值都是1,绿色就是我们所说的基址,就是全局变量,下一次打开程序这些地址是不会发生变化的;黑色的可能是在堆中分的,也就是局部变量,下一次打开nag程序,黑色的地址可能会发生变化
**怎么解释为什么显示这些数:看到第一行的
0x026A01A1 1
表示的是从0x026A01A1这个内存地址开始往后的1字节宽度存的就是1!**现在懂了数值类型是什么意思了叭。再举个例子:如果扫描的值为4字节的100,则最后显示出来的结果就是以显示的地址开始往后4字节宽度内存中存的值是100 -
如果我们还想继续搜其他的数据,先点击新的扫描,接着设置好要扫描的内容即可
-
扫描类型:还可以是大于指定值、在指定值之间等等;数值类型:还可以是string,4字节等
-
找到我们想要的值以后,双击一下,接着会显示到下方栏中,再点击下方的数据修改即可:如果想改数值就双击数值;如果想修改地址就双击地址…
3.修改内存数据说明
- 如果我们平时打开单机游戏修改内存中的值,改的就会生效
- 但是现在很多网页端游或者需要连网和服务器交互的游戏,如果你修改此时客户端的游戏数据,只是改的客户端的数据,也就是你自己看的,但是服务器端的数据你是没有修改的,而且目前市面上大多连网游戏的服务端都有数据校验的,改了客户端的内存的值没有任何用
二、模拟CE扫描的功能
1.说明
-
这一堆数据中存储了角色的血值信息,假设血值的类型为int类型,值为100(10进制),请列出所有可能的值以及该值对应的地址
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09, 0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00, 0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11, 0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00, 0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00, 0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00, 0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00, 0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
2.模拟思路
-
先准备一大段数据(如上图),十六进制或者二进制都可以,这就模拟了任意exe文件的数据,可以使用char型数组来存放这100个十六进制数,因为这100个数都是两位十六进制,即1字节,那么使用char来存储就可以了
-
现在在一堆数据中,我们需要找到值为100的数,100换成16进制为0x64,即找0x64,但是注意我们应该要给定100的数值类型:比如是1字节的100,还是2字节的200,还是4字节的100等,即给定一个宽度
-
如果查找1字节的100,则从头到尾依次查找0x64即可
-
如果查找2字节的100,则从头到尾依次查找0x0064即可(在内存中会显示64 00,因为内存是反着存的,从低位到高位),但是要注意不是2字节2字节跳着查找,而是从第一个地址开始,往后数两字节宽度,如果是0x0064即把这个地址返回;接着再从第二个地址开始查找(而不是直接从第三个地址查)!即从第二个地址开始往后数两字节宽度,如果是0x0064(内存中显式64 00)则把第二个地址返回。所以还是要一个地址一个地址挨着查找,每到一个地址,取后面的2字节宽度来判断是否是0x0064
因为如果是00,64,00,00,64,00…虽然我们查的是2字节的100,即0x0064,但是不知道起始地址从哪开始。这个例子中,从第一个地址开始往后的2字节是0x6400;但是如果我们直接从第三个地址开始0x0000不满足;接着又从第五个地址开始0x0064,那么此时就会漏一个0x0064,因为第二个地址开始往后数两个字节值也是0x0064(记得反过来从内存中读数)。所以查找2字节的100,不能以两字节为单位跳着查找!那么还是要一个地址一个地址挨着查找,每到一个地址,取后面的2字节宽度来判断是否是0x0064
-
如果查找4字节的100,同理还是要一个地址一个地址挨着查找,每到一个地址,取后面的4字节宽度来判断是否是0x00000064,不能以4字节跳着查
-
3.实现步骤
-
C语言代码如下:
#include "stdafx.h" char blood[100] = { //全局变量 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09, 0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00, 0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11, 0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00, 0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00, 0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00, 0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00, 0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00 }; void findblood(int size){ if(size == 2){ //数值类型为short时,即2字节 short* temp = (short*)blood; //因为每次定位到一个地址,要查的数是从这往后2字节,所以先强转 for(int i = 0;i < 100 - size + 1;i++){ if(*(temp) == 100){ //因为temp是short*类型,所以取的地址宽度为2字节 printf("%x\t%d\n",temp,*temp); } char* temp2 = (char*)temp; //因为要一个一个地址挨着找,所以先转换成char*类型,如果拿 short*的temp直接++,那么结果就是源temp中地址+2*1,就不是依次逐个地址找了,就是跳了一个 temp2++; //那么char*变量在++时,结果为:地址 + char宽度,即下一个地址 temp = (short*)temp2;//然后再将下一个地址的值赋给temp,下次比较就是从temp往后数2字节是否 是0x0064 } }else if(size == 4){ //数值类型为int时,即4字节 int* temp = (int*)blood; for(int i = 0;i < 100 - size + 1;i++){ //因为如果4字节为单位,则查到倒数第4个地址就是最后 一个地址了,就不用继续我往后查了 if(*(temp) == 100){ printf("%x\t%d\n",temp,*temp); } char* temp2 = (char*)temp; temp2++; temp = (int*)temp2; } }else{ //数值类型为char时,即1字节 for(int i = 0;i < 100 - size + 1;i++){ if(*(blood + i) == 100){ printf("%x\t%d\n",(blood + i),*(blood + i)); } } } } int main(int argc,char* argv[]){ printf("result:\n"); int size = 2; //当数值类型为short时 findblood(size); printf("\n\n"); size = 1; //当数值类型为char时 findblood(size); printf("\n\n"); size = 4; //当数值类型为int时 findblood(size); printf("\n\n"); return 0; }
第二遍学时的改进:
#include "stdafx.h" char data[100] = { //全局变量 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09, 0x00,0x20,0x10,0x03,0x03,0x0C,0x00,0x00,0x44,0x00, 0x00,0x33,0x00,0x47,0x0C,0x0E,0x00,0x0D,0x00,0x11, 0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xAA,0x00, 0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x02,0x00,0x74,0x0F,0x41,0x00,0x00,0x00, 0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0A,0x00, 0x00,0x02,0x74,0x0F,0x41,0x00,0x06,0x08,0x00,0x00, 0x00,0x00,0x00,0x64,0x00,0x0F,0x00,0x00,0x0D,0x00, 0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00 }; int length = sizeof(data) / sizeof(data[0]); void find_blood(int num,int width){ //输入要查找的数值,查找宽度 if(width == 1){ for(int i = 0;i < length - width + 1;i++){ if(*(data + i) == num) printf("add:%x num:%d\n",data + i,*(data + i)); } }else if(width == 2){ for(int i = 0;i < length - width + 1;i++){ short* p = (short*)(data + i); //直接使用char* data++,但是每次判断前把当前地址转型赋给一个新short* p即可,不需要上面那么麻烦 if(*p == num){ printf("add:%x num:%d\n",p,*p); } } }else if(width == 4){ for(int i = 0;i < length - width + 1;i++){ int* p = (int*)(data + i); if(*p == num){ printf("add:%x num:%d\n",p,*p); } } }else{ printf("宽度不符合规定"); } } int main(int argc, char* argv[]) { printf("%x\n",data); find_blood(256,2); getchar(); return 0; }
-
内存图和结果如下:
-
-
我们来分析一下short:当选择查询的数据类型为2字节时,从第一个地址开始查
-
当查到0x424A52时,从这个地址开始往后数2字节在内存中显式为
64 00
,那么由于VC的堆栈图和内存都是反的:从低地址往高地址显式,那么此时我们应该读作0x0064,换算成十进制就是100,满足要求; -
当查到0x424A5A时,虽然此地址是0x64,但是我们选择的数据类型为2字节,所以应该从地址往后数2字节,内存显式为
64 10
,我们读作0x1064,所以不满足要求,则不要 -
当查到0x424A83时,从此地址开始往后数2字节,内存显式为
64 00
,我们读作0x0064,所以满足要求 -
后面依次类推
-
-
再分析一下int类型:也是从第一个地址开始逐个往后查,当查到0x424A52时,往后数4字节内存显式为
64 00 00 00
,我们读作0x00000064,所以满足要求。其他的4字节为单位都不满足要求