游戏外挂基本原理及实现

游戏外挂基本原理及实现html

游戏外挂已经深深地影响着众多网络游戏玩家,今天在网上看到了一些关于游戏外挂编写的技术,因而转载上供你们参考c++

  一、游戏外挂的原理算法

  外挂如今分为好多种,好比模拟键盘的,鼠标的,修改数据包的,还有修改本地内存的,但好像没有修改服务器内存的哦,呵呵。其实修改服务器也是有办法的,只是技术过高通常人没有办法入手而已。(好比请GM去夜总会、送礼、收黑钱等等办法均可以修改服务器数据,哈哈)数据库

  修改游戏无非是修改一下本地内存的数据,或者截获API函数等等。这里我把所能想到的方法都做一个介绍,但愿你们能作出很好的外挂来使游戏厂商更好的完善本身的技术。我见到一篇文章是讲魔力宝贝的理论分析,写得不错,大概是那个样子。下来我就讲解一下技术方面的东西,以做引玉之用。编程

  2 技术分析部分小程序

  2.1 模拟键盘或鼠标的响应windows

  咱们通常使用:api

  UINT SendInput(
    UINT nInputs,   // count of input events
    LPINPUT pInputs, // array of input events
    int cbSize    // size of structure
  ); 安全

  API函数。第一个参数是说明第二个参数的矩阵的维数的,第二个参数包含了响应事件,这个本身填充就能够,最后是这个结构的大小,很是简单,这是最简单的方法模拟键盘鼠标了,呵呵。注意,这个函数还有个替代函数: 服务器

  VOID keybd_event(
    BYTE bVk,       // 虚拟键码
    BYTE bScan,      // 扫描码
    DWORD dwFlags,
    ULONG_PTR dwExtraInfo // 附加键状态
  );

  与

  VOID mouse_event(
    DWORD dwFlags,      // motion and click options
    DWORD dx,         // horizontal position or change
    DWORD dy,        // vertical position or change
    DWORD dwData,      // wheel movement
    ULONG_PTR dwExtraInfo  // application-defined information
  );

  这两个函数很是简单了,我想那些按键精灵就是用的这个吧。上面的是模拟键盘,下面的是模拟鼠标的。这个仅仅是模拟部分,要和游戏联系起来咱们还须要找到游戏的窗口才行,或者包含快捷键,就象按键精灵的那个激活键同样,咱们能够用GetWindow函数来枚举窗口,也能够用Findwindow函数来查找制定的窗口(注意,还有一个FindWindowEx),FindwindowEx能够找到窗口的子窗口,好比按钮,等什么东西。当游戏切换场景的时候咱们能够用FindWindowEx来肯定一些当前窗口的特征,从而判断是否还在这个场景,方法不少了,好比能够GetWindowInfo来肯定一些东西,好比当查找不到某个按钮的时候就说明游戏场景已经切换了,等等办法。有的游戏没有控件在里面,这是对图像作坐标变换的话,这种方法就要受到限制了。这就须要咱们用别的办法来辅助分析了。

  至于快捷键咱们要用动态链接库实现了,里面要用到hook技术了,这个也很是简单。你们可能都会了,其实就是一个全局的hook对象而后SetWindowHook就能够了,回调函数都是现成的,并且如今网上的例子多如牛毛。这个实如今外挂中已经很广泛了。若是还有谁不明白,那就去看看MSDN查找SetWindowHook就能够了。

  不要低估了这个动态链接库的做用,它能够切入全部的进程空间,也就是能够加载到全部的游戏里面哦,只要用对,你会发现颇有用途的。这个须要你复习一下Win32编程的基础知识了。呵呵,赶快去看书吧。

  2.2 截获消息

  有些游戏的响应机制比较简单,是基于消息的,或者用什么定时器的东西。这个时候你就能够用拦截消息来实现一些有趣的功能了。

  咱们拦截消息使用的也是hook技术,里面包括了键盘消息,鼠标消息,系统消息,日志等,别的对咱们没有什么大的用处,咱们只用拦截消息的回调函数就能够了,这个不会让我写例子吧。其实这个和上面的同样,都是用SetWindowHook来写的,看看就明白了很简单的。

  至于拦截了之后作什么就是你的事情了,好比在每一个定时器消息里面处理一些咱们的数据判断,或者在定时器里面在模拟一次定时器,那么有些数据就会处理两次,呵呵。后果嘛,不必定是好事情哦,呵呵,不过若是数据计算放在客户端的游戏就能够真的改变数据了,呵呵,试试看吧。用途还有不少,本身想也能够想出来的,呵呵。

  2.3 拦截Socket包

  这个技术难度要比原来的高不少。

  首先咱们要替换WinSock.DLL或者WinSock32.DLL,咱们写的替换函数要和原来的函数一致才行,就是说它的函数输出什么样的,咱们也要输出什么样子的函数,并且参数,参数顺序都要同样才行,而后在咱们的函数里面调用真正的WinSock32.DLL里面的函数就能够了。

  首先:咱们能够替换动态库到系统路径。

  其次:咱们应用程序启动的时候能够加载原有的动态库,用这个函数LoadLibary而后定位函数入口用GetProcAddress函数得到每一个真正Socket函数的入口地址。

  当游戏进行的时候它会调用咱们的动态库,而后从咱们的动态库中处理完毕后才跳转到真正动态库的函数地址,这样咱们就能够在里面处理本身的数据了,应该是一切数据。呵呵,兴奋吧,拦截了数据包咱们还要分析以后才能进行正确的应答,不要觉得这样工做就完成了,还早呢。等分析完毕之后咱们还要仿真应答机制来和服务器通讯,一个不当心就会被封号。

  分析数据才是工做量的来源呢,游戏每次升级有可能加密方式会有所改变,所以咱们写外挂的人都是亡命之徒啊,被人愚弄了还不知道。

  2.4 截获API

  上面的技术若是能够灵活运用的话咱们就不用截获API函数了,其实这种技术是一种补充技术。好比咱们须要截获Socket之外的函数做为咱们的用途,咱们就要用这个技术了,其实咱们也能够用它直接拦截在Socket中的函数,这样更直接。

  如今拦截API的教程处处都是,我就不列举了,我用的比较习惯的方法是根据输入节进行拦截的,这个方法能够用到任何一种操做系统上,好比Windows 98/2000等,有些方法不是跨平台的,我不建议使用。这个技术你们能够参考《Windows核心编程》里面的545页开始的内容来学习,若是是Win98系统能够用“Windows系统奥秘”那个最后一章来学习。


网络游戏外挂编写基础①

