滴水三期:day20.1-CE基本使用与原理

本文详细介绍了CheatEngine这款内存修改工具的使用,包括如何查找和修改游戏内存中的数据,以及如何模拟其扫描功能。通过实例展示了在不同数值类型下如何搜索特定值,并给出了C语言代码实现模拟内存扫描。强调了对于网络游戏中内存修改的限制,以及在处理不同数据宽度时的注意事项。
摘要由CSDN通过智能技术生成

一、CE的简单使用

1.CE的介绍

  • CE是一款什么软件:

    • CE修改器(Cheat Engine)是一款内存修改编辑工具,CE允许你修改游戏。它包括16进制编辑,反汇编程序,内存查找工具。与同类修改工具相比,它具有强大的反汇编功能,且自身附带了辅助工具制作工具,可以用它直接生成辅助工具

    • 页面如下:

      image-20211213160946823

2.CE的简单查找及修改数据

  • 假如现在你的电脑上正在运行一个exe文件,再复杂的exe文件,都是由一堆二进制数组成的

  • 可以用CE搜索任何一款没有加反调试功能的exe程序,打开后可以搜索和修改此时exe文件在内存中的数据

    比如现在我打开一款游戏,但是现在血量不多了,我想将血量加满,那么我们可以在游戏运行时使用CE打开游戏进程,接着可以找到血量对应内存中的数据是哪一块,可以将它修改

  • 现在点击左上角的电脑图标,这里会显示电脑上的应用程序和当前正在运行的进程

    如果有的软件加了反调试,这里就不会显示,打都打不开,后边中级阶段再去研究

    屏幕截图 2021-12-13 161625 image-20211213162042512

  • 如果我现在想知道nag.exe这个程序的在内存中有多少个1字节的1,就是以1字节的内存为单位找数值1。那么在CE中加载它,接着将数值类型改为"字节",十六进制数写为"1",由于搜索的是精确的数值,那么扫描类型选"精确数值",点击首次扫描

    image-20211213162509010
  • 接着我们发现扫描出来的内存中,有两个颜色的地址中的数值都是1,绿色就是我们所说的基址,就是全局变量,下一次打开程序这些地址是不会发生变化的;黑色的可能是在堆中分的,也就是局部变量,下一次打开nag程序,黑色的地址可能会发生变化

    屏幕截图 2021-12-13 163143

    **怎么解释为什么显示这些数:看到第一行的0x026A01A1 1表示的是从0x026A01A1这个内存地址开始往后的1字节宽度存的就是1!**现在懂了数值类型是什么意思了叭。再举个例子:如果扫描的值为4字节的100,则最后显示出来的结果就是以显示的地址开始往后4字节宽度内存中存的值是100

  • 如果我们还想继续搜其他的数据,先点击新的扫描,接着设置好要扫描的内容即可

  • 扫描类型:还可以是大于指定值、在指定值之间等等;数值类型:还可以是string,4字节等

    image-20211213163643309 image-20211213164203346

  • 找到我们想要的值以后,双击一下,接着会显示到下方栏中,再点击下方的数据修改即可:如果想改数值就双击数值;如果想修改地址就双击地址…

    image-20211213164750768

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;
    }
    
    • 内存图和结果如下:

      image-20211214112504378 image-20211214112555555

  • 我们来分析一下short:当选择查询的数据类型为2字节时,从第一个地址开始查

    • 当查到0x424A52时,从这个地址开始往后数2字节在内存中显式为64 00,那么由于VC的堆栈图和内存都是反的:从低地址往高地址显式,那么此时我们应该读作0x0064,换算成十进制就是100,满足要求;

    • 当查到0x424A5A时,虽然此地址是0x64,但是我们选择的数据类型为2字节,所以应该从地址往后数2字节,内存显式为64 10,我们读作0x1064,所以不满足要求,则不要

    • 当查到0x424A83时,从此地址开始往后数2字节,内存显式为64 00,我们读作0x0064,所以满足要求

    • 后面依次类推

      屏幕截图 2021-12-14 112858 image-20211214113221152

  • 再分析一下int类型:也是从第一个地址开始逐个往后查,当查到0x424A52时,往后数4字节内存显式为64 00 00 00,我们读作0x00000064,所以满足要求。其他的4字节为单位都不满足要求

    image-20211214113922755 image-20211214113934860

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值