向导
陈末
陈末
2023-04-04回复过
游戏服务器中应对各种外挂/作弊的策略
在游戏开发过程中,游戏本身是十分脆弱的,在复杂的网络环境中,犹如一叶小舟,如果没有好的防守,可能说翻车就翻车。对于游戏开发者来说,与破坏者的斗争是长期的,艰苦卓绝的,也是一个斗智斗勇的过程,力量对比往往是此消彼长。所谓是道高一尺,魔高一丈,对于游戏守护者来说,要往往要做到的是魔高一尺,道高一丈。我们不可能完全避免作弊,只是在尽量增加作弊的成本。






游戏作弊的一般方式主要有以下几种常见的:


一. 加速器的使用


目前手游非常火热,就拿手游来说吧,很多手游都有自动战斗的选项, 而自动战斗的计算是在客户端执行的,仅在最后的结算阶段将数据提交给服务器,比如闯关卡,竞技场,一场PVE战斗设定的时间可能是五分钟左右,但是如果使用加速器的话,可能十几秒就完事了。这种防护也很简单,在服务器要记录关卡的开始时间,在提交结算时,比较一下这个时间,如果提交结算时间太短,不合常理,则视为结果无效。这种只是影响操作时间,对整个游戏来说影响不太大。






二 本地内存修改


有很多本地内存修改工具,可以修改内存中的数据,内存修改直接影响的就是战斗结果,因为目前我们的游戏都是联网的,很多操作都放在服务器进行了验证。而现在有很多游戏在前期战斗可能只是让客户端计算,这个时候修改一些内存数据就可能直接影响结果了。所以重要的内存数据也是需要加密的。


内存数据一般分为三类:






临时数据: 比如从网络传输过来的数据,这些数据都是一次性的,可以不加密;


需要暂时记录的数据: 比如战斗开始时,玩家的血量,武器,战斗力等,这些数据每次战斗开始要重置,是暂时存储的,需要加密,可能会被修改。比如,把血量无限大,怎么也死不了。


永久型内存数据:比如金币,钻石,虽然目前消费都会服务器验证,但是如果被修改了很多,在不知情的玩家那里看到影响也不好。也需要加密。数据类的加密也非常简单,比如,对存储的数据进行一次计算,显示的金币是500,而存储的时候加上一个值(这个值每次登陆的时候都要变化),取的时候再减去。这样就不容易根据数值,找到存储的内存地址了。也可以用类似的其它方法。比如防做一个加密的HashMap ,进的时候是加密的,出的时候是解密的,在函数里面加上一些较为强加密的算法就行了。


三 协议破解


协议破解一种方式是通过抓包工具,抓取包之后,如果你的协议没加密,是以明文语传输的,那么用户就可以根据你的协议伪造请求,也可以伪造服务器响应。虽然我们的协议是自定义的,也是以二进制传输的,但是明文的包结构很容易被破解。所以协议我们需要加密,增加破解难度,起码不能让小白使用一个第三方软件就能破解来。建议采用AES和RSA混合加密。用AES加密协议,RSA加密AES的密钥。






四 封包重发


所谓封包重发,指的是那些不怀好意的玩家截获客户端发出的封包(通常使用某种封包嗅探器),然后多次发送此包。特别是充值,此类现象很多,比如重复发送成功的订单。


虽然客户端运用定时器检测机制能够阻止正常客户端过快发送命令,比如定时器没每秒最多发出一个指令,那么无论客户端多么频繁地截获玩家的命令(比如玩家疯 狂地按键),这些命令仍会每秒只发送一次。使用封包重发则是非正规渠道,他是在客户端控制之外重新发送封包,所以能够在一秒内发送相同的指令数百次。


系统设计者也许会在服务器端使用同样每秒一次的定时器检测机制防御这种攻击。但是由于有网络延迟,这样做显得不切实际。因为虽然能够检测出大多数封 包重发攻击,但是网络延迟可能会使多个正常封包同时到达服务器,这样就会引起合法命令被抛弃。


一个有效的方式是,给每个用户的通信协议的包头中添加一个唯一的SequenceID,随着包的处理递增,记录当前的最大值,如果再来过的请求包中的SequenceID小于当前的最大记录,则视为非法,此次通信无效。因为每个用户的操作是单线程顺序性的。同时seqenceID也是做断线重连功能与回放功能必须的信息, 当然sequenceID也可以定义一个范围,比如包的序列相差不能超过10,超过之后视为非法包,丢弃。






五 游戏逻辑bug






字节越界bug :在C++中,4字节的存放int型变量的范围是-2147483648至2147483647,在游戏中,我们都有购买物品的操作,可以输入购买的数量,比如购买100件,每件30000000金币,那个总共需要30亿(当然没这么脑残的需求,这里只是举个例子,可能有其它的功能可能达到这个值,也可能是玩家作弊,在协议中修改了数据),这个时候,在内存中,需要花费的总钱数就是一个随机的负数,我们在扣钱时,计算减法,这个时候负负得正,相当于加钱了。






SQL注入: 目前这个只要不是使用sql字符串拼接,使用mybaits或preparedstatement都可以有效防止;如果是sql字符串拼接,可以在进行拼接前对玩家输入数据进行特判, 如果包含敏感符号则反馈非法输入。






设计bug: 举个例子,在地下城与勇士中,据玩家称,在游戏中,角色的装备是需要用包裹来存放的,不过目前角色的包裹最多只有48格,也就是只能存放最多48件装备。漏洞就是利用包裹的有限空间,存放47件装备(存放满了又无法开罐子),只留下一格空位,而在开“云幂袖珍罐”出装备时,就会因包裹空间不足,而导致开罐失败,而罐子还存在。玩家继续开罐,直到出现金币,但金币不会占据包裹的空间,因此开罐成功,然后罐子消失。发现这个漏洞后,部分玩家狂刷游戏币,然后马上在第三方交易平台出售游戏币,兑换成现金。这种类型的bug最不容易发现,一般只有靠运营数据监控。






并发请求的漏洞: 在游戏中,多数都有道具出售的功能,比如直接卖到商店,以及从商店买入材料和物品。当玩家同时买入和卖出两个操作,瞬间大量并发请求,在服务器处理的逻辑如果两个进程或线程处理,卖出时,先取出当前有的金币500,加上卖获得的金币,写回共享数据,买入时,先取出当前数据500,减去所扣金币,写入共享数据,在大并发时,这两个操作都可能会导致金币的共享数据错误,比如卖出的时候,买入的操作也来了,都取出了当前数据,然后都执行写入操作,这时,只对500执行了一次操作,买入操作后,又写入了卖出时以500计算的数据,这时候金币不仅未减少,还增加了,或者反过来,不管理怎么样都是错误的。所以针对此类数据的操作,都要做好并发处理操作,保证此类操作按顺序执行。比如同一个用户的类似操作都放到一个进程服务或线程处理,保证顺序性。






解决逻辑bug的大招,做好数据的监控和日志。日志是最可信的,也是以后分析数据和恢复数据的依据。所以,对于游戏中所有数据的更新,都要有日志记录,做到数据可查,可回滚。


评论
林
学习了
2023-04-04
10
评论 抢首评