要想在修改游戏中作到百战百胜,是须要至关丰富的计算机知识的。有不少计算机高手就是从玩游戏,修改游戏中,逐步对计算机产生浓厚的兴趣,逐步成长起来的。不要在羡慕别人可以作到的,由于别人可以作的你也可以!我相信大家看了本教程后,会对游戏有一个全新的认识,呵呵,由于我是个好老师!(别拿鸡蛋砸我呀,救命啊!#¥%……*)   不过要想从修改游戏中学到知识,增长本身的计算机水平,可不能只是靠修改游戏呀! 要知道,修改游戏只是一个验证你对你所了解的某些计算机知识的理解程度的场所,只能给你一些发现问题、解决问题的机会,只能起到帮助你提升学习计算机的兴趣的做用,而决不是学习计算机的捷径。

  一:什么叫外挂?

  如今的网络游戏可能是基于Internet上客户/服务器模式,服务端程序运行在游戏服务器上,游戏的设计者在其中创造一个庞大的游戏空间,各地的玩家能够经过运行客户端程序同时登陆到游戏中。简单地说,网络游戏实际上就是由游戏开发商提供一个游戏环境,而玩家们就是在这个环境中相对自由和开放地进行游戏操做。那么既然在网络游戏中有了服务器这个概念,咱们之前传统的修改游戏方法就显得无能为力了。记得咱们在单机版的游戏中,为所欲为地经过内存搜索来修改角色的各类属性,这在网络游戏中就没有任何用处了。由于咱们在网络游戏中所扮演角色的各类属性及各类重要资料都存放在服务器上,在咱们本身机器上(客户端)只是显示角色的状态,因此经过修改客户端内存里有关角色的各类属性是不切实际的。那么是否咱们就没有办法在网络游戏中达到咱们修改的目的?回答是"否"。

  咱们知道Internet客户/服务器模式的通信通常采用TCP/IP通讯协议,数据交换是经过IP数据包的传输来实现的,通常来讲咱们客户端向服务器发出某些请求,好比移动、战斗等指令都是经过封包的形式和服务器交换数据。那么咱们把本地发出消息称为SEND,意思就是发送数据,服务器收到咱们SEND的消息后,会按照既定的程序把有关的信息反馈给客户端,好比,移动的坐标,战斗的类型。那么咱们把客户端收到服务器发来的有关消息称为RECV。知道了这个道理,接下来咱们要作的工做就是分析客户端和服务器之间往来的数据(也就是封包),这样咱们就能够提取到对咱们有用的数据进行修改,而后模拟服务器发给客户端,或者模拟客户端发送给服务器,这样就能够实现咱们修改游戏的目的了。

  目前除了修改游戏封包来实现修改游戏的目的,咱们也能够修改客户端的有关程序来达到咱们的要求。咱们知道目前各个服务器的运算能力是有限的,特别在游戏中,游戏服务器要计算游戏中全部玩家的情况几乎是不可能的,因此有一些运算仍是要依靠咱们客户端来完成,这样又给了咱们修改游戏提供了一些便利。好比咱们能够经过将客户端程序脱壳来发现一些程序的判断分支,经过跟踪调试咱们能够把一些对咱们不利的判断去掉,以此来知足咱们修改游戏的需求。 在下几个章节中,咱们将给你们讲述封包的概念,和修改跟踪客户端的有关知识。你们准备好了吗?

  游戏数据格式和存储:

  在进行咱们的工做以前,咱们须要掌握一些关于计算机中储存数据方式的知识和游戏中储存数据的特色。本章节是提供给菜鸟级的玩家看的,若是你是高手就能够跳过了,若是,你想成为无坚不摧的剑客,那么,这些东西就会花掉你一些时间;若是,你只想做个江湖的游客的话,那么这些东西,了解与否可有可无。是做剑客,仍是做游客,你选择吧!

  如今咱们开始!首先,你要知道游戏中储存数据的几种格式,这几种格式是:字节(BYTE)、字(WORD)和双字(DOUBLE WORD),或者说是8位、16位和32位储存方式。字节也就是8位方式能储存0~255的数字;字或说是16位储存方式能储存0~65535的数;双字即32位方式能储存0~4294967295的数。

  为什么要了解这些知识呢?在游戏中各类参数的最大值是不一样的,有些可能100左右就够了,好比,金庸群侠传中的角色的等级、随机遇敌个数等等。而有些却须要大于255甚至大于65535,象金庸群侠传中角色的金钱值可达到数百万。因此,在游戏中各类不一样的数据的类型是不同的。在咱们修改游戏时须要寻找准备修改的数据的封包,在这种时候,正确判断数据的类型是迅速找到正确地址的重要条件。

  在计算机中数据以字节为基本的储存单位,每一个字节被赋予一个编号,以肯定各自的位置。这个编号咱们就称为地址。

  在须要用到字或双字时,计算机用连续的两个字节来组成一个字,连续的两个字组成一个双字。而一个字或双字的地址就是它们的低位字节的地址。 如今咱们经常使用的Windows 9x操做系统中,地址是用一个32位的二进制数表示的。而在平时咱们用到内存地址时,老是用一个8位的16进制数来表示它。

  二进制和十六进制又是怎样一回事呢?

  简单说来,二进制数就是一种只有0和1两个数码,每满2则进一位的计数进位法。一样,16进制就是每满十六就进一位的计数进位法。16进制有0--F十六个数字,它为表示十到十五的数字采用了A、B、C、D、E、F六个数字,它们和十进制的对应关系是:A对应于10,B对应于11,C对应于12,D对应于13,E对应于14,F对应于15。并且,16进制数和二进制数间有一个简单的对应关系,那就是;四位二进制数至关于一位16进制数。好比,一个四位的二进制数1111就至关于16进制的F,1010就至关于A。

  了解这些基础知识对修改游戏有着很大的帮助,下面我就要谈到这个问题。因为在计算机中数据是以二进制的方式储存的,同时16进制数和二进制间的转换关系十分简单,因此大部分的修改工具在显示计算机中的数据时会显示16进制的代码,并且在你修改时也须要输入16进制的数字。你清楚了吧?

  在游戏中看到的数据可都是十进制的,在要寻找并修改参数的值时,能够使用Windows提供的计算器来进行十进制和16进制的换算,咱们能够在开始菜单里的程序组中的附件中找到它。

  如今要了解的知识也差很少了!不过,有个问题在游戏修改中是须要注意的。在计算机中数据的储存方式通常是低位数储存在低位字节,高位数储存在高位字节。好比,十进制数41715转换为16进制的数为A2F3,但在计算机中这个数被存为F3A2。

  看了以上内容你们对数据的存贮和数据的对应关系都了解了吗? 好了,接下来咱们要告诉你们在游戏中,封包究竟是怎么一回事了,来!你们把袖口卷起来,让咱们来干活吧!
  二:什么是封包?

  怎么截获一个游戏的封包?怎么去检查游戏服务器的ip地址和端口号? Internet用户使用的各类信息服务,其通信的信息最终都可以归结为以IP包为单位的信息传送,IP包除了包括要传送的数据信息外,还包含有信息要发送到的目的IP地址、信息发送的源IP地址、以及一些相关的控制信息。当一台路由器收到一个IP数据包时,它将根据数据包中的目的IP地址项查找路由表,根据查找的结果将此IP数据包送往对应端口。下一台IP路由器收到此数据包后继续转发,直至发到目的地。路由器之间能够经过路由协议来进行路由信息的交换,从而更新路由表。

  那么咱们所关心的内容只是IP包中的数据信息,咱们能够使用许多监听网络的工具来截获客户端与服务器之间的交换数据,下面就向你介绍其中的一种工具:WPE。

  WPE使用方法:执行WPE会有下列几项功能可选择:

  SELECT GAME选择目前在记忆体中您想拦截的程式,您只需双击该程式名称便可。

  TRACE追踪功能。用来追踪撷取程式送收的封包。WPE必须先完成点选欲追踪的程式名称,才能够使用此项目。 按下Play键开始撷取程式收送的封包。您能够随时按下 | | 暂停追踪,想继续时请再按下 | | 。按下正方形能够中止撷取封包而且显示全部已撷取封包内容。若您没按下正方形中止键,追踪的动做将依照OPTION里的设定值自动中止。若是您没有撷取到资料,试试将OPTION里调整为Winsock Version 2。WPE 及 Trainers 是设定在显示至少16 bits 颜色下才可执行。

  FILTER过滤功能。用来分析所撷取到的封包,而且予以修改。

  SEND PACKET送出封包功能。可以让您送出假造的封包。

  TRAINER MAKER制做修改器。

  OPTIONS设定功能。让您调整WPE的一些设定值。

  FILTER的详细教学

  - 当FILTER在启动状态时 ,ON的按钮会呈现红色。- 当您启动FILTER时,您随时能够关闭这个视窗。FILTER将会保留在原来的状态,直到您再按一次 on / off 钮。- 只有FILTER启用钮在OFF的状态下,才能够勾选Filter前的方框来编辑修改。- 当您想编辑某个Filter,只要双击该Filter的名字便可。

  NORMAL MODE:

  范例:

  当您在 Street Fighter Online ﹝快打旋风线上版?#123;游戏中,您使用了两次火球并且击中了对方,这时您会撷取到如下的封包:SEND-> 0000 08 14 21 06 01 04 SEND-> 0000 02 09 87 00 67 FF A4 AA 11 22 00 00 00 00 SEND-> 0000 03 84 11 09 11 09 SEND-> 0000 0A 09 C1 10 00 00 FF 52 44 SEND-> 0000 0A 09 C1 10 00 00 66 52 44

  您的第一个火球让对方减了16滴﹝16 = 10h?#123;的生命值,而您观察到第4跟第5个封包的位置4有10h的值出现,应该就是这里了。

  您观察10h前的0A 09 C1在两个封包中都没改变,可见得这3个数值是发出火球的关键。

  所以您将0A 09 C1 10填在搜寻列﹝SEARCH?#123;,而后在修改列﹝MODIFY?#123;的位置4填上FF。如此一来,当您再度发出火球时,FF会取代以前的10,也就是攻击力为255的火球了!

  ADVANCED MODE:

  范例: 当您在一个游戏中,您不想要用真实姓名,您想用修改过的假名传送给对方。在您使用TRACE后,您会发现有些封包里面有您的名字出现。假设您的名字是Shadow,换算成16进位则是﹝53 68 61 64 6F 77?#123;;而您打算用moon﹝6D 6F 6F 6E 20 20?#123;来取代他。1) SEND-> 0000 08 14 21 06 01 042) SEND-> 0000 01 06 99 53 68 61 64 6F 77 00 01 05 3) SEND-> 0000 03 84 11 09 11 094) SEND-> 0000 0A 09 C1 10 00 53 68 61 64 6F 77 00 11 5) SEND-> 0000 0A 09 C1 10 00 00 66 52 44

  可是您仔细看,您的名字在每一个封包中并非出如今相同的位置上

  - 在第2个封包里,名字是出如今第4个位置上- 在第4个封包里,名字是出如今第6个位置上

  在这种状况下,您就须要使用ADVANCED MODE- 您在搜寻些zSEARCH?#123;填上:53 68 61 64 6F 77 ﹝请务必从位置1开始填?#123;- 您想要从原来名字Shadow的第一个字母开始置换新名字,所以您要选择从数值被发现的位置开始替代连续数值﹝from the position of the chain found?#123;。- 如今,在修改列﹝MODIFY?#123;000的位置填上:6D 6F 6F 6E 20 20 ﹝此为相对应位置,也就是从原来搜寻栏的+001位置开始递换?#123;- 若是您想从封包的第一个位置就修改数值,请选择﹝from the beginning of the packet?#123;

  了解一点TCP/IP协议常识的人都知道,互联网是将信息数据打包以后再传送出去的。每一个数据包分为头部信息和数据信息两部分。头部信息包括数据包的发送地址和到达地址等。数据信息包括咱们在游戏中相关操做的各项信息。那么在作截获封包的过程以前咱们先要知道游戏服务器的IP地址和端口号等各类信息,实际上最简单的是看看咱们游戏目录下,是否有一个SERVER.INI的配置文件,这个文件里你能够查看到个游戏服务器的IP地址,好比金庸群侠传就是如此,那么除了这个咱们还能够在DOS下使用NETSTAT这个命令,

  NETSTAT命令的功能是显示网络链接、路由表和网络接口信息,可让用户得知目前都有哪些网络链接正在运做。或者你能够使用木马客星等工具来查看网络链接。工具是不少的,看你喜欢用哪种了。

  NETSTAT命令的通常格式为:NETSTAT [选项]

  命令中各选项的含义以下:-a 显示全部socket,包括正在监听的。-c 每隔1秒就从新显示一遍,直到用户中断它。-i 显示全部网络接口的信息。-n 以网络IP地址代替名称,显示出网络链接情形。-r 显示核心路由表,格式同"route -e"。-t 显示TCP协议的链接状况。-u 显示UDP协议的链接状况。-v 显示正在进行的工做。

 


网络游戏外挂编写基础②

三:怎么来分析咱们截获的封包?

  首先咱们将WPE截获的封包保存为文本文件,而后打开它,这时会看到以下的数据(这里咱们以金庸群侠传里PK店小二客户端发送的数据为例来说解):

  第一个文件:SEND-> 0000 E6 56 0D 22 7E 6B E4 17 13 13 12 13 12 13 67 1BSEND-> 0010 17 12 DD 34 12 12 12 12 17 12 0E 12 12 12 9BSEND-> 0000 E6 56 1E F1 29 06 17 12 3B 0E 17 1ASEND-> 0000 E6 56 1B C0 68 12 12 12 5ASEND-> 0000 E6 56 02 C8 13 C9 7E 6B E4 17 10 35 27 13 12 12SEND-> 0000 E6 56 17 C9 12

  第二个文件:SEND-> 0000 83 33 68 47 1B 0E 81 72 76 76 77 76 77 76 02 7ESEND-> 0010 72 77 07 1C 77 77 77 77 72 77 72 77 77 77 6DSEND-> 0000 83 33 7B 94 4C 63 72 77 5E 6B 72 F3SEND-> 0000 83 33 7E A5 21 77 77 77 3FSEND-> 0000 83 33 67 AD 76 CF 1B 0E 81 72 75 50 42 76 77 77SEND-> 0000 83 33 72 AC 77

  咱们发现两次PK店小二的数据格式同样,可是内容却不相同,咱们是PK的同一个NPC,为何会不一样呢? 原来金庸群侠传的封包是通过了加密运算才在网路上传输的,那么咱们面临的问题就是如何将密文解密成明文再分析了。

  由于通常的数据包加密都是异或运算,因此这里先讲一下什么是异或。 简单的说,异或就是"相同为0,不一样为1"(这是针对二进制按位来说的),举个例子,0001和0010异或,咱们按位对比,获得异或结果是0011,计算的方法是:0001的第4位为0,0010的第4位为0,它们相同,则异或结果的第4位按照"相同为0,不一样为1"的原则获得0,0001的第3位为0,0010的第3位为0,则异或结果的第3位获得0,0001的第2位为0,0010的第2位为1,则异或结果的第2位获得1,0001的第1位为1,0010的第1位为0,则异或结果的第1位获得1,组合起来就是0011。异或运算从此会遇到不少,你们能够先熟悉熟悉,熟练了对分析颇有帮助的。

  下面咱们继续看看上面的两个文件,按照常理,数据包的数据不会所有都有值的,游戏开发时会预留一些字节空间来便于往后的扩充,也就是说数据包里会存在一些"00"的字节,观察上面的文件,咱们会发现文件一里不少"12",文件二里不少"77",那么这是否是表明咱们说的"00"呢?推理到这里,咱们就开始行动吧!

  咱们把文件一与"12"异或,文件二与"77"异或,固然用手算很费事,咱们使用"M2M 1.0 加密封包分析工具"来计算就方便多了。获得下面的结果:

  第一个文件:1 SEND-> 0000 F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09SEND-> 0010 05 00 CF 26 00 00 00 00 05 00 1C 00 00 00 892 SEND-> 0000 F4 44 0C E3 3B 13 05 00 29 1C 05 083 SEND-> 0000 F4 44 09 D2 7A 00 00 00 484 SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 005 SEND-> 0000 F4 44 05 DB 00

  第二个文件:1 SEND-> 0000 F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09SEND-> 0010 05 00 70 6B 00 00 00 00 05 00 05 00 00 00 1A2 SEND-> 0000 F4 44 0C E3 3B 13 05 00 29 1C 05 843 SEND-> 0000 F4 44 09 D2 56 00 00 00 484 SEND-> 0000 F4 44 10 DA 01 B8 6C 79 F6 05 02 27 35 01 00 005 SEND-> 0000 F4 44 05 DB 00

  哈,这一下两个文件大部分都同样啦,说明咱们的推理是正确的,上面就是咱们须要的明文!

  接下来就是搞清楚一些关键的字节所表明的含义,这就须要截获大量的数据来分析。

  首先咱们会发现每一个数据包都是"F4 44"开头,第3个字节是变化的,可是变化颇有规律。咱们来看看各个包的长度,发现什么没有?对了,第3个字节就是包的长度! 经过截获大量的数据包,咱们判断第4个字节表明指令,也就是说客户端告诉服务器进行的是什么操做。例如向服务器请求战斗指令为"30",战斗中移动指令为"D4"等。 接下来,咱们就须要分析一下上面第一个包"F4 44 1F 30 6C 79 F6 05 01 01 00 01 00 01 75 09 05 00 CF 26 00 00 00 00 05 00 1C 00 00 00 89",在这个包里包含什么信息呢?应该有通知服务器你PK的哪一个NPC吧,咱们就先来找找这个店小二的代码在什么地方。 咱们再PK一个小喽罗(就是大理客栈外的那个咯):SEND-> 0000 F4 44 1F 30 D4 75 F6 05 01 01 00 01 00 01 75 09SEND-> 0010 05 00 8A 19 00 00 00 00 11 00 02 00 00 00 C0 咱们根据常理分析,游戏里的NPC种类虽然不会超过65535(FFFF),但开发时不会把本身限制在字的范围,那样不利于游戏的扩充,因此咱们在双字里看看。经过"店小二"和"小喽罗"两个包的对比,咱们把目标放在"6C 79 F6 05"和"CF 26 00 00"上。(对比一下很容易的,但你不能太迟钝咯,呵呵)咱们再看看后面的包,在后面的包里应该还会出现NPC的代码,好比移动的包,游戏容许观战,服务器必然须要知道NPC的移动坐标,再广播给观战的其余玩家。在后面第4个包"SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00"里咱们又看到了"6C 79 F6 05",初步判定店小二的代码就是它了!(这分析里边包含了不少工做的,你们能够用WPE截下数据来本身分析分析)

  第一个包的分析暂时就到这里(里面还有的信息咱们暂时不须要彻底清楚了)

  咱们看看第4个包"SEND-> 0000 F4 44 10 DA 01 DB 6C 79 F6 05 02 27 35 01 00 00",再截获PK黄狗的包,(狗会出来2只哦)看看包的格式:SEND-> 0000 F4 44 1A DA 02 0B 4B 7D F6 05 02 27 35 01 00 00SEND-> 0010 EB 03 F8 05 02 27 36 01 00 00

  根据上面的分析,黄狗的代码为"4B 7D F6 05"(100040011),不过两只黄狗服务器怎样分辨呢?看看"EB 03 F8 05"(100140011),是上一个代码加上100000,呵呵,这样服务器就能够认出两只黄狗了。咱们再经过野外遇敌截获的数据包来证明,果真如此。

  那么,这个包的格式应该比较清楚了:第3个字节为包的长度,"DA"为指令,第5个字节为NPC个数,从第7个字节开始的10个字节表明一个NPC的信息,多一个NPC就多10个字节来表示。

  你们若是玩过网金,必然知道随机遇敌有时会出现增援,咱们就利用游戏这个增援来让每次战斗都会出现增援的NPC吧。

  经过在战斗中出现增援截获的数据包,咱们会发现服务器端发送了这样一个包:F4 44 12 E9 EB 03 F8 05 02 00 00 03 00 00 00 00 00 00 第5-第8个字节为增援NPC的代码(这里咱们就简单的以黄狗的代码来举例)。 那么,咱们就利用单机代理技术来同时欺骗客户端和服务器吧!

  好了,呼叫NPC的工做到这里算是完成了一小半,接下来的事情,怎样修改封包和发送封包,咱们下节继续讲解吧。
  四:怎么冒充"客户端"向"服务器"发咱们须要的封包?

  这里咱们须要使用一个工具,它位于客户端和服务器端之间,它的工做就是进行数据包的接收和转发,这个工具咱们称为代理。若是代理的工做单纯就是接收和转发的话,这就毫无心义了,可是请注意:全部的数据包都要经过它来传输,这里的意义就重大了。咱们能够分析接收到的数据包,或者直接转发,或者修改后转发,或者压住不转发,甚至伪造咱们须要的封包来发送。

  下面咱们继续讲怎样来同时欺骗服务器和客户端,也就是修改封包和伪造封包。 经过咱们上节的分析,咱们已经知道了打多个NPC的封包格式,那么咱们就动手吧!

  首先咱们要查找客户端发送的包,找到战斗的特征,就是请求战斗的第1个包,咱们找"F4 44 1F 30"这个特征,这是不会改变的,固然是要解密后来查找哦。 找到后,表示客户端在向服务器请求战斗,咱们不动这个包,转发。 继续向下查找,这时须要查找的特征码不太好办,咱们先查找"DA",这是客户端发送NPC信息的数据包的指令,那么可能其余包也有"DA",不要紧,咱们看前3个字节有没有"F4 44"就好了。找到后,咱们的工做就开始了!

  咱们肯定要打的NPC数量。这个数量不能很大,缘由在于网金的封包长度用一个字节表示,那么一个包能够有255个字节,咱们上面分析过,增长一个NPC要增长10个字节,因此你们算算就知道,打20个NPC比较合适。

  而后咱们要把客户端原来的NPC代码分析计算出来,由于增长的NPC代码要加上100000哦。再把咱们增长的NPC代码计算出来,而且组合成新的封包,注意表明包长度的字节要修改啊,而后转发到服务器,这一步在编写程序的时候要注意算法,不要形成较大延迟。

  上面咱们欺骗服务器端完成了,欺骗客户端就简单了。

  发送了上面的封包后,咱们根据新增NPC代码构造封包立刻发给客户端,格式就是"F4 44 12 E9 NPC代码 02 00 00 03 00 00 00 00 00 00",把每一个新增的NPC都构造这样一个包,按顺序连在一块儿发送给客户端,客户端也就被咱们骗过了,很简单吧。

  之后战斗中其余的事咱们就无论了,尽情地开打吧。

 

网络游戏通信模型初探①
序言

  网络游戏,做为游戏与网络有机结合的产物,把玩家带入了新的娱乐领域。网络游戏在中国开始发展至今也仅有3,4年的历史,跟已经拥有几十年开发历史的单机游戏相比,网络游戏仍是很是年轻的。固然,它的造成也是根据历史变化而产生的能够说没有互联网的兴起,也就没有网络游戏的诞生。做为新兴产物,网络游戏的开发对广大开发者来讲更加神秘,对于一个未知领域,开发者可能更须要了解的是网络游戏与普通单机游戏有何区别,网络游戏如何将玩家们链接起来,以及如何为玩家提供一个互动的娱乐环境。本文就将围绕这三个主题来给你们讲述一下网络游戏的网络互连实现方法。
 网络游戏与单机游戏

  说到网络游戏,不得不让人联想到单机游戏,实际上网络游戏的实质脱离不了单机游戏的制做思想,网络游戏和单机游戏的差异你们能够很直接的想到:不就是能够多人连线吗?没错,但如何实现这些功能,如何把网络连线合理的融合进单机游戏,就是咱们下面要讨论的内容。在了解网络互连具体实现以前,咱们先来了解一下单机与网络游戏它们各自的运行流程,只有了解这些,你才能深刻网络游戏开发的核心。


如今先让咱们来看一下普通单机游戏的简化执行流程:

Initialize() // 初始化模块
{
 初始化游戏数据;
}
Game() // 游戏循环部分
{
 绘制游戏场景、人物以及其它元素;
 获取用户操做输入;
 switch( 用户输入数据)
 {
  case 移动:
  {
   处理人物移动;
  }
  break;
  case 攻击:
  {
   处理攻击逻辑:
  }
  break;
  ...
  其它处理响应;
  ...
  default:
   break;
 }
 游戏的NPC等逻辑AI处理;
}
Exit() // 游戏结束
{
 释放游戏数据;
 离开游戏;
}


  咱们来讲明一下上面单机游戏的流程。首先,无论是游戏软件仍是其余应用软件,初始化部分必不可少,这里须要对游戏的数据进行初始化,包括图像、声音以及一些必备的数据。接下来,咱们的游戏对场景、人物以及其余元素进行循环绘制,把游戏世界展示给玩家,同时接收玩家的输入操做,并根据操做来作出响应,此外,游戏还须要对NPC以及一些逻辑AI进行处理。最后,游戏数据被释放,游戏结束。
  网络游戏与单机游戏有一个很显著的差异,就是网络游戏除了一个供操做游戏的用户界面平台(如单机游戏)外,还须要一个用于链接全部用户,并为全部用户提供数据服务的服务器,从某些角度来看,游戏服务器就像一个大型的数据库,提供数据以及数据逻辑交互的功能。让咱们来看看一个简单的网络游戏模型执行流程:

 

 客户机:

Login()// 登入模块
{
 初始化游戏数据;
 获取用户输入的用户和密码;
 与服务器建立网络链接;
 发送至服务器进行用户验证;
 ...
 等待服务器确认消息;
 ...
 得到服务器反馈的登入消息;
 if( 成立 )
  进入游戏;
 else
  提示用户登入错误并从新接受用户登入;
}
Game()// 游戏循环部分
{
 绘制游戏场景、人物以及其它元素;
 获取用户操做输入;
 将用户的操做发送至服务器;
 ...
 等待服务器的消息;
 ...
 接收服务器的反馈信息;
 switch( 服务器反馈的消息数据 )
 {
  case 本地玩家移动的消息:
  {
   if( 容许本地玩家移动 )
    客户机处理人物移动;
   else
    客户机保持原有状态;
  }
   break;
  case 其余玩家/NPC的移动消息:
  {
   根据服务器的反馈信息进行其余玩家或者NPC的移动处理;
  }
  break;
  case 新玩家加入游戏:
  {
   在客户机中添加显示此玩家;
  }
   break;
  case 玩家离开游戏:
  {
   在客户机中销毁此玩家数据;
  }
   break;
  ...
  其它消息类型处理;
  ... 
  default:
   break;
 }
}
Exit()// 游戏结束
{
 发送离开消息给服务器;
 ...
 等待服务器确认;
 ...
 获得服务器确认消息;
 与服务器断开链接;
 释放游戏数据;
 离开游戏;
}


  服务器:

Listen()  // 游戏服务器等待玩家链接模块
{
 ...
 等待用户的登入信息;
 ...
 接收到用户登入信息;
 分析用户名和密码是否符合;
 if( 符合 )
 {
  发送确认容许进入游戏消息给客户机; 
  把此玩家进入游戏的消息发布给场景中全部玩家;
  把此玩家添加到服务器场景中;
 }
 else
 {
  断开与客户机的链接;
 }
}
Game() // 游戏服务器循环部分
{
 ...
 等待场景中玩家的操做输入;
 ...
 接收到某玩家的移动输入或NPC的移动逻辑输入;
 // 此处只以移动为例
 进行此玩家/NPC在地图场景是否可移动的逻辑判断;

 if( 可移动 )
 {
  对此玩家/NPC进行服务器移动处理;
  发送移动消息给客户机;
  发送此玩家的移动消息给场景上全部玩家;
 }
 else
  发送不可移动消息给客户机;
}
Exit()  // 游戏服务=器结束
{
 接收到玩家离开消息;
 将此消息发送给场景中全部玩家;
 发送容许离开的信息;
 将玩家数据存入数据库;
 注销此玩家在服务器内存中的数据;
}
}


  让咱们来讲明一下上面简单网络游戏模型的运行机制。先来说讲服务器端,这里服务器端分为三个部分(实际上一个完整的网络游戏远不止这些):登入模块、游戏模块和登出模块。登入模块用于监听网络游戏客户端发送过来的网络链接消息,而且验证其合法性,而后在服务器中建立这个玩家而且把玩家带领到游戏模块中; 游戏模块则提供给玩家用户实际的应用服务,咱们在后面会详细介绍这个部分; 在获得玩家要离开游戏的消息后,登出模块则会把玩家从服务器中删除,而且把玩家的属性数据保存到服务器数据库中,如: 经验值、等级、生命值等。

  接下来让咱们看看网络游戏的客户端。这时候,客户端再也不像单机游戏同样,初始化数据后直接进入游戏,而是在与服务器建立链接,而且得到许可的前提下才进入游戏。除此以外,网络游戏的客户端游戏进程须要不断与服务器进行通信,经过与服务器交换数据来肯定当前游戏的状态,例如其余玩家的位置变化、物品掉落状况。一样,在离开游戏时,客户端会向服务器告知此玩家用户离开,以便于服务器作出相应处理。


以上用简单的伪代码给你们阐述了单机游戏与网络游戏的执行流程,你们应该能够清楚看出二者的差异,以及二者间相互的关系。咱们能够换个角度考虑,网络游戏就是把单机游戏的逻辑运算部分搬移到游戏服务器中进行处理,而后把处理结果(包括其余玩家数据)经过游戏服务器返回给链接的玩家。
  网络互连

  在了解了网络游戏基本形态以后,让咱们进入真正的实际应用部分。首先,做为网络游戏,除了常规的单机游戏所必需的东西以外,咱们还须要增长一个网络通信模块,固然,这也是网络游戏较为主要的部分,咱们来讨论一下如何实现网络的通信模块。

  一个完善的网络通信模块涉及面至关广,本文仅对较为基本的处理方式进行讨论。网络游戏是由客户端和服务器组成,相应也须要两种不一样的网络通信处理方式,不过也有相同之处,咱们先就它们的共同点来进行介绍。咱们这里以Microsoft Windows 2000 [2000 Server]做为开发平台,而且使用Winsock做为网络接口(可能一些朋友会考虑使用DirectPlay来进行网络通信,不过对于当前在线游戏,DirectPlay并不适合,具体缘由这里就不作讨论了)。

 

  肯定好平台与接口后,咱们开始进行网络链接建立以前的一些必要的初始化工做,这部分不管是客户端或者服务器都须要进行。让咱们看看下面的代码片断:

