标签:通过 没有 自己的 mil 原型 页游 解析 原理 图标
本章旨在讲解外挂实现原理,未深入涉及至代码层面。希望能与对这方面感兴趣的朋友多多交流,毕竟理论是死的,套路是固定的,只有破解经验是花大量时间和心血积累的。
一般套路就是上述,一些防护性强大的游戏会在上述的每一步中都设置难题,等着我们去破解。
在此之前,我们来了解一些基础知识:
整数型:游戏中比如血量、法力可能用到这种类型。
字节型:根据不同的编辑器,1个整形占用N个字节(N>1),一般很早之前出产的GBA游戏为了节省开销会用到这种类型。
浮点型:带有小数点的数字,如果金币或者伤害值带有小数点,那很可能是这种数据类型保存的。
文本型:比如世界喊话,人物命名,一般采用文本型保存这类数据。
推荐阅读《数据结构》相关书籍更好的熟悉数据结构,有编程经验的就不必多说了。
推荐阅读《计算机操作系统》相关书籍更多的了解计算机原理。
进入正题,根据目的反向推导下需要得到哪些信息,我们最终才能想要实现修改数值。
修改变量的数值→得先找到存储变量的内存地址→得先找到游戏窗口的句柄→得先找到游戏的进程。
根据什么依据推导出的路线呢?
Windows系统库的kernel32.dll库文件中包含了内存操作的API,其中VirtualQueryEx用于查询地址空间中内存地址的信息。
函数原型:
/// <summary> /// 查询地址空间中内存地址存储的信息 /// </summary> /// <param name="hProcess">句柄</param> /// <param name="lpAddress">内存地址</param> /// <param name="lpBuffer">结构体指针</param> /// <param name="dwLength">结构体大小</param> /// <returns>写入字节数</returns> [DllImport("kernel32.dll")] public static extern int VirtualQueryEx( IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);
我们下面逐个分析参数:
hProcess:顾名思义,进程句柄,也就是说想要查询地址存放的信息,首先得获得进程的句柄。
lpAddress:查询的内存地址,输入参数,需要主动提供地址。但我们并不知道我们需要的数值它被存放的地址,我们只能一个页面一个页面的猜测,直到扫描到某个页面的某块内存里面存放的信息正是与我们需要的信息一致或是存在一定的函数关系。其次,对于同一个数值也许出现在多个地方,比如 在某个时间区间人物的攻击数值和防御数值等同都为1200,那么说明至少有两个地址存放了这个数据。我们需要把这些地址全都筛选出来。
lpBuffer:结构体指针,用于存放内存信息。
dwLength:上述结构体的大小。
返回值:函数写入lpBuffer的字节数,如果字节数等于结构体PMEMORY_BASIC_INFORMATION的大小,表示函数执行成功。
但此函数只负责获取内存信息,而查询内存信息中具体存放数值则用到另一函数ReadProcessMemory,来看一下函数原型:
/// <summary> /// 根据进程句柄读入该进程的某个内存空间 /// </summary> /// <param name="lpProcess">进程句柄</param> /// <param name="lpBaseAddress">内存读取的起始地址</param> /// <param name="lpBuffer">写入地址</param> /// <param name="nSize">写入字节数</param> /// <param name="BytesRead">实际传递的字节数</param> /// <returns>读取结果</returns> [DllImportAttribute("kernel32.dll", EntryPoint = "ReadProcessMemory")] public static extern bool ReadProcessMemory ( IntPtr lpProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, IntPtr BytesRead );
此函数将根据句柄读取该进程的某个内存空间,并将读取到的字节数写入到我们开辟的一块空间中,而此空间存放的正是我们苦苦追寻的“有意义的数值”。此函数的部分参数依赖于上一个函数VirtualQueryEx产生的结果。
根据上面的API,先来看怎么获取第一个参数:窗体句柄,同样的kernel32.dll提供了名为OpenProcess的函数用来打开一个已存在的进程对象,并返回进程的句柄。
函数原型:
/// <summary> /// 打开一个已存在的进程对象,并返回进程的句柄 /// </summary> /// <param name="iAccess">渴望得到的访问权限</param> /// <param name="Handle">是否继承句柄</param> /// <param name="ProcessID">进程PID</param> /// <returns>进程的句柄</returns> [DllImportAttribute("kernel32.dll", EntryPoint = "OpenProcess")] public static extern IntPtr OpenProcess ( int iAccess, bool Handle, int ProcessID );
dwDesiredAccess :渴望得到的访问权限,这里默认填写PROCESS_ALL_ACCESS | 0x1F0FFF 给予所有可能允许的权限。
bInheritHandle :是否继承句柄,FALSE即可。
dwProcessId :进程标示符PID。
对于进程PID各种编程语言有自己的获取方式,以C#语言为例(针对非多开客户端):
//根据进程名称获取PID public int GetPIDByPName(string ProcessName) { Process[] ArrayProcess = Process.GetProcessesByName(processName); foreach (Process pro in ArrayProcess) { return pro.Id; } return -1; }
我们获取到进程的PID以后,就可以调用OpenProcess获取窗体的句柄,然后利用函数VirtualQueryEx遍历内存查找地址信息,根据地址利用ReadProcessMemory查找具体存放的值,最后利用WriteProcessMemory把修改后的值写入该地址,这样就完成了一次数据的修改。来看一下API的函数原型:
/// <summary> /// 写入某一进程的内存区域 /// </summary> /// <param name="lpProcess">进程句柄</param> /// <param name="lpBaseAddress">写入的内存首地址</param> /// <param name="lpBuffer">写入数据的指针</param> /// <param name="nSize">写入字节数</param> /// <param name="BytesWrite">实际写入字节数的指针</param> /// <returns>大于0代表成功</returns> [DllImportAttribute("kernel32.dll", EntryPoint = "WriteProcessMemory")] public static extern bool WriteProcessMemory ( IntPtr lpProcess, IntPtr lpBaseAddress, int [] lpBuffer, int nSize, IntPtr BytesWrite );
参数就不再一一解释了,注释都有,最后一个参数默认填写Null或者IntPtr.ZeroI即可。
到了这里,修改游戏数值的原理和套路已经很明白了,甚至脱离游戏来讲,任何的应用如果没有对缓存中的数据进行良好的加密,都是存在很大的风险隐患的,这一章节主要了解一些常用到的名词和API的运用。具体如何利用代码进行调用API,以及更详细的剖析每一步的逻辑,将在下一章节讲述。
PS:转载请附带原文路径:http://www.cnblogs.com/lene-y/p/7096485.html ,我已委托“维权骑士”为我的文章进行维权行动。
欢迎关注微信公众号[游戏外挂原理解析与制作],对本文有不理解的地方或者不同的观点可以给我留言,一定回复。
标签:通过 没有 自己的 mil 原型 页游 解析 原理 图标
原文地址:http://www.cnblogs.com/lene-y/p/7096485.html