Unite 2019 Shanghai:一步解决 Unity 游戏新安全风险

作者:转载小公举
2019-05-17
8 4 1

会议主题:技术专场 2
会议时间:2019 年 5 月 12 日
会议地点:上海国际会议中心

5 月 10 日晚,Unite Shanghai 2019 Keynote 主题演讲于上海国际会议中心盛大举行。从最前沿的技术到令人惊艳的 Made with Unity 案例,整场 Keynote 无不体现了 Unity 技术的每一个进步,都是服务于每一位创作者,以技术致敬每一位创作者 -Unity for all!

我们选择了一些演讲实录在这里分享给大家。这一次是是腾讯游戏安全的陈小虎老师和 Unity 高川老师为我们带来的《一步解决 Unity 游戏新安全风险》。

演讲内容

主持人:最后一场分享由腾讯游戏安全的陈小虎老师和 Unity 的高川老师为我们带来《一步解决 Unity 游戏新安全风险》,大家欢迎!

陈小虎:大家下午好!很高兴今天以讲师的身份参加 Unite Shanghai 2019 的技术分享。今天的分享由我和 Unity 的一位同事一起,我负责技术环节的分享,高川负责后面 demo show 环节。

先做个自我介绍,我叫陈小虎,目前就职于腾讯游戏安全团队。我们团队主要负责腾讯游戏反外挂相关的工作。近年来,我们也向外部的游戏厂商输出了我们的安全服务和能力。我从事游戏安全行业已经十多年了,现在主要负责腾讯游戏客户端保护工作,包括代码加密、游戏加固、反盗版、反外挂的内容。

今天我们分享的主题是:一步解决 Unity 游戏新安全风险,那么什么是新安全风险呢?

今天在座的各位大多是从事游戏开发相关工作的。以前,我们经常使用 C/C++ 进行游戏开发,这类游戏会被直接编译为汇编代码,外挂作者在制作这类游戏外挂的时候需要具备一定的逆向经验,不同外挂作者的经验和技术差距非常之大。这是为什么呢?因为外挂作者在编写这类游戏外挂时,需要熟悉汇编代码、各种调试器以及分析工具,这使得学习成本成倍增加。

相比于使用 C/C++开发游戏,使用 Unity 引擎开发游戏,易上手,效率高,成本低。Unity 引擎带来便捷和高效的同时,也引入了一些新的安全风险。游戏逆向分析的门槛也被大幅降低,分析人员的差距被拉平,即使没有太多逆向经验的人,也可以开始尝试制作外挂了。

Unity 支持 Mono 和 IL2CPP 两种编译模式。使用 Mono 编译的游戏,它将 C#脚本代码编译为 IL 中间语言打包到游戏客户端,在游戏执行时候编译为汇编代码。这类中间语言存在容易被反编译为 C#源代码的风险。后来基于安全性和执行效率方面的考虑,Unity 支持了 IL2CPP 编译,大大提升了游戏安全性,但还是存在被攻击的风险。

那么 Unity 游戏新的安全风险有哪些特点呢?

相比 C/C++ 游戏,Unity 游戏的安全风险存在分析工具多、破解门槛低、攻击方法通用化等几个特点。

我们先来看看分析工具有多厉害。C# 反编译相关的工具有很多,比如传统的 ILDASM、PEBroswerDbg、GrayWolf、XenoCode 等。还有功能更加强大、操作更加简单的 ILSpy、DnSpy、Reflector 等。以及内存 Hacking 工具 CheateEngine。这些工具简便易用,很多人 5 分钟就可以学会。我现在就来示范一下,如何使用这些工具完成一个游戏破解任务。

我们前面说到,使用 Mono 模式编译的游戏,会将 C# 脚本代码编译为 IL 中间码,发布到游戏客户端。这种中间码安全性较低,可以被一键反编译,一键修改。

我们使用 Unity 官方的游戏 demo 作为演示。游戏玩法是消灭一只怪可以获得 100 积分,如何提高积分获取效率呢,比如消灭一只怪可以想要获得 9999 积分。如何实现这个目标呢,下面我就开始正式的破解工作。首先使用工具 dnSpy 打开这个游戏的 Assembly-CSharp.dll 文件,就可以看到游戏的 C# 代码了。

你可能会说,这不就是我们开发的代码么?难道是源代码泄露了?没错,就是源代码泄露了。这里不只能看,还可以改。只要我们修改图中所示位置,就能实现修改积分的外挂功能。看一下我们的修改效果,左边是修改前打一次加 100 积分,修改后打一次就可以获得 9999 积分。几乎是零学习成本,就是这么简单。

我们前面提到 IL2CPP,那么 IL2CPP 编译又有哪些风险呢?

使用 IL2CPP 模式编译,游戏的脚本代码没有了,脚本代码被编译成了 Native 代码发布。前面提到的工具都失效了,安全性得到了一定的提升。但是 IL2CPP 编译后会生成一个 global-metadata.dat 文件,这个文件里面包含了大量的符号信息。

如图所示,我们使用现成的工具解析 global-metadata.dat 文件,可以看到大量的源码信息,例如类名、方法名、变量名以及函数代码位置等。

虽然无法看到游戏具体实现的代码,但有了这些信息,依然可以大幅降低逆向成本。例如前面演示的修改积分功能,这次我们来修改游戏初始积分。通过这些符号信息,可以很快找到积分处理的相关函数:Score::Awake 函数。我们只要修改积分赋初值的地方就可以实现修改初始积分的效果。

这里的重点不是修改积分带来的危害,而是游戏的逻辑被暴露以后,非常容易滋生外挂等各类传统游戏会遇到的安全问题。传统游戏中存在的安全风险,比如 FPS 游戏的穿墙、飞天遁地、透视,自动开枪。MMORPG 中的无敌,秒怪,刷金币,脱机外挂等安全问题。当游戏逻辑暴露之后,就更容易被外挂利用,外挂制作变得更简单。还可能存在游戏盗版、替换支付、广告植入、病毒插入等安全问题。

为了解决上述安全问题,我们和 Unity 联合开发了一个 Unity 保护方案:UPS。方案主要做了两个方面的保护:Mono 保护,IL2CPP 保护。Mono 保护的对象是游戏的主逻辑 dll:Assembly-CSharp.dllAssembly-CSharp-firstpass.dll。IL2CPP 保护的对象是 metadata 文件:global-metadata.dat

首先要做的是文件级加密,以达到保护文件的效果。加密前的脚本文件,我们可以看到 Assemply-CSharp.dll 可以被 PE 工具正确的识别。前面演示过,通过这个文件可以直接反编译得到源代码。加密后的脚本文件,PE 工具无法识别出,其他反编译工具也无法反编译。

经过文件加密后的游戏安全性的确是提升了,但是攻击者还是可以从内存中得到明文文件,然后进行反编译。为了防止攻击者从内存中获取明文文件,然后进行反编译这样的攻击方式,我们还做了方法加密。

经过方法加密后,就算攻击者得到了内存中的明文脚本文件,也无法反编译。工具直接报错,完全看不到原始的代码信息。如图中所示,绿色部分都是反编译失败的错误提示。

方法加密之后,还存在很多的字符串信息,比如方法名和类名。于是我们要做进一步加密,把字符串信息也处理掉。加密结果可以看到,代码和类名方法名完全不可读。

除了方法加密,我们在指令方面也进行了保护。经过加密后的 opcode 对应的不再是 Mono 中标准的 IL 指令。比如标准中 0x01 opcode 对应的是 A(ldarg.0)指令,但经过指令加密后,0x01 对应的可能就是 B(call)指令了。乱序后的指令必须通过我们的解密过程,才能被正确的解释和执行。通过以上这些方式,已经把字节码的安全性提高到了一个比较高的 level。

前述内容是关于 Mono 编译的游戏的保护,那 IL2CPP 保护又是什么效果呢?

前面提到 global-metadata.dat 文件中包含了大量的符号信息,我们对此文件进行了多级别加密,使得通用工具解析失败,无法拿到游戏的敏感信息。实现的效果见下图,这里可以看到工具直接报错无法解析。

前面介绍的都是我们核心的安全特性。此外,通过和 Unity 合作, UPS 方案在稳定性和兼容性都得到了进一步的提升。

稳定性方面,UPS 方案是 Unity 原生支持的,不会修改游戏和引擎二进制。

兼容性方面,UPS 方案不会在游戏工程中引入第三方组件。

跨平台方面,我们支持 Win/Mac 版的 Unity-Editor,支持对 Windows/Android 双平台游戏的保护。

保护强度方面,我们提供文件级、方法级、指令级等多级加密,并且每次加密的结果都不一样。

那么这么棒的功能使用起来一定很复杂吧,其实很简单,现在只需要一步就能实现这样的保护效果。和传统的加密方案相比,我们的方案集成到 UnityEditor,只需勾选 Security Setting 页面的 Enable Security Build 复选框就可以了,真正做到零成本接入。

接下来有请 Unity 大中华区企业支持经理高川来为大家演示如何使用这么棒的保护功能。

高川:我们这个方案是基于 2018.2.12f1 版本做的一个先行验证。后续我们会把它推广到更多现行的版本里。我们可以看到,现在是我做的一个非常简单的 Demo 场景,在这个场景里我用一个 button 控制 y 轴,让它上下移动,非常简单,只有一个脚本。可以看一下 Move 脚本里的内容,非常简单的一段代码。我们首先做一个没有经过加密的安卓版本,因为大家也知道,现在最容易受到攻击的版本,基本上就是安卓和 Windows,并且攻击成本是非常低的。我们现在已经 build 出了一个 apk,把它解压一下。拿到 apk 内容后,现在干一点坏事,找到一个工具打开它,比如说刚才介绍的 ilSpy。我一个键都没有按,只是拖动了一下。我们现在在 ilSpy 里能看到的基本就是我的源代码。大家可以做简单的对比,基本上没有什么差别。如果这是一个上线游戏的话,恭喜你,你的代码被攻击者者完全掌握,他可以为所欲为了。

如何避免这样的事情发生呢?我们重新构建一个新的版本,只需要勾选 Enable Security Build。按照刚才的步骤再进行反编译。大家可以看到这个地方是一个红红的叉,告诉你失败了。我们再找其他的工具反编译 Assembly-CSharp.dll,发现其他工具也失败了。可以看到,如果在构建游戏时勾选了加密选项,你的游戏进行加密后,攻击者是没有办法通过反编译工具直接获取游戏脚本代码,从而危害你的游戏。因为现场环境的限制,我这边没有 Windows 平台,实际上 Windows 上 Unity-Editor 有同样的功能。我们现在支持 Windows/Android 平台游戏的加密,包括 Mono/IL2CPP 模式。

大家现在还不能拿到 Security 版本的 Unity,但是我们会尽快把它发布出来,希望大家能随时关注我们公众号上的一些信息。如果有后续信息,我们会在第一时间跟大家进行沟通,也欢迎大家试用。同时也希望大家多关注我们合作伙伴腾讯游戏安全平台,也会有更多的安全设施和安全技术加入到我们 Unity 这边来。我的演示结束了,谢谢大家。

主持人:今天的分享到此结束,谢谢大家参与。