随着各类游戏在国内和国际越发火热,外挂和打金工作室等黑产业务也愈演愈烈;
阻止外挂和黑产的主要手段,从攻击产生的时间上可以分为:
一个优秀的游戏厂商,会全面发展上述三种技术手段来对抗外挂黑产。在这三者中,事先防御手段,对于游戏业务的代价较低,可以在造成实际损失之前从服务器侧解决漏洞问题。
一款游戏在开发完成上线测试前,可能存在一些日后导致严重危害的安全漏洞如:协议漏洞、服务器宕机、内存变速、隐私合规等。
游戏安全评审就是为了防止游戏在线上运营阶段因以上问题产生安全事件而进行的工作,旨在提前暴露游戏安全风险,完成漏洞修复,不给外挂黑产以可乘之机。
提到协议安全,大家肯定会首先想到协议fuzzing,这也和我们最初的测试思路一致
由于游戏服务器和客户端一般都采用了特殊的格式进行通讯,并且报文内容和报文外部的封装都进行了复杂的序列化与加密,所以直接向游戏服务器发送大量的fuzz二进制数据流绝大部分会被服务器的网关过滤掉;因此我们需要对游戏协议进行重建。
协议重建即在摆脱游戏操作场景的情况下,对游戏的数据流进行完全模拟和重新封装,构建一个合法的游戏链接,发送经过fuzz字段覆盖的测试协议,通过返回包或者游戏表现来确定是否有安全漏洞。测试流程如图所示:
游戏业务对于登录态和存活链接有校验,所以我们需要根据游戏校验逻辑的不同,来执行针对性的登录态和心跳模块。
大部分游戏要求的登录态都是唯一的,也就是说,在开启测试工具的同时,原本客户端上的游戏登录态会被挤下线,所以对于测试工具新建立的登录态,我们要保证:
虽然登录态、密钥交换、心跳包的维持比较费时费力,但是在这个过程中可以发现很多安全漏洞,例如:不合理的密钥交换甚至固定密钥、固定的登录口令、多登录态等严重问题。
由于游戏业务在通信上有别于常规业务的特殊性:很多特殊的协议及其生效判定只有在特定场景和环境下才会生效,可以理解为只有正确的协议链条,才会得到服务器正常的反馈和数据;于是我们在进行如下改进。
虽然这个过程较为耗时,但是可以发现很多在游戏里不会触发的隐藏BUG, 这类问题主要是使游戏进程不按照预定的调用链来进行通信,从而产生的多协议组合漏洞。例如在战斗中进行跳关和在商店中开启战斗等;
对于重建协议的方案来说,大部分的模块都是通用的,尤其是协议解析和序列化的部分;但是在个别情况下,除了采用常见的protobuf、json等数据系列化格式外,游戏还会使用自研私有DSL协议。
针对自研协议格式的游戏,我们进行了一些研究,发现大部分私有协议格式本根同源,对于将明文数据序列化成二进制流的关键点上的差异不大,于是我们对这些通用的部分进行整合,开发可以通用的序列化与反序列化接口,设置几个可选参数来适应不同的格式,这样就可以适配大部分的私有协议了。
OK,到这里为止,我们就可以对绝大部分接入安全评审的游戏进行协议安全测试了,但是该方案还存在一些问题,看到这里,相信有的同学已经发现了这个方案中的一些不足。
根据评审工作的发展,发现本方案存在的几个问题:
这些问题都是由于本方案的局限性导致,那么,我们就要考虑在后续的方案中进行升级;
我们在上一段提到了,新方案主要是为了提升漏洞发现的效率,所以我们首先需要解决的问题是不能直观的看到漏洞在游戏中的表现效果。为了达到这个目的,就需要一个可以不脱离客户端的测试方案:将数据流的起点和终点定位到客户端和服务器,而测试工具通过中间人的思想,在信道中进行测试操作,不关心客户端上的实现和协议链。
设计的大体模型如下:
为了实现tcp中间人的操作,我们调研了使用 iptables&pfctl 进行端口转发和使用代理进行流量转发等几种方式,经过一段时间的摸索和验证,最终采用了socks5代理服务器的方式来完成数据流的转发和中间人操作;
选择socks5代理的原因是它很简单,由于其不关心协议种类,只是简单的进行数据包的传递,所以socks5速度较快,不会影响到游戏的正常运行,另外socks5代理对测试客户端的环境没有特殊要求,相比端口转发的方式更加轻便和快捷。socks5代理方式的工作流程示意如下:
我们使用了一个socks5的代理客户端,并在测试端上搭建一个代理服务器,测试逻辑主要运行在代理服务器中;预期本次设计的方案,其核心架构图设计如下:
现在我们已经可以通过新的工具方案用代理的模式正常的进行游戏了,并且还可以在工具中看到游戏的上下行数据流,但是为了达到测试目的,我们还需要将数据管道中的数据拿出来,经过修改后再写回数据管道,这里的解析和封装模块与上一个方案一致,不再详细展开;
我们通过对本地测试文件进行改写,来控制测试的流程和目标,现在我们的新方案已经初步成型了:
但是我们目前还有一些问题存在:
中间人方案能进行测试的协议都是由游戏客户端发起的,例如购买商品,所以无法绕过客户端的限制比如金币不足时无法发起购买,但实际上游戏服务器中可能存在金币不足购买不扣费的漏洞;简而言之,就是只依赖中间人方案测试覆盖面不够:虽然可以覆盖到全部的游戏协议,但是不能覆盖游戏在特殊情况下存在的问题;
我们希望,可以在任何情况下都可以主动的发起对一条协议的测试,从而绕过客户端上的逻辑限制,深度挖掘游戏服务器中的问题(还是那个原则,客户端不可信)。这里就有一个很巧妙的办法:协议劫持。
而劫持的具体做法是:寻找一条简单的,不会受到客户端逻辑限制的协议,比如游戏里的动作、表情等。因为这类协议对于游戏安全来说是冗余的,安全可靠的。
使用中间人工具进行测试的工作流程如图所示
基于中间人的测试方案可以在游戏客户端上直接看到bug的展示效果,这很大程度上提升了漏洞的发现效率;但相对于上一代方案来说,还是有两点问题没有解决:
以上问题的产生,都是由于协议解析类方案的局限性而导致的,想要进一步发展,就需要从根本上抛弃原有的思路。
为了解决以往方案中的不足,我们准备对测试方案进行重新设计,结合以往的测试经验,重新设计的方案的目标是:
1、不在数据流层面进行操作,较少的在协议层面进行操作,直接操作可以看到的明文数据内容。
2、测试协议的组件过程尽量在加解密之前,因为加解密方案是多变的、难以固定适配的。
3、可以直接在客户端上看到漏洞的展示效果,不需要脱机运行。
4、可以方便的主动发起测试协议,用非劫持的方法来向游戏服务器发起测试协议,并且不需要对游戏本身的包序号进行管理。
5、测试工具在前端展示完整解析的协议格式和数据,进行测试数据推荐和fuzz自动覆盖。
为了达到以上的设计目标,我们放弃了原有的协议方案,通过代码注入的方式来开发一款新的游戏安全漏洞挖掘工具——GameDancer。
设计的主要思路是通过hook游戏逻辑中协议交互的接口,拿到游戏的交互明文数据,对数据进行解析后,通过主动调用游戏接口,将测试协议通过游戏客户端发往游戏服务器。
说的简单一点,对于这套测试方案来讲,游戏客户端就是一个给我们工具提供协议解析和协议发送服务的模块,将这部分复杂的工作通过游戏本身来自动化的完成。其整体的设计模型如下:
工具前端是与安全评审人员主要的交互场景,也是决定使用效率的地方。为了提高漏洞的发现效率,我们需要完成以下目标:
前端主要和核心框架中的hook引擎进行数据交互,数据交互流程较为简洁;
加载模块主要提供注入游戏的功能,需要将包含游戏脚本hook引擎功能的库文件加载到游戏同一个进程和命名空间下,使我们自定义的游戏测试逻辑可以注入到游戏上下文当中,从而对游戏原生逻辑进行修改和操控;
加载器有两种技术选型,一种是针对root环境,使用附加Zygote的方式来完成注入;另一种是针对非root环境使用虚拟多开技术进行注入,root模式对测试终端的环境有一定要求,但是其适配的游戏范围广;非root模式不额外需要root环境,但是对于有些游戏和机型需要特殊的适配。
注入Hook引擎的流程如下:
hook引擎中包含了针对各类游戏引擎及其技术选型的hook支持,可以对游戏脚本进行注入和替换,是该套技术方案中的重点部分。
以Unity3d引擎mono选型为例,主要方案是借助libmono.so本身的接口和Mono的JIT机制;
逻辑层是控制游戏运行逻辑和游戏协议管理的核心,在逻辑层我们需要完成以下预定功能:
GameDancer的开发,并不只是提供了一款快捷、高效游戏安全评审工具,更重要的,它提供了一个游戏注入平台,根据一定的文档指引,相关人员可以开发自己的游戏插件来控制游戏的逻辑走向、观察游戏实时性能数据、了解游戏BUG产生的详细情况;这些也是我们对工具未来发展的期望;
各个方案虽然是一代代的演进过程,但是旧方案并不会被淘汰,每一个技术方案在漏洞挖掘的过程中都有其独特的用处:基于客户端注入的方案虽然高效。但是无法发现游戏加密方案和登录流程在设计上的安全问题,这类问题仍然需要通过协议重建来发现。
在安全评审工作中,我们会采用多类型的工具对游戏进行多角度的安全评估,保证在评审覆盖的过程中不留遗漏。
“工欲善其事必先利其器”,如你所见,通过技术方案的不断演进,游戏安全漏洞的挖掘效率和漏洞质量得到了提升;从最开始为了快速开展安全评估工作的协议重建方案,到后来可以直观看到漏洞效果的中间人方案,再到GameDancer方案,每一次的方案升级都是为了更好的解决游戏安全评审业务眼下所存在的问题和局限。
游戏安全评审是一项繁琐的工作,漏洞的种类及其产生条件、原因不一而足,想要尽可能全面的覆盖一款游戏中的安全问题,需要评审人员耐心与细致的检查,更加需要技术能力和高效工具的支持。在游戏安全评审技术方案的进阶之路上,“追求极致”是激励我们不断前进的动力。
如果你也想参与到我们的游戏安全工作建设中来,体会在各类游戏中发现漏洞的乐趣、享受和黑产外挂斗的其乐无穷,欢迎加入无恒实验室,简历投递:https://job.toutiao.com/s/ee1qpHt ;或者通过二维码直接投递: