对于任何一款要长期线上运营的游戏,防破解防外挂是必不可少的。本文总结了手游常用的防破解防外挂技术方案,这些方案都经过了笔者所在团队和线上项目的长期考验。很多方案来自于弱联网手游项目,但大部分思路也同样适用于强联网游戏。以Unity为例,但思路也适用于非Unity项目。笔者尽可能做到总结全面,希望能帮助大家形成一个整体的防御思路。
强联网游戏的特点是很多逻辑在服务端计算,重要数据由服务端控制,客户端多数时候着重于表现。而弱联网游戏因为要求玩家能在不联网或网络环境很差的情况也能正常玩,所以客户端可能包含了很多重要的游戏逻辑和数据,服务端则提供一些额外的业务逻辑,比如作弊校验,数据同步,排行榜,各种联网活动等。如果我们信赖客户端的逻辑和数据,那么一旦客户端被破解,整个游戏就会被操控,轻者损失了部分玩家,重者会污染游戏的整个生态环境。最麻烦的是,破解者只要有代码,本质上被破解就只是个成本和时间的问题。但是,我们仍有各种方式来抵御常见的破解和外挂。对于那些根本上很难防住的破解方式,我们至少能大大增加其破解成本。
本文从两方面来总结:客户端和服务端。这篇先讲客户端,分为几个章节:
- 加固
- 内存加密
- 代码混淆
- 破解apk
- 资源加密
- 玩家存档加密
- 时间防作弊
加固
加固是对代码做各种形式的变换,比如加密,混淆,隐藏等,以提高代码逆向的难度。这是所有游戏都通用的一个技术,有不少公司提供了成熟的解决方案,比如网易,腾讯,乐变。已有的加固技术包括:
1 加壳
目的是防止二次打包。对加壳后的apk包重签名,进游戏时会闪退。
加壳分两种方式:
(1)dex加固:比较成熟,很多厂商采用的解决方案,比如乐变。
(2)so加固:比较新,网易易盾用的此方案,native层加密,更安全可靠。
2 反调试
目的是防止IDA动态调试。
这部分没什么需要过多考虑的,建议直接从这些成熟的解决方案中挑选一个应用于项目。
内存加密
网上有一些内存修改器可以搜索和修改内存数据,从而实现各种夸张的效果,比如金币无限,血量无限,攻击力无限等。常用的工具有八门神器,葫芦侠,烧饼修改器。他们的使用原理都是类似的,比如,若要修改玩家当前的金币数,先用工具在内存中搜索当前的金币数值,会搜出来很多内存地址。然后消耗一些金币,在之前的内存地址中再搜索当前的金币数,得到较少的匹配地址。重复该步骤,直到只剩一个地址匹配,就是存放金币的内存地址。最后,通过工具更改该地址存储的数值,就能把金币数改成一个很大的数值。
要防止这种工具的破解,就需要对内存数据做加密,让工具搜索不到该数据所在的内存地址。最简单的方案是:
1 准备一个key值,不要用字符串明文,得是运行期动态生成的。
2 存数据时,先把数据和一个key做异或操作,再存到内存。
3 读数据时,把从内存读出的数据和同样的key做异或,返回给上层。
该方案简单高效,能防住大部分内存修改器,但有一些搜索功能比较强大的工具,比如烧饼修改器有模糊搜索功能,仍能搜索到经过加密的数据。于是我们需要一个更强大的方案。
由于这些内存修改器都是在搜索到的内存地址集合里再次搜索筛查,所以只要不停地变换数据存储的地址,就能从根本上防住这种修改器。具体做法是:
对于任何一个需要加密的数据类型:
1 分配N个同类型元素的数组,N至少为3。
2 每次存储数据时,数组index加1,若超出数组长度则index归零,然后将数据和一个key做异或,得到加密数据,将其存储到该index指向的数组槽。记录下当前的index和key。
3 读取数据时,根据存储的index,读取数组槽中的数据,和key做异或,将结果返回。
实测下来,经过这样的处理后,烧饼修改器也完全无法搜索到其内存地址,所以能有效防住这种类型的工具。该方案听说在腾讯内部项目里使用了,笔者自己在Unity里实现了一套加密数据类型,可直接拿来在项目中使用,放在Github上[1]:
https://github.com/nichos1983/CSEncryptType
该代码实现的要点:
1 用泛型尽量精简了代码。
2 实现了类型转换的操作符,这样能最大程度简化已有项目的重构,比如若要将基础数据类型更改为加密数据类型,只需要更改变量声明处的类型,比如将int改为EncryptInt,其他的上层代码不需要做任何改动,自定义的类型转换操作符会帮助编译器处理剩下的工作。