详解游戏辅助编程
【目录】
1-什么是Windows API
2-Windows进程
3-Windows 的内存的运行原理
4-windows 中句柄的概念
5-Windows的变量类型
6-辅助实现的原理
7-编程实现游戏辅助
8-怎样查找内存地址
9-总结
准备软件:VC,CheatEngineer5.5
学习这部分内容,你必须要掌握C语言的基础知识,非常基础的语法就行了。这篇文章的内容适合刚开始接触编程的人,高手请飘过。
【1】什么是windows API
Windows API 中文翻译过来就是windows应用程序接口(Application Programming Interface)
我们知道,我们在使用C/C++的时候,会包含很多的头文件,例如最常用的stdio.h,里面有很多函数的声明,例如使用scanf函数能输入数据,使用printf函数能打印文本到屏幕上显示出来。又例如math.h里面有很多数学计算函数,如sqrt求平方根的函数。我们只要包含这些头文件,就能调用这些函数。只要给函数传递相应的参数,就能实现我们想要的效果,这些函数就是C语言给我们提供的“接口”。
所谓windows应用程序接口,其实就是windows提供给程序员的一组函数。
Windows api 包含几千个可调用的函数,这些函数能让我们程序员操作系统的方方面面。
我们就可以调用者几千个函数实现各种功能。如调用ExitWindowsEx来关闭计算机,CopyFile来复制文件,MessageBox来弹出系统提示框,用BitBlt来绘制图像等等。而调用这几千函数的条件,就是在代码的开头加上#include <windows.h>就行了,因为绝大部分的函数声明都在windows.h里面,我们只要包含了这个头文件,就能够使用这些函数。
所以可以这么理解
Windows API =windows提供的几千个函数
【windows进程】
关于进程,我想大家都应该熟悉这个名词。在应用程序无响应无法关闭的时候,我们常常使用CTRL + ALT +DELETE来叫出任务管理器
我们可以看到一个进程列表,其中列出了所有正在运行的进程
进程的概念:进程就是一个正在执行的程序。
假如我们要运行一个程序,首先找到文件(如qq.exe)或者快捷方式,双击打开。操作系统就会把这个程序的文件从硬盘上加载到内存中,等加载完毕后,CPU开始执行包含在程序中的代码,然后系统进程列表多了一个qq.exe,这就是进程——正在运行的应用程序。等到这个程序qq.exe运行结束了,系统就会释放这个程序在内存中占用的空间,并且从进程列表里移除这个程序。
进程的特点:每个进程都是相互独立的,互不干扰的。通常情况下,一个进程只能操作自己的代码和数据,无法对其他进程造成影响。这也保证了windows系统运行的稳定性。
进程=正在内存中运行的程序
【windows内存的运行原理】
这个是外挂编程的最重要部分,我们分几个方面讨论:1 什么是内存?2 进程的边界--虚拟内存空间 3 打开进程的边界
什么是内存
内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁,其存取速度比硬盘快很多倍。内存对应是电脑的内存条,能存储数据,但是与硬盘不同,在内存中的数据一断电就会消失,并且存取数据的速度是硬盘的几十倍,所以将程序加载到内存中运行将大大提高程序的运行速度。现在一般的电脑内存1GB,2GB,4GB或者更高的8GB。
计算机中所有程序的运行都是在内存中进行的,因此内存的性能对计算机的影响非常大,并且CPU只能执行和处理在内存中代码和资源。
程序的存在形式实际上是这样的:我们的程序文件,首先是以二进制存在于硬盘上的,有的游戏特别大,几个GB,当我们打开资源管理器时,查看的,其实就是硬盘上的文件。当我们运行程序时,程序会适当的从硬盘加载自己需要的数据,然后开始运行。
由于内存比较少,当程序不需要这些数据的时候,就会从内存中释放一些不需要的资源,以保证内存的充足。
总的看来当执行一个程序的时候,系统会将exe文件中的代码和资源从硬盘加载到内存中,加载完成后,CPU开始执行程序入口点的代码,一个程序开始运行。
程序结束时,将释放所有这个进程占用的资源,以避免内存的浪费。
概括来说:内存,其实存储运行中的程序代码和数据的地方(CPU只能处理内存中的代码和数据),当进程结束时,该进程所有占用的内存空间将被系统释放。
进程的边界--虚拟内存空间、
我们设想一下,假如一个系统中有很多的程序在运行,我们只有一个内存空间,这样的话,一个程序在读写数据的过程中,由于程序自己本身的缺陷,错误的读写了其他程序的数据,这样就很容易影响其他程序,造成其他程序的崩溃。这当然是我们不愿意见到的。
为了解决程序之间使用内存互相干扰的问题,于是便有了虚拟内存。
Windows的虚拟内存机制为每个进程分配了4GB的虚拟内存空间。这里我们不免产生疑问,我们的实际物理内存空间只有1GB或者2GB的,等等,怎么能给每个进程分配4GB内存空间呢?
实际上,这4GB空间最下面的64KB是空指针赋值区域,系统保留。内存最上面的2G被系统占用。我们能够访问和使用的,也只有中间这一部分内存而已。这部分内存将近2GB,我想对所有程序来说,存储代码和数据都是够用的。
但是每个进程都有这么大内存可以用,这些内存空间从何而来呢。Windows在执行一个程序的时候,为了节约内存空间,将程序内存中暂时用不到的数据,以二进制存储到硬盘上(这些二进制文件其实就是页面文件)。等到需要的时候,再从硬盘上加载到内存中,这样就以牺牲少量CPU运行时间为代价,将硬盘当做内存使用。使得多程序同时执行成为了可能。这4GB中的数据,有的是存在于硬盘上的,有的是存在于内存中,所以说,每个进程的4GB内存空间,是“虚拟”内存。
虚拟内存的地址一般用十六进制表示,以字节为单位,地址范围是0x00000000~0xFFFFFFFF(0x表示十六进制)。
由于这样的机制,我们不难发现,进程之间就不能相互影响,保证了各进程的稳定性。例如有两个进程A和B,进程A可以在它自己内存空间0x12345678地址存储数据,进程B也可以在0x12345678地址处存储数据。当进程A访问0x12345678处的内存时,访问的是进程A的内存空间;当进程B访问0x12345678处的内存时,访问的是进程B的内存空间。进程A无法访问位于进程B的内存空间中的数据,反之亦然。
这就是进程的边界--每个进程分配了4GB的虚拟内存空间,它们在自己的内存空间内运行,不会相互干扰,保护了系统和各进程的稳定性。
为什么要以16进制表示地址
编程中,我们一般用十进制表示一个数,因为C/C++是高级语言。
例如x=78;
不过,由于数据在计算机中的表示,最终以二进制的形式存在,所以有时候使用二进制,可以更直观地解决 问题。但二进制数太长了。比如int 类型占用4个字节,32位。比如100,用int类型的二进制数表达将是:
0000 0000 0000 0000 0110 0100
面对这么长的数进行思考或操作,没有人会喜欢。因此,C,C++ 没有提供在代码直接写二进制数的方法。用16进制可以解决这个问题。因为,进制越大,数的表达长度也就越短。
例如同样一个地址。用十进制和十六效果对比下:
十进制 十六进制
1258965451 4B0A49CB
5600 000015E0
85693 00014EBD
8965231 0088CC6F
这样可以看到用十六进制表现更直观一些。并且,在很多软件中,数值的表示都是用十六进制的,例如WinHex,CE,OllyDBG,几乎所有与内存有关的数据都是用十六进制表示。用十六进制最大的优点就是缩小了表达长度。
打开进程的边界
当然所有的事情不是绝对的,我们有很多时候也需要访问其他进程中的数据。例如,杀毒软件要监视其他进程的行为,防止其他进程做有害系统的事,杀毒软件往往就会在其他进程里注入自己的代码,以此来监视其他进程的行为。这就是访问其他进程数据的最好例子。
当然,想要访问其他进程的内存空间,需要强大的windows api。我们知道,windows api是系统提供给程序员的一组强大的函数,几乎能实现任何关于系统的任何操作。
我们要打开进程的边界,修改其他进程的数据,当然需要调用相应的函数。
我们的目的很明确,就是要修改其他进程(如游戏进程)的数据。
下面的一些函数是访问其他进程数据和外挂编程必备的函数,在后面介绍外挂编程的时候,我会详细的介绍这几个函数,现在先熟悉一下。
1. 查找窗口函数
FindWindow
2. 通过窗口获取进程ID函数
GetWindowThreadProcessId
3. 打开进程函数
OpenProcess
4. 写其他进程数据的函数
WriteProcessMemory
5. 读其他进程数据的函数
ReadProcessMemory
6. 关闭句柄的函数
CloseHandle
这六个函数,就是写一个简单外挂的所有函数。在后面我会详细说明打开进程边界的步骤,以及如何使用这些函数。
【windows 中句柄的概念】
句柄,是整个windows编程的基础。一个句柄是指使用的一个唯一的整数值,即一个四字节长的数值,来标志系统中的不同对象。
打个比喻,一个学校有很多很多的学生,为了识别这些学生,我们会给每个学生分配一个学号。当我们要找一个学生的时候,我们只需要知道这个学生的学号,然后查阅相关信息就能找到这个学生。
同样,在windows系统中有很多很多的对象,如一个窗体,一个进程,一个图标,一张图片,甚至一块内存空间等。Windows 给它们都分配了不同的一个unsigned int型数(即无符号整型数)来标识和区分它们。这个标识号,就叫做句柄。
我们可以通过系统分配的这个标识号,也就是句柄,来访问这些对象。
例如,我们可以通过一个窗口的句柄,找到这个窗口,然后可以修改这个窗口的大小,标题名字等等。
我们可以通过一个进程的句柄,来结束这个进程。
我们可以通过一张图片的句柄,来将这张图片画在屏幕上。
等等等等。
句柄,就是系统中每个对象的标识号,我们可以通过这些标识号,来访问相应的系统对象,如窗体,进程等等。
【windows变量类型】
在windows编程中,我们往往会看到很多变量类型,如HANDLE,HWND,BYTE,DWORD等等。
这些变量类型是什么呢,和我们熟悉的char,short,int等等的变量类型有什么区别呢。
而经常要用到的 句柄 HANDLE类型,实质上是无类型指针void,HANDLE定义为:
typedef PVOID HANDLE;
HANDLE实际上就是一个PVOID,那PVOID又是什么呢?
Typeef void *PVOID;
PVOID就是指向void的指针(void *)。
所以HANDLE = void*
那为什么要多此一举呢,直接用void*代替就行了,为什么要用HNADLE呢?其实,这是为了让程序员更能读懂程序。只要看到变量类型就能知道这个变量是用来干什么的。
例如:我要用a,b来表示长方形的宽和高,如果都用int型我们要花费一番功夫才能理解这些意思。
但是我们分别定义两种变量类型Width,Height就能一目了然了。
typedef int Width;
typedef int Height;
Width a; //定义width型变量a,一眼就能看出a变量是来表示宽度的
Height b; /定义wheight型变量b,一眼就能看出b变量是来表示高度的
声明特定的变量类型其好处就不言而喻了,就是为了让程序员更好的理解任何变量的作用,使人一目了然。
下面我们需要知道的变量类型以及这些变量类型的作用;
HANDLE =void * HANDLE型变量是一个对象句柄
HWND =void *HANDLE WINDOW 窗口句柄
DWORD = unsigned int 无符号整型,windows通常用来表示一个对象的序号ID
BYTE =unsigned char 无符号字符型,0255
我们做外挂,只需要了解上面3种windows数据类型就行。
另外我需要声明一点:void *型的变量,一般都是用来表明内存地址的,
如 void * Ptr=0x12345678
这个ptr指针,表示的是0x12345678这个内存地址
指针我现在不想多说了,这部分在后面的编程中会看到
【辅助的的实现原理】
我们现在做的只是游戏外挂,有一句古话说的好:知己知彼,方能百战不殆。我们要做游戏的辅助,就要先知道游戏是怎样运行的。
我们知道,一个游戏进程的有很多数据,例如,一个角色的HP,一个角色的经验,他的金钱,等级,以及装备都是通过变量来存储的。我们只要找到这些变量在内存中的地址,然后通过某种方法去修改这些数的数值,就能达到修改游戏的目的。
我们先来说说这些游戏中德数据,怎么判断是什么类型,怎么得到在内存中占的空间大小。
例如,一个人的经验一般用int型变量(4字节)来存储,为什么呢?
因为int型变量(4字节)的取值范围是2147483648~2147483647,而short型变量(2字节)的范围是,由于short的范围太小,而很多游戏的经验值一般都超过这个范围,例如在地下城与勇士的游戏中,我的经验值是108866674523,一千多万,所以,在这个游戏中,一个人物的经验值是必须是int型的,用short型变量会超出范围导致程序运行出错。
我们为什么要知道这些变量占多大内存空间呢?那是因为我们在修改其他进程的数据的时候,我们必须首先要有三个参数:1:在哪个地址 2要修改成多少 3有多大的内存数据要被修改。例如:我们要修改的地址是0x00EFFAE0,要修改成1000000,由于这个变量是int型的,所以,有4字节的内存数据要被修改。这很容易让我们想到游戏程序的源代码里面有这条代码
int exp; //人物经验
然后&exp就等于0x00EFFAE0
好吧,开始进入正题了。游戏辅助一般分为两大部分:
一就是找出我们想要的内存地址。
二就是写程序去修改这个内存地址的数到一个相应的数值。
我们先不谈第一个部分,因为第一个部分变化性大,因为很多游戏做了不同程度的保护,使我们找内存地址大费周折,例如CS。不过也有游戏没做任何保护,例如,植物大战僵尸。但是,我们写程序修改其他进程内存中的数据,这个方法是不变的。所以我先来说说如何修改其他进程中的内存数据。
第一步:查找游戏窗口句柄
第二步:通过窗口句柄,获取目标进程的ID
第三步:通过目标进程的ID,打开目标进程,获得句柄
第四步:通过目标进程的句柄,修改目标进程的内存数据
第五步:关闭目标进程
我想有必要说一下,第一步,第二步是为第三步服务的,我们要修改某一进程的内存数据,必须获得这个进程在系统的身份证,即进程的句柄。获取一个进程的句柄有很多方法,我在这里说的第一步,第二步,第三步是最常用的获取目标进程句柄的方法。
但是,为什么如此曲折的才能获得目标进程的句柄呢,我想,主要和我们要使用的windows api 有关。
接下来我们就要具体说说这些强大的windows api函数了。我们之前说的所有知识,很多windows运行原理,都是为了理解下一节这些函数的调用。
并且下一节用到的函数较多,我们只有经常使用,我们才能掌握它们。
[编程实现辅助]
上一节我们说了:要修改一个进程的内存数据必须先获得这个进程的句柄,就像我们要找一个人一样,我们可以通过这个人的身份证,知道这个人住在哪里,才能找到这个人。在windows系统里面也一样,我们只有知道这个进程的身份证--句柄,系统才能找到这个进程,并相应的按照我们的需求修改数据。
在开始说辅助编程之前,我先要说说一个很有用的函数,这个函数就是MessageBox,先来看看这个函数有什么效果。
是不是有种熟悉的感觉,没错这就是我经常看见的windows提示,现在我们程序员可以自由操作windows提示。现在来具体说说MessageBox函数
首先来看看函数原型
int WINAPI MessageBox(
HWND hWnd,//消息窗口父窗口句柄
LPCTSTR lpText,//显示的消息内容
LPCTSTR lpCaption, //消息框的标题
UINT uType);//消息框风格
第一个参数:消息框的父窗口句柄,为了方便,我们可以设为NULL,不影响辅助的使用。
第二个参数:显示的消息内容,上面图片示例中游戏已经运行,游戏没有运行都属于消息内容
第三个参数:消息窗口标题,上面图片示例中都是“提示”。
第四个参数:消息框的风格,现在我只介绍三个常量
MB_OK:表示有确定按钮
MB_ICONINFORMATON:表示有信息图标,上左图
MB_ICONERROR:表示有错误图标上右图
用 ” | ”符号同时使用多种风格,例如使用MB_OK|MB_ICONINFORMATION,确定按钮和信息图标,上左图所示。MB_OK|MB_ICONERROR,确定按钮和错误图标,上右图所示。
下面,进入正题我们来说说修改其他进程内存数据的第一,二,三步——获取目标进程的句柄,并且学习相应的函数。
第一步:获取目标游戏窗口的句柄
在windows中,一个窗口的句柄数据类型是HWND,就是handle window的简写,我们可以这样,前面我们已经说过windows变量类型的概念,HWND其实就是void*类型的,这里我就不多说了。重点我们知道怎么用。
Window API里面有这么一个函数,函数原型如下
HWND FindWindow(
LPCSTR lpClassName,
LPCSTR lpWindowName);
我们可以知道,返回值HWND类型的变量是存储一个窗口的句柄的。那么LPCSTR是什么变量类型呢?
LPCSTR=char *,这个变量类型其实就是char *类型,是一个字符串的指针,以后我们只要看到LPCSTR类型的变量,我们就可以知道这个变量是存储一个字符床地址的指针的。这就是声明很多变量类型的好处,看到这个变量是什么类型的,就知道这个变量是用来干什么的。
它有两个参数,两个参数都是字符串的指针
第一个参数lpClassName,这是要查找窗口的类名,关于窗口的类名,这里没有说明,主要是因为我们可以将此参数设为NULL,也基本不影响我们查找游戏窗口的句柄。有兴趣的可以看看《windws核心编程第5版》,上面说的很详细。
第二个参数lpWindowName,这个就是主要参数了,目标窗口的标题,例如,植物大战僵尸的窗口标题就是“植物大战僵尸中文版”,红色警戒窗口的标题就是“Red Alert 2”.但是有很多游戏是全屏幕显示的,不是窗体形式的,没有标题,那我们怎么知道它的标题呢?其实有一种很简单的方法,游戏全屏运行后,就是按开始菜单键将游戏最小化。然后将鼠标移动到任务栏的游戏图标上,系统就会提示该窗体的标题。
知道了这两个参数,我们可以这么写代码调用这个函数。
HWND gameWindow=FindWindow(NULL,”植物大战僵尸中文版”);
当这行代码执行完毕后我们gameWindow这个变量就保存游戏窗口的句柄。但是我们需要注意,当目标进程没有运行,也就是不存在窗体标题为”植物大战僵尸中文版”的窗体时,游戏系统找不到这个窗口,FindWindow调用失败,返回值为0,即gameWindow为0。所以这个函数,可以判断游戏有没有运行。加上下面代码就有这个效果
If(gameWindow==NULL)
//提示游戏没有运行
Else
//提示游戏已运行
第二步:通过窗口句柄,获得进程ID
Windows api提供了这样一个函数,函数定义如下
DWORD GetWindowThreadProcessId(
HWND hWnd,
LPDWORD lpdwProcessId
);
我们可以看到,这个函数只有两个参数,第一个参数,就是目标窗口的句柄(上例中的gameWindow),我们只要填上我们获取到的窗口句柄就OK
第二个参数LPDWORD,LP代表指针,DWORD代表unsigned int,所以这个参数就代表unsigned int *,是一个无符号整数型的指针。那么这个参数是什么呢?
每个进程不仅有自己的句柄,还有自己的序号,在系统中叫ID,这个ID是DWORD型整数,在windows中就叫ProcessID
上面这张图就是我用tasklist命令列举出系统中正在运行的进程,上面的PID(ProcessID)就是该进程的标识号,可以看到,wininit.exe进程的ID是516 ,csrss.exe进程的ID是528,等等。我们可以通过目标窗口的句柄,知道这个窗体是属于哪个进程的,然后通过这个函数我们就可以知道,这个窗体所在进程的ID,我们可以这样使用。
DWORD pid;
GetWindowThreadProcessID(gameWindow,&pid);
第二个参数传入一个DWORD变量的地址就行了。
当这行代码运行完毕后。pid就自动填充了目标窗体所在进程的ID。
第三步通过目标进程的ID,打开目标进程,获得句柄
Windows api提供了这样一个函数
HANDLE OpenProcess(
DWORD dwDesiredAccess, //渴望得到的访问权限(标志)
BOOL bInheritHandle, // 是否继承句柄
DWORD dwProcessId// 进程标示符即,进程ID
);
首先它的返回值是HANDLE类型的,返回值就是目标进程的句柄。再来看看这三个参数。
第一个参数,我们想要得到的访问权限。这里我们使用常量PROCESS_ALL_ACCESS,这个是在windows.h里面定义的常量,注意要全部大写。用了这个常量,就等于我们对系统说:“我要获取对该进程操作的所有权限”,例如读写内存空间等等。等这个函数调用成功后,我们就可以获取对该进程进行任何操作了。
第二个参数,我们不考虑,设为NULL。
第三个参数,就是我们获得的进程ID。
我们可以这么调用:
HANDLE hProcess=OpenPrcess(PROCESS_ALL_ACCESS,NULL,pid);
这样这行代码运行后,hProcess被填充了目标进程的句柄。当然,如果这个函数因为某种原因调用失败的话hProcess就为NULL,所以我们可以加上下面的错误处理代码
If(hProcess==NULL)
//提示打开进程失败
好现在总结一下这个步骤。
我们先通过FindWindow找出目标窗体的句柄
再通过GetWindowThreadProcessId获得目标进程的ID
最后通过OpenPocess打开进程,获得句柄
最后需要特别注意一点,在windows xp上这么写代码会运行正常,但是在windows7或者windows8上,程序必须要管理员权限。FindWindow会因为你没有管理员权限而调用失败,最后导致你的程序无法正确的获得目标进程句柄。
第四步修改进程的内存数据
这是最关键的一步了,修改内存数据。前面我们已经说过,外挂主要分为两个重要步骤,第一个是找内存地址,哪些数据是我们要修改的,如一个人的金钱,经验,属性,等级。我们要找出这些数据在进程中的内存地址。第二步就是写程序去修改这些数据。写程序修改数据的方法是一成不变的,但是找内存地址却有很大的技巧性。这里限于篇幅就不多说了,后面我会简单的介绍一下找代码的原理,然后我会推荐一些专门的文章给大家看的。
Window提供了下面一个函数来修改进程的内存数据,函数定义如下
BOOL WriteProcessMemory(
HANDLE hProcess,//目标进程句柄
LPVOID lpBaseAddress,//目标进程写入地址
LPVOID lpBuffer,//自己进程中缓冲区地址
DWORD nSize,//缓冲区大小
LPDWORD lpNumberOfBytesWritten//实际数据长度,设为NULL
);
返回值是BOOL型,返回TRUE调用成功,FALSE调用失败
第一个参数:目标进程的句柄,我们可以通过前三步获得目标进程的句柄
第二个参数:目标写入的起始地址,即要将数据写到目标进程哪个位置。这个就是我们找到的内存地址,例如一个人的血量内存地址是0x40000000
第三个参数:写入的缓冲区地址
第四个参数;写入的缓冲区大小
这第三个,第四个参数是什么意思呢?其实WriteProcessMemory工作原理是这样的
我们将自己进程中的数据,拷贝到其他进程中。自己这个缓冲区的地址就是第三个参数lpBuffer,缓冲区的大小事第四个参数nSize。这个函数将我们进程中lpBuffer地址起始处nSize大小的数据,原封不动的拷贝到目标进程的 lpBaseAddress(第二个参数)处。
假如,我们找到游戏进程人物金钱的地址是0x40000000,,我们就可以这样写代码
Int Money=10000000;//定义一个变量后自己进程已经分配一块区域存这个变量
WriteProcessMemory(hProcess,(LPVOID)0x40000000,(LPVOID)&Money,,(DWORD)4,NULL);
第一个参数我们填入目标进程的句柄
第二个参数是目标进程的写入地址,编译器认识“0x”,知道0x40000000是一个16进制的数,我们将这个数强制转换成LPVOID型,也就是void*型,无值型指针
第三个参数我们填入了我们进程Money变量的地址,并且将这个变量地址强制转换成LPVOID型。
第四个参数由于在c++中int型变量默认占四个字节内存空间,所以我们填入4,并将这个数转换成LPVOID型
第五个参数 填NULL
这就是WriteProcessMemory的用法,功能强大,能写其他进程的数据。
这样,我们就完成了第四步,写游戏内存数据,我们再次回到游戏会发现,游戏中相应的数据已经变成我们想要的数值了,看下图,植物大战僵尸修改后的结果
第五步关闭进程句柄
我们需要调用CloseHandle函数来关闭我们打开的进程,这个函数的使用方法很简单。
CloseHandle(hProcess);
它只有一个参数------要关闭的句柄这里我们填入我们打开的进程句柄即可。
至此我们已经看到了一个完整的程序代码
我们来回顾一下
我想没有什么能比真实的游戏辅助源代码更好的了解外挂了,下面,我将给出Vc++6.0环境下,植物大战僵尸辅助的真实源代码。我把修改代码放到了ChangeGame函数里面,我们只要在main函数或者WinMain中调用这个函数就ok
void ChangeGame()
{
//通过标题获取窗口
HWND gameWindow=FindWindow(NULL,"植物大战僵尸中文版");
if(!gamewindow)
MessageBox(NULL,”游戏未运行”,”错误”,MB_OK|MB_ICONERROR);
//获取进程标示符pid
DWORD pid;
GetWindowThreadProcessId(gameWindow,&pid);
//打开进程
HANDLE hprocess=OpenProcess(PROCESS_ALL_ACCESS,0,pid);
//修改数据
int sun=56789;
WriteProcessMemory(hprocess,(void *)0x1429E2B0,&sun,4,0);
//关闭进程
CloseHandle(hprocess);
}
[怎样查找内存地址]
CE是一个强大的工具,它的搜索速度非常快,一般的游戏程序用它来搜索内存能在短短数秒内搜索几百万甚至几千万个内存地址。为我们编写外挂提供了很大的帮助。
好,废话不多说,边上图边解说,先来学学简单的内存修改,熟悉熟悉CE的操作。
第一步:选择目标进程
先点击左上角的第一个按钮,会出现右图进程列表。该列表中列举出了系统中所有正在运行的程序。我们选择植物大战僵尸程序(PlantVsZombies.exe)。这样就完成了选择目标进程。
第二部:搜索阳光的内存地址
先运行植物大战僵尸,开始一局游戏,我们的目的是要修改阳光。于是先看看阳光现在是多少,150,很好(下左图)。我们在CE值里面的填入150,点击首次扫描(下右图)
看CE左边的地址栏,我们可以看到,我们用CE搜索到了145个地址(下左图)。这说明在这个游戏中,有这么多个地址的值是150.。但是我们所需要的阳光的变量,只是这么多其中的一个,所以我们种一颗植物,现在阳光变成了50,然后我们输入50,点击再次扫描(下右图)
再次扫描其实是在上次扫描的结果中,再次搜索这些结果的值。当我们通过游戏操作修改我们所需要的变量的值的时候,通过再次搜索就很容易把我们想要的变量找出来。之后会出现下面的结果
我们可以看到,结果地址里面只有一个了,没错,这个搜索到的地址0x1429E2B0就是我们要找到阳光的地址。我们双击这个地址,这个地址会出现在下面。的编辑列表里,我们再双击编辑列表里这个地址的值,输入56789,点确定。这个时候我们回游戏就会发现已经变成我们想要的值了。
好了,这是找内存的最简单的方法。但是还有一些问题。
第一个问题:
事实上,有很多游戏都做了一些保护措施。例如你的植物大战僵尸里面金币显示的是1120,但是实际上在内存中这个值是112。如果你一开始搜1120,并且按照这个方法搜下去的话会一个地址也搜不到。相同的例子还有很多。例如侠盗飞车人物的血量上面显示的是87,在内存中这个数值是17023+87,流星蝴蝶剑中你看到你的血是230,但是在内存中你的血的值是2300等等。
第二个问题:
如果你重启植物大战僵尸后会发现,阳光变量的地址发生了改变,变成0x143780B4,这是为什么呢。因为植物大战僵尸中,存储阳光的变量地址是动态分配的,每次分配的地址都是不一样呢。
这就是编程中的局部变量,这个局部变量所属的函数执行完了,这个局部变量就会从内存中释放。等下次再次执行这个函数时,这个局部变量又会从内存中重新分配空间。
这样每次运行植物大战僵尸,由于动态分配,每次阳光变量的的地址都是不一样的。
所以说,搜索也是有一定复杂性的。第一个问题可以通过模糊搜索,多次搜索解决。第二个问题,可以通过查找变量的偏移地址,计算出准确的阳光地址(这个需要ReadProcessMemory函数)或者通过反汇编。
虽然听起来很复杂,但是这些搜索技巧其实都是很简单的,这里就不一一介绍了。因为要说搜索内存的方法的话,说清楚没有几千字是不行的。这篇文章主要是说外挂编程的原理,关于内存搜索的方法,我找到几篇很好的文章,大家看一看就足够了:
CE找内存偏移地址图文教程:
http://bbs.52miji.com/thread-1224-1-1.html
CE搜索内存技巧:
http://www.v5pc.com/thread-572-1-1.html
关于找内存地址就说到这里。
[总结]
游戏辅助,其实就是用来修改游戏,方便玩家玩游戏的的一个软件工具。
这篇文章所说的游戏修改都是初级的游戏修改,只涉及到修改内存变量。事实上,像高级的游戏修改,例如消除冷却时间,修改游戏界面,改变游戏运行逻辑等等,这些知识还远远不够。这些都需要最低级而又最强大的汇编语言来解决。通过反汇编逆向分析游戏代码,修改游戏代码而达到令人咋舌效果。
例如LOL盒子,至少运用了DLL远线程注入,函数挡截,反汇编代码修改等等技术,所以能够给游戏添上一些辅助功能。
学习编程的道路很漫长,有很多的非常有用知识不是课堂上能学到的,等着你自己去发现,去挖掘。等你深入到某一领域的时候,你会发现,这里还有非常广阔的天空,你要用一生的时间去探索它,掌握它。