WORD wVersionRequested;
WSADATAwsaData;
wVersionRequested MAKEWORD(1, 1);
if( WSAStartup( wVersionRequested, &wsaData ) !0 )
{
 Failed( WinSock Version Error!" );
}


  上面经过调用Windows的socket API函数来初始化网络设备,接下来进行网络Socket的建立,代码片断以下:

SOCKET sSocket socket( AF_INET, m_lProtocol, 0 );
if( sSocket == INVALID_SOCKET )
{
 Failed( "WinSocket Create Error!" );
}


  这里须要说明,客户端和服务端所须要的Socket链接数量是不一样的,客户端只须要一个Socket链接足以知足游戏的须要,而服务端必须为每一个玩家用户建立一个用于通信的Socket链接。固然,并非说若是服务器上没有玩家那就不须要建立Socket链接,服务器端在启动之时会生成一个特殊的Socket用来对玩家建立与服务器链接的请求进行响应,等介绍网络监听部分后会有更详细说明。

 

  有初始化与建立必然就有释放与删除,让咱们看看下面的释放部分:

if( sSocket != INVALID_SOCKET )
{
 closesocket( sSocket );
}
if( WSACleanup() != 0 )
{
 Warning( "Can't release Winsocket" );
}

 


  这里两个步骤分别对前面所做的建立初始化进行了相应释放。

  接下来看看服务器端的一个网络执行处理,这里咱们假设服务器端已经建立好一个Socket供使用,咱们要作的就是让这个Socket变成监听网络链接请求的专用接口,看看下面代码片断:

SOCKADDR_IN addr;
memset( &addr, 0, sizeof(addr) );
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl( INADDR_ANY );
addr.sin_port = htons( Port );  // Port为要监听的端口号
// 绑定socket
if( bind( sSocket, (SOCKADDR*)&addr, sizeof(addr) ) == SOCKET_ERROR )
{
 Failed( "WinSocket Bind Error!");
}
// 进行监听
if( listen( sSocket, SOMAXCONN ) == SOCKET_ERROR )
{
 Failed( "WinSocket Listen Error!");
}


  这里使用的是阻塞式通信处理,此时程序将处于等待玩家用户链接的状态,假若这时候有客户端链接进来,则经过accept()来建立针对此玩家用户的Socket链接,代码片断以下:

sockaddraddrServer;
int nLen sizeof( addrServer );
SOCKET sPlayerSocket accept( sSocket, &addrServer, &nLen );
if( sPlayerSocket == INVALID_SOCKET )
{
 Failed( "WinSocket Accept Error!");
}


  这里咱们建立了sPlayerSocket链接,此后游戏服务器与这个玩家用户的通信所有经过此Socket进行,到这里为止,咱们服务器已经有了接受玩家用户链接的功能,如今让咱们来看看游戏客户端是如何链接到游戏服务器上,代码片断以下:

SOCKADDR_IN addr;
memset( &addr, 0, sizeof(addr) );
addr.sin_family = AF_INET;// 要链接的游戏服务器端口号
addr.sin_addr.s_addr = inet_addr( IP );// 要链接的游戏服务器IP地址,
addr.sin_port = htons( Port );//到此,客户端和服务器已经有了通信的桥梁,
//接下来就是进行数据的发送和接收:
connect( sSocket, (SOCKADDR*)&addr, sizeof(addr) );
if( send( sSocket, pBuffer, lLength, 0 ) == SOCKET_ERROR )
{
 Failed( "WinSocket Send Error!");
}


  这里的pBuffer为要发送的数据缓冲指针,lLength为须要发送的数据长度,经过这支Socket API函数,咱们不管在客户端或者服务端均可以进行数据的发送工做,同时,咱们能够经过recv()这支Socket API函数来进行数据接收:

if( recv( sSocket, pBuffer, lLength, 0 ) == SOCKET_ERROR )
{
 Failed( "WinSocket Recv Error!");
}


  其中pBuffer用来存储获取的网络数据缓冲,lLength则为须要获取的数据长度。

  如今,咱们已经了解了一些网络互连的基本知识,但做为网络游戏,如此简单的链接方式是没法知足网络游戏中百人千人同时在线的,咱们须要更合理容错性更强的网络通信处理方式,固然,咱们须要先了解一下网络游戏对网络通信的需求是怎样的。

rning收集整理(请*勿删除)


网络游戏通信模型初探②

你们知道,游戏须要不断循环处理游戏中的逻辑并进行游戏世界的绘制,上面所介绍的Winsock处理方式均是以阻塞方式进行,这样就违背了游戏的执行本质,能够想象,在客户端链接到服务器的过程当中,你的游戏不能获得控制,这时若是玩家想取消链接或者作其余处理,甚至显示一个最基本的动态链接提示都不行。

  因此咱们须要用其余方式来处理网络通信,使其不会与游戏主线相冲突,可能你们都会想到: 建立一个网络线程来处理不就能够了?没错,咱们能够建立一个专门用于网络通信的子线程来解决这个问题。固然,咱们游戏中多了一个线程,咱们就须要作更多的考虑,让咱们来看看如何建立网络通信线程。

  在Windows系统中,咱们能够经过CreateThread()函数来进行线程的建立,看看下面的代码片断:

DWORD dwThreadID;
HANDLE hThread = CreateThread( NULL, 0, NetThread/*网络线程函式*/, sSocket, 0, &dwThreadID );
if( hThread == NULL )
{
 Failed( "WinSocket Thread Create Error!");
}


  这里咱们建立了一个线程,同时将咱们的Socket传入线程函数:

DWORD WINAPINetThread(LPVOID lParam)


{
 SOCKET sSocket (SOCKET)lParam;
 ...
 return 0;
}

 

  NetThread就是咱们未来用于处理网络通信的网络线程。那么,咱们又如何把Socket的处理引入线程中?

  看看下面的代码片断:

HANDLE hEvent;
hEvent = CreateEvent(NULL,0,0,0);
// 设置异步通信
if( WSAEventSelect( sSocket, hEvent,
FD_ACCEPT|FD_CONNECT|FD_READ|FD_WRITE|FD_CLOSE ) ==SOCKET_ERROR )
{
 Failed( "WinSocket EventSelect Error!");
}


  经过上面的设置以后,WinSock API函数均会以非阻塞方式运行,也就是函数执行后会当即返回,这时网络通信会以事件方式存储于hEvent,而不会停顿整支程式。

  完成了上面的步骤以后,咱们须要对事件进行响应与处理,让咱们看看如何在网络线程中得到网络通信所产生的事件消息:

WSAEnumNetworkEvents( sSocket, hEvent, &SocketEvents );
if( SocketEvents.lNetworkEvents != 0 )
{
 switch( SocketEvents.lNetworkEvents )
 {
  case FD_ACCEPT:
   WSANETWORKEVENTS SocketEvents;
   break;
  case FD_CONNECT:
  {
   if( SocketEvents.iErrorCode[FD_CONNECT_BIT] == 0)
   // 链接成功  
   {
   // 链接成功后通知主线程(游戏线程)进行处理
   }
  }
   break;
  case FD_READ:
  // 获取网络数据
  {
   if( recv( sSocket, pBuffer, lLength, 0) == SOCKET_ERROR )
   {
    Failed( "WinSocket Recv Error!");
   }
  }
   break;
  case FD_WRITE:
   break;
  case FD_CLOSE:
   // 通知主线程(游戏线程), 网络已经断开
   break;
  default:
   break;
 }
}


  这里仅对网络链接(FD_CONNECT) 和读取数据(FD_READ) 进行了简单模拟操做,但实际中网络线程接收到事件消息后,会对数据进行组织整理,而后再将数据回传给咱们的游戏主线程使用,游戏主线程再将处理过的数据发送出去,这样一个往返就构成了咱们网络游戏中的数据通信,是让网络游戏动起来的最基本要素。

  最后,咱们来谈谈关于网络数据包(数据封包)的组织,网络游戏的数据包是游戏数据通信的最基本单位,网络游戏通常不会用字节流的方式来进行数据传输,一个数据封包也能够看做是一条消息指令,在游戏进行中,服务器和客户端会不停的发送和接收这些消息包,而后将消息包解析转换为真正所要表达的指令意义并执行。
 互动与管理

  说到互动,对于玩家来讲是与其余玩家的交流,但对于计算机而言,实现互动也就是实现数据消息的相互传递。前面咱们已经了解过网络通信的基本概念,它构成了互动的最基本条件,接下来咱们须要在这个网络层面上进行数据的通信。遗憾的是,计算机并不懂得如何表达玩家之间的交流,所以咱们须要提供一套可以让计算机了解的指令组织和解析机制,也就是对咱们上面简单提到的网络数据包(数据封包)的处理机制。


为了可以更简单的给你们阐述网络数据包的组织形式,咱们以一个聊天处理模块来进行讨论,看看下面的代码结构:

struct tagMessage{
 long lType;
 long lPlayerID;
};
// 消息指令
// 指令相关的玩家标识
char strTalk[256]; // 消息内容


  上面是抽象出来的一个极为简单的消息包结构,咱们先来谈谈其各个数据域的用途:

  首先,lType 是消息指令的类型,这是最为基本的消息标识,这个标识用来告诉服务器或客户端这条指令的具体用途,以便于服务器或客户端作出相应处理。lPlayerID 被做为玩家的标识。你们知道,一个玩家在机器内部实际上也就是一堆数据,特别是在游戏服务器中,可能有成千上万个玩家,这时候咱们须要一个标记来区分玩家,这样就能够迅速找到特定玩家,并将通信数据应用于其上。

  strTalk 是咱们要传递的聊天数据,这部分才是真正的数据实体,前面的参数只是数据实体应用范围的限定。

  在组织完数据以后,紧接着就是把这个结构体数据经过Socket 链接发送出去和接收进来。这里咱们要了解,网络在进行数据传输过程当中,它并不关心数据采用的数据结构,这就须要咱们把数据结构转换为二进制数据码进行发送,在接收方,咱们再将这些二进制数据码转换回程序使用的相应数据结构。让咱们来看看如何实现:

tagMessageMsg;
Msg.lTypeMSG_CHAT;
Msg.lPlayerID 1000;
strcpy( &Msg.strTalk, "聊天信息" );


  首先,咱们假设已经组织好一个数据包,这里MSG_CHAT 是咱们自行定义的标识符,固然,这个标识符在服务器和客户端要统一。玩家的ID 则根据游戏须要来进行设置,这里1000 只做为假设,如今继续:

char* p = (char*)&Msg;
long lLength = sizeof( tagMessage );
send( sSocket, p, lLength );
// 获取数据结构的长度


  咱们经过强行转换把结构体转变为char 类型的数据指针,这样就能够经过这个指针来进行流式数据处理,这里经过sizeof() 得到结构体长度,而后用WinSock 的Send() 函数将数据发送出去。

  接下来看看如何接收数据:

long lLength = sizeof( tagMessage );
char* Buffer = new char[lLength];
recv( sSocket, Buffer, lLength );
tagMessage* p = (tagMessage*)Buffer;
// 获取数据


  在经过WinSock 的recv() 函数获取网络数据以后,咱们一样经过强行转换把获取出来的缓冲数据转换为相应结构体,这样就能够方便地对数据进行访问。(注:强行转换仅仅做为数据转换的一种手段,实际应用中有更多可选方式,这里只为简洁地说明逻辑)谈到此处,不得不提到服务器/ 客户端如何去筛选处理各类消息以及如何对通信数据包进行管理。不管是服务器仍是客户端,在收到网络消息的时候,经过上面的数据解析以后,还必须对消息类型进行一次筛选和派分,简单来讲就是相似Windows 的消息循环,不一样消息进行不一样处理。这能够经过一个switch 语句(熟悉Windows 消息循环的朋友相信已经明白此意),基于消
息封包里的lType 信息,对消息进行区分处理,考虑以下代码片断:

switch( p->lType ) // 这里的p->lType为咱们解析出来的消息类型标识
{
 case MSG_CHAT: // 聊天消息
  break;
 case MSG_MOVE: // 玩家移动消息
  break;
 case MSG_EXIT: // 玩家离开消息
  break;
 default:
  break;
}


  上面片断中的MSG_MOVE 和MSG_EXIT 都是咱们虚拟的消息标识(一个真实游戏中的标识可能会有上百个,这就须要考虑优化和优先消息处理问题)。此外,一个网络游戏服务器面对的是成百上千的链接用户,咱们还须要一些合理的数据组织管理方式来进行相关处理。普通的单体游戏服务器,可能会由于当机或者用户过多而致使整个游戏网络瘫痪,而这也就引入分组服务器机制,咱们把服务器分开进行数据的分布式处理。

  咱们把每一个模块提取出来,作成专用的服务器系统,而后创建一个链接全部服务器的数据中心来进行数据交互,这里每一个模块均与数据中心建立了链接,保证了每一个模块的相关性,同时玩家转变为与当前提供服务的服务器进行链接通信,这样就能够缓解单独一台服务器所承受的负担,把压力分散到多台服务器上,同时保证了数据的统一,并且就算某台服务器由于异常而当机也不会影响其余模块的游戏玩家,从而提升了总体稳定性。

  分组式服务器缓解了服务器的压力,但也带来了服务器调度问题,分组式服务器须要对服务器跳转进行处理,就以一个玩家进行游戏场景跳转做为讨论基础:假设有一玩家处于游戏场景A,他想从场景A 跳转到场景B,在游戏中,咱们称之场景切换,这时玩家就会触发跳转需求,好比走到了场景中的切换点,这样服务器就把玩家数据从"游戏场景A 服务器"删除,同时在"游戏场景B 服务器"中把玩家创建起来。

  这里描述了场景切换的简单模型,当中处理还有不少步骤,不过经过这样的思考相信你们能够派生出不少应用技巧。不过须要注意的是,在场景切换或者说模块间切换的时候,须要切实考虑好数据的传输安全以及逻辑合理性,不然切换极可能会成为未来玩家复制物品的桥梁。

 

  总结

  本篇讲述的都是经过一些简单的过程来进行网络游戏通信,提供了一个制做的思路,虽然具体实现起来还有许多要作,但只要顺着这个思路去扩展、去完善,相信你们很快就可以编写出本身的网络通信模块。因为时间仓促,本文在不少细节方面都有省略,文中如有错误之处也望你们见谅。


go*odmorning收集整理(请勿删除)

游戏外挂设计技术探讨①

1、 前言

  所谓游戏外挂,实际上是一种游戏外辅程序,它能够协助玩家自动产生游戏动做、修改游戏网络数据包以及修改游戏内存数据等,以实现玩家用最少的时间和金钱去完成功力升级和过关斩将。虽然,如今对游戏外挂程序的“合法”身份众说纷纭,在这里我不想对此发表任何我的意见,让时间去说明一切吧。

  无论游戏外挂程序是否是“合法”身份,可是它倒是具备必定的技术含量的,在这些小小程序中使用了许多高端技术,如拦截Sock技术、拦截API技术、模拟键盘与鼠标技术、直接修改程序内存技术等等。本文将对常见的游戏外挂中使用的技术进行全面剖析。

  2、认识外挂

  游戏外挂的历史能够追溯到单机版游戏时代,只不过当时它使用了另外一个更通俗易懂的名字??游戏修改器。它能够在游戏中追踪锁定游戏主人公的各项能力数值。这样玩家在游戏中能够达到主角不掉血、不耗费魔法、不消耗金钱等目的。这样下降了游戏的难度,使得玩家更容易通关。

  随着网络游戏的时代的来临,游戏外挂在原有的功能之上进行了新的发展,它变得更加多种多样,功能更增强大,操做更加简单,以致有些游戏的外挂已经成为一个体系,好比《石器时代》,外挂品种达到了几十种,自动战斗、自动行走、自动练级、自动补血、加速、不遇敌、原地遇敌、快速增长经验值、按键精灵……几乎无所不包。

  游戏外挂的设计主要是针对于某个游戏开发的,咱们能够根据它针对的游戏的类型可大体可将外挂分为两种大类。

  一类是将游戏中大量繁琐和无聊的攻击动做使用外挂自动完成,以帮助玩家轻松搞定攻击对象并能够快速的增长玩家的经验值。好比在《龙族》中有一种工做的设定,玩家的工做等级越高,就能够驾驭越好的装备。可是增长工做等级却不是一件有趣的事情,毋宁说是重复枯燥的机械劳动。若是你想作法师用的杖,首先须要作基本工做--?砍树。砍树的方法很简单,在一棵大树前不停的点鼠标就能够了,每10000的经验升一级。这就意味着玩家要在大树前不停的点击鼠标,这种无聊的事情经过"按键精灵"就能够解决。外挂的"按键精灵"功能可让玩家摆脱无趣的点击鼠标的工做。

  另外一类是由外挂程序产生欺骗性的网络游戏封包,并将这些封包发送到网络游戏服务器,利用这些虚假信息欺骗服务器进行游戏数值的修改,达到修改角色能力数值的目的。这类外挂程序针对性很强,通常在设计时都是针对某个游戏某个版原本作的,由于每一个网络游戏服务器与客户端交流的数据包各不相同,外挂程序必需要对欺骗的网络游戏服务器的数据包进行分析,才能产生服务器识别的数据包。这类外挂程序也是当前最流利的一类游戏外挂程序。

  另外,如今不少外挂程序功能强大,不只实现了自动动做代理和封包功能,并且还提供了对网络游戏的客户端程序的数据进行修改,以达到欺骗网络游戏服务器的目的。我相信,随着网络游戏商家的反外挂技术的进展,游戏外挂将会产生更多更优秀的技术,让咱们期待着看场技术大战吧......

  3、外挂技术综述

  能够将开发游戏外挂程序的过程大致上划分为两个部分:

  前期部分工做是对外挂的主体游戏进行分析,不一样类型的外挂分析主体游戏的内容也不相同。如外挂为上述谈到的外挂类型中的第一类时,其分析过程常是针对游戏的场景中的攻击对象的位置和分布状况进行分析,以实现外挂自动进行攻击以及位置移动。如外挂为外挂类型中的第二类时,其分析过程常是针对游戏服务器与客户端之间通信包数据的结构、内容以及加密算法的分析。因网络游戏公司通常都不会公布其游戏产品的通信包数据的结构、内容和加密算法的信息,因此对于开发第二类外挂成功的关键在因而否能正确分析游戏包数据的结构、内容以及加密算法,虽然能够使用一些工具辅助分析,可是这仍是一种坚苦而复杂的工做。

  后期部分工做主要是根据前期对游戏的分析结果,使用大量的程序开发技术编写外挂程序以实现对游戏的控制或修改。如外挂程序为第一类外挂时,一般会使用到鼠标模拟技术来实现游戏角色的自动位置移动,使用键盘模拟技术来实现游戏角色的自动攻击。如外挂程序为第二类外挂时,一般会使用到挡截Sock和挡截API函数技术,以挡截游戏服务器传来的网络数据包并将数据包修改后封包后传给游戏服务器。另外,还有许多外挂使用对游戏客户端程序内存数据修改技术以及游戏加速技术。

  本文主要是针对开发游戏外挂程序后期使用的程序开发技术进行探讨,重点介绍的以下几种在游戏外挂中常使用的程序开发技术:

  ● 动做模拟技术:主要包括键盘模拟技术和鼠标模拟技术。

  ● 封包技术:主要包括挡截Sock技术和挡截API技术。
4、动做模拟技术

  咱们在前面介绍过,几乎全部的游戏都有大量繁琐和无聊的攻击动做以增长玩家的功力,还有那些数不完的迷宫,这些好像已经成为了角色游戏的代名词。如今,外挂能够帮助玩家从这些繁琐而无聊的工做中摆脱出来,专一于游戏情节的进展。外挂程序为了实现自动角色位置移动和自动攻击等功能,须要使用到键盘模拟技术和鼠标模拟技术。下面咱们将重点介绍这些技术并编写一个简单的实例帮助读者理解动做模拟技术的实现过程。

  1. 鼠标模拟技术
  
  几乎全部的游戏中都使用了鼠标来改变角色的位置和方向,玩家仅用一个小小的鼠标,就能够使角色畅游天下。那么,咱们如何实如今没有玩家的参与下角色也能够自动行走呢。其实实现这个并不难,仅仅几个Windows API函数就能够搞定,让咱们先来认识认识这些API函数。

  (1) 模拟鼠标动做API函数mouse_event,它能够实现模拟鼠标按下和放开等动做。

    VOID mouse_event(
      DWORD dwFlags, // 鼠标动做标识。
      DWORD dx, // 鼠标水平方向位置。
      DWORD dy, // 鼠标垂直方向位置。
      DWORD dwData, // 鼠标轮子转动的数量。
      DWORD dwExtraInfo // 一个关联鼠标动做辅加信息。
    );

  其中,dwFlags表示了各类各样的鼠标动做和点击活动,它的经常使用取值以下:

   MOUSEEVENTF_MOVE 表示模拟鼠标移动事件。

   MOUSEEVENTF_LEFTDOWN 表示模拟按下鼠标左键。

   MOUSEEVENTF_LEFTUP 表示模拟放开鼠标左键。

   MOUSEEVENTF_RIGHTDOWN 表示模拟按下鼠标右键。

   MOUSEEVENTF_RIGHTUP 表示模拟放开鼠标右键。

   MOUSEEVENTF_MIDDLEDOWN 表示模拟按下鼠标中键。

   MOUSEEVENTF_MIDDLEUP 表示模拟放开鼠标中键。

  (2)、设置和获取当前鼠标位置的API函数。获取当前鼠标位置使用GetCursorPos()函数,设置当前鼠标位置使用SetCursorPos()函数。

    BOOL GetCursorPos(
     LPPOINT lpPoint // 返回鼠标的当前位置。
    );
    BOOL SetCursorPos(
    int X, // 鼠标的水平方向位置。
      int Y //鼠标的垂直方向位置。
    );

  一般游戏角色的行走都是经过鼠标移动至目的地,而后按一下鼠标的按钮就搞定了。下面咱们使用上面介绍的API函数来模拟角色行走过程。

   CPoint oldPoint,newPoint;
   GetCursorPos(&oldPoint); //保存当前鼠标位置。
   newPoint.x = oldPoint.x+40;
   newPoint.y = oldPoint.y+10;
   SetCursorPos(newPoint.x,newPoint.y); //设置目的地位置。
   mouse_event(MOUSEEVENTF_RIGHTDOWN,0,0,0,0);//模拟按下鼠标右键。
   mouse_event(MOUSEEVENTF_RIGHTUP,0,0,0,0);//模拟放开鼠标右键。

  2. 键盘模拟技术

  在不少游戏中,不只提供了鼠标的操做,并且还提供了键盘的操做,在对攻击对象进行攻击时还能够使用快捷键。为了使这些攻击过程可以自动进行,外挂程序须要使用键盘模拟技术。像鼠标模拟技术同样,Windows API也提供了一系列API函数来完成对键盘动做的模拟。

  模拟键盘动做API函数keydb_event,它能够模拟对键盘上的某个或某些键进行按下或放开的动做。

   VOID keybd_event(
     BYTE bVk, // 虚拟键值。
     BYTE bScan, // 硬件扫描码。
     DWORD dwFlags, // 动做标识。
     DWORD dwExtraInfo // 与键盘动做关联的辅加信息。
   );

  其中,bVk表示虚拟键值,其实它是一个BYTE类型值的宏,其取值范围为1-254。有关虚拟键值表请在MSDN上使用关键字“Virtual-Key Codes”查找相关资料。bScan表示当键盘上某键被按下和放开时,键盘系统硬件产生的扫描码,咱们能够MapVirtualKey()函数在虚拟键值与扫描码之间进行转换。dwFlags表示各类各样的键盘动做,它有两种取值:KEYEVENTF_EXTENDEDKEY和KEYEVENTF_KEYUP。

  下面咱们使用一段代码实如今游戏中按下Shift+R快捷键对攻击对象进行攻击。

   keybd_event(VK_CONTROL,MapVirtualKey(VK_CONTROL,0),0,0); //按下CTRL键。
   keybd_event(0x52,MapVirtualKey(0x52,0),0,0);//键下R键。
   keybd_event(0x52,MapVirtualKey(0x52,0), KEYEVENTF_KEYUP,0);//放开R键。
   keybd_event(VK_CONTROL,MapVirtualKey(VK_CONTROL,0),
   KEYEVENTF_KEYUP,0);//放开CTRL键。

  3. 激活外挂

  上面介绍的鼠标和键盘模拟技术实现了对游戏角色的动做部分的模拟,但要想外挂能工做于游戏之上,还须要将其与游戏的场景窗口联系起来或者使用一个激活键,就象按键精灵的那个激活键同样。咱们能够用GetWindow函数来枚举窗口,也能够用Findwindow函数来查找特定的窗口。另外还有一个FindWindowEx函数能够找到窗口的子窗口,当游戏切换场景的时候咱们能够用FindWindowEx来肯定一些当前窗口的特征,从而判断是否还在这个场景,方法不少了,好比能够GetWindowInfo来肯定一些东西,好比当查找不到某个按钮的时候就说明游戏场景已经切换了等等办法。当使用激活键进行关联,须要使用Hook技术开发一个全局键盘钩子,在这里就不具体介绍全局钩子的开发过程了,在后面的实例中咱们将会使用到全局钩子,到时将学习到全局钩子的相关知识。


游戏外挂设计技术探讨②

4. 实例实现

  经过上面的学习,咱们已经基本具有了编写动做式游戏外挂的能力了。下面咱们将建立一个画笔程序外挂,它实现自动移动画笔字光标的位置并写下一个红色的“R”字。以这个实例为基础,加入相应的游戏动做规则,就能够实现一个完整的游戏外挂。这里做者不想使用某个游戏做为例子来开发外挂(因没有游戏商家的受权啊!),如读者感兴趣的话能够找一个游戏试试,最好仅作测试技术用。

  首先,咱们须要编写一个全局钩子,使用它来激活外挂,激活键为F10。建立全局钩子步骤以下:

  (1).选择MFC AppWizard(DLL)建立项目ActiveKey,并选择MFC Extension DLL(共享MFC拷贝)类型。

  (2).插入新文件ActiveKey.h,在其中输入以下代码:

   #ifndef _KEYDLL_H
   #define _KEYDLL_H

   class AFX_EXT_CLASS CKeyHook:public CObject
   {
    public:
 CKeyHook();
 ~CKeyHook();
 HHOOK Start(); //安装钩子
 BOOL Stop(); //卸载钩子
   };
   #endif

  (3).在ActiveKey.cpp文件中加入声明"#include ActiveKey.h"。

  (4).在ActiveKey.cpp文件中加入共享数据段,代码以下:

   //Shared data section
   #pragma data_seg("sharedata")
   HHOOK glhHook=NULL; //钩子句柄。
   HINSTANCE glhInstance=NULL; //DLL实例句柄。
   #pragma data_seg()

  (5).在ActiveKey.def文件中设置共享数据段属性,代码以下:

   SETCTIONS
   shareddata READ WRITE SHARED

  (6).在ActiveKey.cpp文件中加入CkeyHook类的实现代码和钩子函数代码:

   //键盘钩子处理函数。
   extern "C" LRESULT WINAPI KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
   {
   if( nCode >= 0 )
   {
   if( wParam == 0X79 )//当按下F10键时,激活外挂。
 {
  //外挂实现代码。
CPoint newPoint,oldPoint;
   GetCursorPos(&oldPoint);
   newPoint.x = oldPoint.x+40;
   newPoint.y = oldPoint.y+10;
   SetCursorPos(newPoint.x,newPoint.y);
   mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);//模拟按下鼠标左键。
  mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);//模拟放开鼠标左键。
  keybd_event(VK_SHIFT,MapVirtualKey(VK_SHIFT,0),0,0); //按下SHIFT键。
  keybd_event(0x52,MapVirtualKey(0x52,0),0,0);//按下R键。
  keybd_event(0x52,MapVirtualKey(0x52,0),KEYEVENTF_KEYUP,0);//放开R键。
  keybd_event(VK_SHIFT,MapVirtualKey(VK_SHIFT,0),KEYEVENTF_KEYUP,0);//放开SHIFT键。
      SetCursorPos(oldPoint.x,oldPoint.y);
 }
   }
   return CallNextHookEx(glhHook,nCode,wParam,lParam);
   }

   CKeyHook::CKeyHook(){}
   CKeyHook::~CKeyHook()
   { 
   if( glhHook )
Stop();
   }
   //安装全局钩子。
   HHOOK CKeyHook::Start()
   {
glhHook = SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,glhInstance,0);//设置键盘钩子。
return glhHook;
}
   //卸载全局钩子。
   BOOL CKeyHook::Stop()
   {
   BOOL bResult = TRUE;
 if( glhHook )
   bResult = UnhookWindowsHookEx(glhHook);//卸载键盘钩子。
   return bResult;
   }

  (7).修改DllMain函数,代码以下:

   extern "C" int APIENTRY
   DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
   {
//若是使用lpReserved参数则删除下面这行
UNREFERENCED_PARAMETER(lpReserved);

if (dwReason == DLL_PROCESS_ATTACH)
{
  TRACE0("NOtePadHOOK.DLL Initializing!/n");
   //扩展DLL仅初始化一次
  if (!AfxInitExtensionModule(ActiveKeyDLL, hInstance))
return 0;
  new CDynLinkLibrary(ActiveKeyDLL);
      //把DLL加入动态MFC类库中
  glhInstance = hInstance;
  //插入保存DLL实例句柄
}
else if (dwReason == DLL_PROCESS_DETACH)
{
  TRACE0("NotePadHOOK.DLL Terminating!/n");
  //终止这个连接库前调用它
  AfxTermExtensionModule(ActiveKeyDLL);
}
return 1;
   }

  (8).编译项目ActiveKey,生成ActiveKey.DLL和ActiveKey.lib。

  接着,咱们还须要建立一个外壳程序将全局钩子安装了Windows系统中,这个外壳程序编写步骤以下:

  (1).建立一个对话框模式的应用程序,项目名为Simulate。

  (2).在主对话框中加入一个按钮,使用ClassWizard为其建立CLICK事件。

  (3).将ActiveKey项目Debug目录下的ActiveKey.DLL和ActiveKey.lib拷贝到Simulate项目目录下。

  (4).从“工程”菜单中选择“设置”,弹出Project Setting对话框,选择Link标签,在“对象/库模块”中输入ActiveKey.lib。

  (5).将ActiveKey项目中的ActiveKey.h头文件加入到Simulate项目中,并在Stdafx.h中加入#include ActiveKey.h。

  (6).在按钮单击事件函数输入以下代码:

   void CSimulateDlg::OnButton1()
   {
// TODO: Add your control notification handler code here
if( !bSetup )
{
m_hook.Start();//激活全局钩子。
}
else
{
m_hook.Stop();//撤消全局钩子。
}
bSetup = !bSetup;

   }

  (7).编译项目,并运行程序,单击按钮激活外挂。

  (8).启动画笔程序,选择文本工具并将笔的颜色设置为红色,将鼠标放在任意位置后,按F10键,画笔程序自动移动鼠标并写下一个红色的大写R。图一展现了按F10键前的画笔程序的状态,图二展现了按F10键后的画笔程序的状态。


