Unity3D引擎AssetBundle包安全研究
背景介绍
Unity3D引擎4.5之后的版本新推出AssetBundle功能,AssetBundle机制支持游戏的热更新,AB包热更新定义为:无需更新游戏客户端版本,通过服务器下发AB包资源便可实现游戏资源和游戏逻辑代码更新。该种方式大大节省了游戏更新所需流量,同时游戏可通过服务器下发AB脚本包更新游戏逻辑代码。
天天来战游戏使用Unity3D 4.5.1引擎开发(Unity引擎使用C#语言开发游戏逻辑),游戏开发过程中大量使用了Unity的AssetBundle机制打包游戏主逻辑代码、资源、纹理等资源,Unity引擎的AssetBundle机制是本文分析和介绍的一个重点内容。天天来战将C#语言的主逻辑Dll的二进制打包为AssetBundle方式的资源包,如此方便实现热更新(无需重新下载APK包,便可实现版本发布和游戏逻辑Bug的修复),通过将主逻辑C#代码以AB(AssetBundle)方式打包的文件之后,主逻辑代码不在Assembly-CSharp.dll模块中,同时天天来战对C#代码编译之后原始Dll文件进行了加密和压缩,来战主逻辑模块还原属于本文分析的另外一个重点内容。
本文分析的内容主要包括两个方面,分别如下:
1、Unity3D引擎的AssetBundle打包方式的实现、脚本逻辑AB包加载运行方式。此块内容可通过下载Unity软件,通过正向的方式了解AB打包、运行相关机制。
2、来战游戏主逻辑脚本的AD包还原为Dll的过程。针对来战游戏AD包还原,则需要逆向分析游戏相关脚本代码,从而最终获取经过解密和解压之后的原始明文Dll。
实现原理
下面将详细的分析Unity新版本的AB包打包及运行机制、来战游戏针对C#逻辑代码预处理方式及AB包过程。
一、Unity的AssetBundle打包的实现
如果需要实现本地的打包方式需要在官网下载最新的Unity3D引擎的游戏开发软件,Unity软件安装之后需要本地安装JDK、Android_SDK(不需要NDK),本文测试使用的Unity软件为4.5.3版本(官网提供的最新Unity版本为5.1,下载地址:http://unity3d.com/unity/download),Unity3D引擎使用C#开发游戏,且跨平台支持得非常好,下图为Unity引擎开发游戏可编译出目前主流平台(Android、IOS、Web、Linux、Windows等)的对应游戏版本:
AssetBundle属于Unity专业版提供的功能(收费版),由于AssetBundle属于Unity公司新推的功能,允许普通用户可免费使用30天,之后需要付费才能使用相关功能。
Unitt环境配置好之后便可按照Unity官方的手册实现资源包的AssetBundle处理,下面介绍如果使用官网的方法(官网链接:http://docs.unity3d.com/Manual/assetbundlesIntro.html)将C#编译之后的Dll二进制打包为AB包。
点开Unity图标并创建新工程之后,需要在工程Assets根目录下创建Editor文件夹,在该文件中创建一个C#的脚本文件,脚本文件中写入的代码如下:
上图的C#代码运行之后会在菜单Assets选项中新增加“Assets/Build AssetBundle From Selection - Track dependencies”名字的按钮,对应效果截图如下所示:
上图红框部分为新添加用于将某些固定资源打包为AssetBundle的按钮,通过该按钮可以实现AssetBundle方式打包任何资源包。上图代码中EditorUtility.SaveFilePanel ("Save Resource","", "New Resource", "unity3d")表明当前需要保存AB包文件后缀名为:.unity3d,其中将资源打包为AB包的关键函数为:BuildPipeline.BuildAssetBundle,通过调用该函数便可将某资源保存为AssetBundle文件。
例如将C#编译出来的Assembly-CSharp.dll打包为AssetBundle方式,首先需要将Assembly-CSharp.dll改名为xx.bytes的文件并保存在Unity工程的Assets目录下,然后在Unity开发界面中点击对应的资源邮件并点击“Assets/Build AssetBundle From Selection - Track dependencies”按钮,之后便可将xx.bytes文件保存对应的AssetBundle文件(后缀名为:.unity3d的文件),下图便是原始的和AB包文件。
其中test.bytes为原始的C#模块改名之后的文件,test.unity3d为利用AssetBundle机制打包之后的AB包文件。
二、C#脚本AB包加载及运行过程
Unity的官网链接:http://docs.unity3d.com/Manual/scriptsinassetbundles.html详细介绍了AB包在游戏中如何加载并运行。本文主要介绍脚本编译之后为Dll文件的AB加载和运行过程,脚本DLL处理为AB包加载并反射运行机制在IOS无效(由于IOS不运行动态发射和运行C#代码),具体包括如下三个流程:
1)、将AB包从网络下载(或者加载本地的AB包),通过www.LoadFromCacheOrDownload函数便可将AB包从网络下载到本地,LoadFromCacheOrDownload函数第一个参数便是网络地址(如果为本地文件则文件全路径全需加入:file://前缀)。
2)、将AB包还原为原始的C#脚本文件,其中利用了AssetBundle的Load函数进行加载并还原。
3)、将还原的C#脚本的Dll文件反射到内存中,利用system.reflection.assembly.load函数可将C#编译之后的文件发射;之后利用assembly.GetType函数获取到对应的Class类型,之后调用GameObject对象的AddComponent函数将GetType获取的Class添加到组件中并供运行。
以上三个步骤对应的完整代码如下图所示:
上图中Url修改为本地路径(前面需加入file://前缀)便可加载并运行本地的C#脚本生成的AB包。
三、天天来战游戏处理方式
1、AB包如何还原为C#脚本文件
AB包的格式和Unity的版本相关,测试4.5.1、4.5.3的Unity版本打包的AB包格式完全相同。如果需要将AB包还原为原始文件,则需要详细分析AB包的格式及压缩方式,AssetBundle分为压缩式和非压缩模式,压缩模式仅仅使用了开源的Lzma库对整个资源包进行压缩。网上已有AB包格式的完全分析,对应链接为:http://blog.codingnow.com/2014/08/unity3d_asset_bundle.html ,AB包的文件格式不复杂,本文不介绍详细介绍AB包文件格式,目前有一款名为Disunity工具可还原大部分的AB包,同时Disunity也可还原Unity的资源包,通过测试发现该工具可解析来战游戏绝大部分的AB包。
2、天天来战对C#脚本文件保护
1)天天来战文件结构
天天来战采用了AssetBundle方式处理绝大部分资源,包括了部分C#脚本逻辑代码。来战游戏主要逻辑代码不在Assembly-CSharp.dll模块中,下图为飞车和来战游戏Assembly-CSharp.dll模块大小截图:
通过上图可看出飞车的Assembly-CSharp.dll模块大小为2.9M,来战的Assembly-CSharp.dll模块大小为411KB,通过反编译可确认飞车游戏逻辑代码在Assembly-CSharp.dll中实现,来战游戏的Assembly-CSharp.dll模块不包含任何逻辑。
飞车和来战游戏的Assets目录下的文件结构差异较大,对应截图如下图所示:
左边为来战游戏的Assets目录文件夹结构,右边为飞车的文件目录结构。通过文件内容查看发现来战游戏中maps、pack等文件夹包含了大量AB包;飞车则采用了Unity常规的Resource方式存储资源包。
2)来战游戏逻辑代码加载方式
通过上一小节的介绍可知天天来战游戏的主逻辑代码不在Assembly-CSharp.dll中,通过利用C#代码反编译工具 (ILSpy、Reflector等)可分析Assembly-CSharp.dll模块的相关代码,通过分析发现Assembly-CSharp.dll模块反编译之后的UpdateManager.cs文件中负责处理主逻辑模块的还原过程,其中关键的加载游戏主逻辑包的处理代码如下图所示:
上图第一个函数为:Assembly Load(string name, AssetBundle bundle)该函数属于加载主逻辑包的上层接口,上图的Assembly Load(string name, AssetBundle bundle)函数代码表明游戏主逻辑模块存在两种加载方式,分别为:AB包方式和常规的Resource方式,当参数bundle为非空时采用AB包加载方式,当参数bundle为空时则采用常规Resource加载方式。其中AB包主要通过网络的方式进行加载,Resource方式则通过本地文件的方式加载,两种方式加载过程类似。
上图第二个函数Assembly Load(string name, byte[] bytes)主要负责还原静态原始代码文件(还原之后的文件一般为C#编译之后的Dll文件,AB包和Resource采用相同方式还原主逻辑模块)、反射C#编译之后的Dll模块代码以供执行C#对象相关代码(上图的“Assembly assembly = Assembly.Load(array);”代码负责反射C#编译之后的Dll模块代码)。
3)来战游戏还原主逻辑模块过程
天天来战开发方针对游戏的主逻辑模块的正向处理过程(C#脚本逻辑代码处理过程)为如下图所示:
通过上图可看出游戏开方法对C#脚本编译之后的逻辑Dll模块进行了压缩和加密处理,游戏在加载运行AB包过程中需要相关的解密和解压,相关的代码在Assembly-CSharp.dll模块反编译之后的UpdateManager.cs文件中,具体功能实现的相关函数定义为:privatestatic byte[] PostLoad(string name, byte[] bytes),对应的关键代码如下图所示:
通过上图处理过程可知,游戏在加载AB包(或者Resources包)时,需要对游戏进行还原操作才能反射编译之后的Dll模块,还原操作包括了:解密和解压数据。
3、来战游戏主逻辑模块提取
以上分析了游戏运行AB包(或者Resources包)时解密和解压处理过程,下面给出游戏主逻辑模块提取过。
天天来战游戏主逻辑模块分为两部分,分别为:游戏本地的主逻辑模块,通过Resources方式打包;网络传输方式的逻辑AB包,通过网络下载并加载运行。两种方式的逻辑包运行方式方式类似,下面介绍本地逻辑模块(Resources方式的文件包)的还原过程,网络部分的AB包还原方式完全类似。本地逻辑模块的还原过程分为两个步骤,对应的相关步骤如下所示:
1)Disunity还原Resources包
来战游戏本地的主逻辑模块存储于游戏根目录的assetsbinData中,对应MD5值的文件名,文件名为: 7ef81effc1c8a4a64800bb6ff544aab0,对应的文件如下图所示:
7ef81effc1c8a4a64800bb6ff544aab0文件可通过Disunity工具进行解析(该工具已在附件中,工具中有详细的使用说明),Disunity是通过Java写的,需要安装JDK,通过Disunity解压之后还原的密文包如下图所示:
Disunity还原Resource包之后的文件名为dump.txt文件,如果游戏开发方未对模块进行保护该阶段还原出来的文件为原始的明文模块,但由于来战游戏私有化加密和压缩了原始的C#脚本Dll模块,需要后续第二步的还原过程。
2)原始模块解密和解压
通过UpdateManager.cs的PostLoad函数代码可知:游戏首先采用异或伪随机Key(文件名的hash计算伪随机的Key)解密数据,然后采用zlib库提供的压缩库进行解压还原数据。测试过程中使用了Unity的C#写代码解密解压还原原始模块信息,其中需要将游戏的Ionic.Zlib.CF.dll模块包含于Unity工程的Assets任意目录下,此时便可调用zlib库的压缩算法。对应的完整性解密代码如下图所示:
通过以上代码可解密解压游戏的主逻辑模块(包括本地的Resources包、网络的AB包),在运行以上代码之后解密出来的文件便是C#代码编译之后的Dll模块。
总结
本文详细介绍了Unity新版本推出的AssetBundle机制、AB包的生成,分析天天来战的主逻辑模块在游戏中的处理过及原始明文模块的还原。AssetBundle机制属于Unity3D引擎公司在4.5版本中主推的功能,该种机制支持游戏逻辑代码的热更新。目前使用Unity引擎开发的游戏越来越多,后续游戏开发方也会大量使用Unity的AssetBundle机制。
本文以天天来战为实例分析AB包处理方式,天天来战采用了AssetBundle机制开发游戏并实现热更新,同时游戏对原始二进制Dll模块进行了加密和压缩保护,本文逆向分析来战游戏AB包加载和运行机制,通过分析解密、解压处理方式可还原为原始的Unity的C#逻辑代码。