图一:按F10前状态(001.jpg)


图二:按F10后状态(002.jpg)
  5、封包技术

  经过对动做模拟技术的介绍,咱们对游戏外挂有了必定程度上的认识,也学会了使用动做模拟技术来实现简单的动做模拟型游戏外挂的制做。这种动做模拟型游戏外挂有必定的局限性,它仅仅只能解决使用计算机代替人力完成那么有规律、繁琐而无聊的游戏动做。可是,随着网络游戏的盛行和复杂度的增长,不少游戏要求将客户端动做信息及时反馈回服务器,经过服务器对这些动做信息进行有效认证后,再向客户端发送下一步游戏动做信息,这样动做模拟技术将失去原有的效应。为了更好地“外挂”这些游戏,游戏外挂程序也进行了升级换代,它们将之前针对游戏用户界面层的模拟推动到数据通信层,经过封包技术在客户端挡截游戏服务器发送来的游戏控制数据包,分析数据包并修改数据包;同时还需按照游戏数据包结构建立数据包,再模拟客户端发送给游戏服务器,这个过程其实就是一个封包的过程。

  封包的技术是实现第二类游戏外挂的最核心的技术。封包技术涉及的知识很普遍,实现方法也不少,如挡截WinSock、挡截API函数、挡截消息、VxD驱动程序等。在此咱们也不可能在此文中将全部的封包技术都进行详细介绍,故选择两种在游戏外挂程序中最经常使用的两种方法:挡截WinSock和挡截API函数。

  1. 挡截WinSock

  众所周知,Winsock是Windows网络编程接口,它工做于Windows应用层,它提供与底层传输协议无关的高层数据传输编程接口。在Windows系统中,使用WinSock接口为应用程序提供基于TCP/IP协议的网络访问服务,这些服务是由Wsock32.DLL动态连接库提供的函数库来完成的。

  由上说明可知,任何Windows基于TCP/IP的应用程序都必须经过WinSock接口访问网络,固然网络游戏程序也不例外。由此咱们能够想象一下,若是咱们能够控制WinSock接口的话,那么控制游戏客户端程序与服务器之间的数据包也将易如反掌。按着这个思路,下面的工做就是如何完成控制WinSock接口了。由上面的介绍可知,WinSock接口实际上是由一个动态连接库提供的一系列函数,由这些函数实现对网络的访问。有了这层的认识,问题就好办多了,咱们能够制做一个相似的动态连接库来代替原WinSock接口库,在其中实现WinSock32.dll中实现的全部函数,并保证全部函数的参数个数和顺序、返回值类型都应与原库相同。在这个自制做的动态库中,能够对咱们感兴趣的函数(如发送、接收等函数)进行挡截,放入外挂控制代码,最后还继续调用原WinSock库中提供的相应功能函数,这样就能够实现对网络数据包的挡截、修改和发送等封包功能。

  下面重点介绍建立挡截WinSock外挂程序的基本步骤:

  (1) 建立DLL项目,选择Win32 Dynamic-Link Library,再选择An empty DLL project。

  (2) 新建文件wsock32.h,按以下步骤输入代码:

  ① 加入相关变量声明:

   HMODULE hModule=NULL; //模块句柄
   char buffer[1000]; //缓冲区
   FARPROC proc; //函数入口指针

  ② 定义指向原WinSock库中的全部函数地址的指针变量,因WinSock库共提供70多个函数,限于篇幅,在此就只选择几个经常使用的函数列出,有关这些库函数的说明可参考MSDN相关内容。

   //定义指向原WinSock库函数地址的指针变量。
   SOCKET (__stdcall *socket1)(int ,int,int);//建立Sock函数。
   int (__stdcall *WSAStartup1)(WORD,LPWSADATA);//初始化WinSock库函数。
   int (__stdcall *WSACleanup1)();//清除WinSock库函数。
   int (__stdcall *recv1)(SOCKET ,char FAR * ,int ,int );//接收数据函数。
   int (__stdcall *send1)(SOCKET ,const char * ,int ,int);//发送数据函数。
   int (__stdcall *connect1)(SOCKET,const struct sockaddr *,int);//建立链接函数。
   int (__stdcall *bind1)(SOCKET ,const struct sockaddr *,int );//绑定函数。
   ......其它函数地址指针的定义略。

  (3) 新建wsock32.cpp文件,按以下步骤输入代码:

  ① 加入相关头文件声明:

   #include <windows.h>
   #include <stdio.h>
   #include "wsock32.h"

  ② 添加DllMain函数,在此函数中首先须要加载原WinSock库,并获取此库中全部函数的地址。代码以下:

   BOOL WINAPI DllMain (HANDLE hInst,ULONG ul_reason_for_call,LPVOID lpReserved)
   {
    if(hModule==NULL){
     //加载原WinSock库,原WinSock库已复制为wsock32.001。
   hModule=LoadLibrary("wsock32.001");
  }
    else return 1;
//获取原WinSock库中的全部函数的地址并保存,下面仅列出部分代码。
if(hModule!=NULL){
     //获取原WinSock库初始化函数的地址,并保存到WSAStartup1中。
proc=GetProcAddress(hModule,"WSAStartup");
   WSAStartup1=(int (_stdcall *)(WORD,LPWSADATA))proc;
     //获取原WinSock库消除函数的地址,并保存到WSACleanup1中。
    proc=GetProcAddress(hModule i,"WSACleanup");
    WSACleanup1=(int (_stdcall *)())proc;
     //获取原建立Sock函数的地址,并保存到socket1中。
    proc=GetProcAddress(hModule,"socket");
     socket1=(SOCKET (_stdcall *)(int ,int,int))proc;
     //获取原建立链接函数的地址,并保存到connect1中。
     proc=GetProcAddress(hModule,"connect");
     connect1=(int (_stdcall *)(SOCKET ,const struct sockaddr *,int ))proc;
     //获取原发送函数的地址,并保存到send1中。
     proc=GetProcAddress(hModule,"send");
     send1=(int (_stdcall *)(SOCKET ,const char * ,int ,int ))proc;
     //获取原接收函数的地址,并保存到recv1中。
     proc=GetProcAddress(hModule,"recv");
     recv1=(int (_stdcall *)(SOCKET ,char FAR * ,int ,int ))proc;
     ......其它获取函数地址代码略。
   }
   else return 0;
   return 1;
}

  ③ 定义库输出函数,在此能够对咱们感兴趣的函数中添加外挂控制代码,在全部的输出函数的最后一步都调用原WinSock库的同名函数。部分输出函数定义代码以下:

//库输出函数定义。
//WinSock初始化函数。
    int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData)
    {
     //调用原WinSock库初始化函数
     return WSAStartup1(wVersionRequired,lpWSAData);
    }
    //WinSock结束清除函数。
    int PASCAL FAR WSACleanup(void)
    {
     return WSACleanup1(); //调用原WinSock库结束清除函数。
    }
    //建立Socket函数。
    SOCKET PASCAL FAR socket (int af, int type, int protocol)
    {
     //调用原WinSock库建立Socket函数。
     return socket1(af,type,protocol);
    }
    //发送数据包函数
    int PASCAL FAR send(SOCKET s,const char * buf,int len,int flags)
    {
   //在此能够对发送的缓冲buf的内容进行修改,以实现欺骗服务器。
   外挂代码......
   //调用原WinSock库发送数据包函数。
     return send1(s,buf,len,flags);
    }
//接收数据包函数。
    int PASCAL FAR recv(SOCKET s, char FAR * buf, int len, int flags)
    {
   //在此能够挡截到服务器端发送到客户端的数据包,先将其保存到buffer中。
   strcpy(buffer,buf);
   //对buffer数据包数据进行分析后,对其按照玩家的指令进行相关修改。
   外挂代码......
   //最后调用原WinSock中的接收数据包函数。
     return recv1(s, buffer, len, flags);
     }
    .......其它函数定义代码略。

  (4)、新建wsock32.def配置文件,在其中加入全部库输出函数的声明,部分声明代码以下:

   LIBRARY "wsock32"
   EXPORTS
    WSAStartup @1
   WSACleanup @2
    recv @3
    send @4
    socket @5
   bind @6
   closesocket @7
   connect @8

   ......其它输出函数声明代码略。

  (5)、从“工程”菜单中选择“设置”,弹出Project Setting对话框,选择Link标签,在“对象/库模块”中输入Ws2_32.lib。

  (6)、编译项目,产生wsock32.dll库文件。

  (7)、将系统目录下原wsock32.dll库文件拷贝到被外挂程序的目录下,并将其更名为wsock.001;再将上面产生的wsock32.dll文件一样拷贝到被外挂程序的目录下。从新启动游戏程序,此时游戏程序将先加载咱们本身制做的wsock32.dll文件,再经过该库文件间接调用原WinSock接口函数来实现访问网络。上面咱们仅仅介绍了挡载WinSock的实现过程,至于如何加入外挂控制代码,还须要外挂开发人员对游戏数据包结构、内容、加密算法等方面的仔细分析(这个过程将是一个艰辛的过程),再生成外挂控制代码。关于数据包分析方法和技巧,不是本文讲解的范围,如您感兴趣能够到网上查查相关资料。

g*oodmorning收集整理(请勿删除)

游戏外挂设计技术探讨③

2.挡截API

  挡截API技术与挡截WinSock技术在原理上很类似,可是前者比后者提供了更强大的功能。挡截WinSock仅只能挡截WinSock接口函数,而挡截API能够实现对应用程序调用的包括WinSock API函数在内的全部API函数的挡截。若是您的外挂程序仅打算对WinSock的函数进行挡截的话,您能够只选择使用上小节介绍的挡截WinSock技术。随着大量外挂程序在功能上的扩展,它们不只仅只提供对数据包的挡截,并且还对游戏程序中使用的Windows API或其它DLL库函数的挡截,以使外挂的功能更增强大。例如,能够经过挡截相关API函数以实现对非中文游戏的汉化功能,有了这个利器,能够使您的外挂程序无所不能了。

  挡截API技术的原理核心也是使用咱们本身的函数来替换掉Windows或其它DLL库提供的函数,有点同挡截WinSock原理类似吧。可是,其实现过程却比挡截WinSock要复杂的多,如像实现挡截Winsock过程同样,将应用程序调用的全部的库文件都写一个模拟库有点不大可能,就只说Windows API就有上千个,还有不少库提供的函数结构并未公开,因此写一个模拟库代替的方式不大现实,故咱们必须另谋良方。

  挡截API的最终目标是使用自定义的函数代替原函数。那么,咱们首先应该知道应用程序什么时候、何地、用何种方式调用原函数。接下来,须要将应用程序中调用该原函数的指令代码进行修改,使它将调用函数的指针指向咱们本身定义的函数地址。这样,外挂程序才能彻底控制应用程序调用的API函数,至于在其中如何加入外挂代码,就应需求而异了。最后还有一个重要的问题要解决,如何将咱们自定义的用来代替原API函数的函数代码注入被外挂游戏程序进行地址空间中,因在Windows系统中应用程序仅只能访问到本进程地址空间内的代码和数据。

  综上所述,要实现挡截API函数,至少须要解决以下三个问题:

  ● 如何定位游戏程序中调用API函数指令代码?

  ● 如何修改游戏程序中调用API函数指令代码?

  ● 如何将外挂代码(自定义的替换函数代码)注入到游戏程序进程地址空间?

  下面咱们逐一介绍这几个问题的解决方法:

  (1) 、定位调用API函数指令代码

  咱们知道,在汇编语言中使用CALL指令来调用函数或过程的,它是经过指令参数中的函数地址而定位到相应的函数代码的。那么,咱们若是能寻找到程序代码中全部调用被挡截的API函数的CALL指令的话,就能够将该指令中的函数地址参数修改成替代函数的地址。虽然这是一个可行的方案,可是实现起来会很繁琐,也不稳健。庆幸的是,Windows系统中所使用的可执行文件(PE格式)采用了输入地址表机制,将全部在程序调用的API函数的地址信息存放在输入地址表中,而在程序代码CALL指令中使用的地址不是API函数的地址,而是输入地址表中该API函数的地址项,如想使程序代码中调用的API函数被代替掉,只用将输入地址表中该API函数的地址项内容修改便可。具体理解输入地址表运行机制,还须要了解一下PE格式文件结构,其中图三列出了PE格式文件的大体结构。


  图三:PE格式大体结构图(003.jpg)

  PE格式文件一开始是一段DOS程序,当你的程序在不支持Windows的环境中运行时,它就会显示“This Program cannot be run in DOS mode”这样的警告语句,接着这个DOS文件头,就开始真正的PE文件内容了。首先是一段称为“IMAGE_NT_HEADER”的数据,其中是许多关于整个PE文件的消息,在这段数据的尾端是一个称为Data Directory的数据表,经过它能快速定位一些PE文件中段(section)的地址。在这段数据以后,则是一个“IMAGE_SECTION_HEADER”的列表,其中的每一项都详细描述了后面一个段的相关信息。接着它就是PE文件中最主要的段数据了,执行代码、数据和资源等等信息就分别存放在这些段中。

  在全部的这些段里,有一个被称为“.idata”的段(输入数据段)值得咱们去注意,该段中包含着一些被称为输入地址表(IAT,Import Address Table)的数据列表。每一个用隐式方式加载的API所在的DLL都有一个IAT与之对应,同时一个API的地址也与IAT中一项相对应。当一个应用程序加载到内存中后,针对每个API函数调用,相应的产生以下的汇编指令:

  JMP DWORD PTR [XXXXXXXX]

  或

  CALL DWORD PTR [XXXXXXXX]

  其中,[XXXXXXXX]表示指向了输入地址表中一个项,其内容是一个DWORD,而正是这个DWORD才是API函数在内存中的真正地址。所以咱们要想拦截一个API的调用,只要简单的把那个DWORD改成咱们本身的函数的地址。

  (2) 、修改调用API函数代码

  从上面对PE文件格式的分析可知,修改调用API函数代码实际上是修改被调用API函数在输入地址表中IAT项内容。因为Windows系统对应用程序指令代码地址空间的严密保护机制,使得修改程序指令代码很是困难,以致于许多高手为之编写VxD进入Ring0。在这里,我为你们介绍一种较为方便的方法修改进程内存,它仅须要调用几个Windows核心API函数,下面我首先来学会一下这几个API函数:

   DWORD VirtualQuery(
   LPCVOID lpAddress, // address of region
   PMEMORY_BASIC_INFORMATION lpBuffer, // information buffer
   DWORD dwLength // size of buffer
   );

  该函数用于查询关于本进程内虚拟地址页的信息。其中,lpAddress表示被查询页的区域地址;lpBuffer表示用于保存查询页信息的缓冲;dwLength表示缓冲区大小。返回值为实际缓冲大小。

   BOOL VirtualProtect(
   LPVOID lpAddress, // region of committed pages
   SIZE_T dwSize, // size of the region
   DWORD flNewProtect, // desired access protection
   PDWORD lpflOldProtect // old protection
   );

  该函数用于改变本进程内虚拟地址页的保护属性。其中,lpAddress表示被改变保护属性页区域地址;dwSize表示页区域大小;flNewProtect表示新的保护属性,可取值为PAGE_READONLY、PAGE_READWRITE、PAGE_EXECUTE等;lpflOldProtect表示用于保存改变前的保护属性。若是函数调用成功返回“T”,不然返回“F”。

  有了这两个API函数,咱们就能够为所欲为的修改进程内存了。首先,调用VirtualQuery()函数查询被修改内存的页信息,再根据此信息调用VirtualProtect()函数改变这些页的保护属性为PAGE_READWRITE,有了这个权限您就能够任意修改进程内存数据了。下面一段代码演示了如何将进程虚拟地址为0x0040106c处的字节清零。

   BYTE* pData = 0x0040106c;
   MEMORY_BASIC_INFORMATION mbi_thunk;
   //查询页信息。
   VirtualQuery(pData, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
   //改变页保护属性为读写。
   VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,
   PAGE_READWRITE, &mbi_thunk.Protect);
   //清零。
   *pData = 0x00;
   //恢复页的原保护属性。
   DWORD dwOldProtect;
   VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize,
   mbi_thunk.Protect, &dwOldProtect);
  (3)、注入外挂代码进入被挂游戏进程中

  完成了定位和修改程序中调用API函数代码后,咱们就能够随意设计自定义的API函数的替代函数了。作完这一切后,还须要将这些代码注入到被外挂游戏程序进程内存空间中,否则游戏进程根本不会访问到替代函数代码。注入方法有不少,如利用全局钩子注入、利用注册表注入挡截User32库中的API函数、利用CreateRemoteThread注入(仅限于NT/2000)、利用BHO注入等。由于咱们在动做模拟技术一节已经接触过全局钩子,我相信聪明的读者已经彻底掌握了全局钩子的制做过程,因此咱们在后面的实例中,将继续利用这个全局钩子。至于其它几种注入方法,若是感兴趣可参阅MSDN有关内容。

  有了以上理论基础,咱们下面就开始制做一个挡截MessageBoxA和recv函数的实例,在开发游戏外挂程序 时,能够此实例为框架,加入相应的替代函数和处理代码便可。此实例的开发过程以下:

  (1) 打开前面建立的ActiveKey项目。

  (2) 在ActiveKey.h文件中加入HOOKAPI结构,此结构用来存储被挡截API函数名称、原API函数地址和替代函数地址。

   typedef struct tag_HOOKAPI
   {
   LPCSTR szFunc;//被HOOK的API函数名称。
   PROC pNewProc;//替代函数地址。
   PROC pOldProc;//原API函数地址。
   }HOOKAPI, *LPHOOKAPI;

  (3) 打开ActiveKey.cpp文件,首先加入一个函数,用于定位输入库在输入数据段中的IAT地址。代码以下:

   extern "C" __declspec(dllexport)PIMAGE_IMPORT_DESCRIPTOR
   LocationIAT(HMODULE hModule, LPCSTR szImportMod)
   //其中,hModule为进程模块句柄;szImportMod为输入库名称。
   {
   //检查是否为DOS程序,如是返回NULL,因DOS程序没有IAT。
   PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER) hModule;
   if(pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) return NULL;
    //检查是否为NT标志,不然返回NULL。
    PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDOSHeader+ (DWORD)(pDOSHeader->e_lfanew));
    if(pNTHeader->Signature != IMAGE_NT_SIGNATURE) return NULL;
    //没有IAT表则返回NULL。
    if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0) return NULL;
    //定位第一个IAT位置。
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDOSHeader + (DWORD)(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));
    //根据输入库名称循环检查全部的IAT,如匹配则返回该IAT地址,不然检测下一个IAT。
    while (pImportDesc->Name)
    {
     //获取该IAT描述的输入库名称。
   PSTR szCurrMod = (PSTR)((DWORD)pDOSHeader + (DWORD)(pImportDesc->Name));
   if (stricmp(szCurrMod, szImportMod) == 0) break;
   pImportDesc++;
    }
    if(pImportDesc->Name == NULL) return NULL;
   return pImportDesc;
   }

  再加入一个函数,用来定位被挡截API函数的IAT项并修改其内容为替代函数地址。代码以下:

   extern "C" __declspec(dllexport)
   HookAPIByName( HMODULE hModule, LPCSTR szImportMod, LPHOOKAPI pHookApi)
   //其中,hModule为进程模块句柄;szImportMod为输入库名称;pHookAPI为HOOKAPI结构指针。
   {
    //定位szImportMod输入库在输入数据段中的IAT地址。
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = LocationIAT(hModule, szImportMod);
  if (pImportDesc == NULL) return FALSE;
    //第一个Thunk地址。
    PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->OriginalFirstThunk));
   //第一个IAT项的Thunk地址。
    PIMAGE_THUNK_DATA pRealThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->FirstThunk));
    //循环查找被截API函数的IAT项,并使用替代函数地址修改其值。
   while(pOrigThunk->u1.Function)
{
 //检测此Thunk是否为IAT项。
if((pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG)
{
  //获取此IAT项所描述的函数名称。
 PIMAGE_IMPORT_BY_NAME pByName =(PIMAGE_IMPORT_BY_NAME)((DWORD)hModule+(DWORD)(pOrigThunk->u1.AddressOfData));
 if(pByName->Name[0] == '/0') return FALSE;
  //检测是否为挡截函数。
if(strcmpi(pHookApi->szFunc, (char*)pByName->Name) == 0)
  {
       MEMORY_BASIC_INFORMATION mbi_thunk;
       //查询修改页的信息。
       VirtualQuery(pRealThunk, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
//改变修改页保护属性为PAGE_READWRITE。
       VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize, PAGE_READWRITE, &mbi_thunk.Protect);
//保存原来的API函数地址。
      if(pHookApi->pOldProc == NULL)
pHookApi->pOldProc = (PROC)pRealThunk->u1.Function;
  //修改API函数IAT项内容为替代函数地址。
pRealThunk->u1.Function = (PDWORD)pHookApi->pNewProc;
//恢复修改页保护属性。
DWORD dwOldProtect;
       VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize, mbi_thunk.Protect, &dwOldProtect);
      }
}
  pOrigThunk++;
  pRealThunk++;
}
  SetLastError(ERROR_SUCCESS); //设置错误为ERROR_SUCCESS,表示成功。
  return TRUE;
   }

  (4) 定义替代函数,此实例中只给MessageBoxA和recv两个API进行挡截。代码以下:

   static int WINAPI MessageBoxA1 (HWND hWnd , LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
   {
    //过滤掉原MessageBoxA的正文和标题内容,只显示以下内容。
return MessageBox(hWnd, "Hook API OK!", "Hook API", uType);
   }
   static int WINAPI recv1(SOCKET s, char FAR *buf, int len, int flags )
   {
   //此处能够挡截游戏服务器发送来的网络数据包,能够加入分析和处理数据代码。
   return recv(s,buf,len,flags);
   }

  (5) 在KeyboardProc函数中加入激活挡截API代码,在if( wParam == 0X79 )语句中后面加入以下else if语句:

   ......
   //当激活F11键时,启动挡截API函数功能。
   else if( wParam == 0x7A )
   {
    HOOKAPI api[2];
api[0].szFunc ="MessageBoxA";//设置被挡截函数的名称。
api[0].pNewProc = (PROC)MessageBoxA1;//设置替代函数的地址。
api[1].szFunc ="recv";//设置被挡截函数的名称。
api[1].pNewProc = (PROC)recv1; //设置替代函数的地址。
//设置挡截User32.dll库中的MessageBoxA函数。
HookAPIByName(GetModuleHandle(NULL),"User32.dll",&api[0]);
//设置挡截Wsock32.dll库中的recv函数。
HookAPIByName(GetModuleHandle(NULL),"Wsock32.dll",&api[1]);
   }
   ......

  (6) 在ActiveKey.cpp中加入头文件声明 "#include "wsock32.h"。 从“工程”菜单中选择“设置”,弹出Project Setting对话框,选择Link标签,在“对象/库模块”中输入Ws2_32..lib。

  (7) 从新编译ActiveKey项目,产生ActiveKey.dll文件,将其拷贝到Simulate.exe目录下。运行Simulate.exe并启动全局钩子。激活任意应用程序,按F11键后,运行此程序中可能调用MessageBoxA函数的操做,看看信息框是否是有所变化。一样,如此程序正在接收网络数据包,就能够实现封包功能了。

  6、结束语

  除了以上介绍的几种游戏外挂程序经常使用的技术之外,在一些外挂程序中还使用了游戏数据修改技术、游戏加速技术等。在这篇文章里,就不逐一介绍了。 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/wgm001/archive/2007/11/25/1901372.aspx

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!但愿你也加入到咱们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

相关文章
相关标签/搜索