(3)用IDA打开libil2cpp.so,先运行script.py或ida.py添加各种符号的可读信息,若是ida.py,还需要选择script.json。这时各种类和函数都具有了可读的字符串名字。找到需要破解的逻辑地址,修改汇编代码。
(4)将修改后的代码导出为新的libil2cpp.so,覆盖解包目录下的同名文件。
3 重签名打包
(1)运行命令:
keytool -genkey -keystore mykey.keystore -keyalg RSA -validity 10000 -alias mykey
得到mykey.keystore文件。
(2)运行命令:
apktool b abc
得到abc.apk文件,位于目录abc/dist/。
(3)运行命令签名打包:
jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -keystore mykey.keystore -signedjar abc_signed.apk abc/dist/abc.apk mykey
得到新包abc_signed.apk。
网上有些教程里会加上-tsa参数,测试下来会导致报错:
jarsigner error: java.lang.NullPointerException
上述破解方式的关键还是在于读懂反编译或反汇编的代码,找到关键逻辑代码做修改。破解者可能会搜索user,level,coin这种常见的关键字,进而很容易就找到关键逻辑。所以,我们可以尽量混淆这些关键类名,函数名,变量名等,改成一些难读懂甚至具有误导性的名字,就能增加破解的难度。但是,如前面所说,这些都只是增加了破解难度,只要有代码,破解就只是时间和成本问题。
针对这种破解方式,有些安全方案对这些静态文件做了保护。mono模式下,对Assembly-CSharp.dll做加密,改变了PE文件格式,使得反编译工具无法识别。il2cpp模式下,可对so文件做加密,或对global-metadata.dat符号文件做保护,使得工具无法还原出符号信息,也增加了破解难度。
资源加密
普通的未加密的ipa和apk包,我们可以用工具解包,很容易得到资源的明文形式。对于Unity包,可以用资源查看工具(比如AssetStudio)解出Resources目录下的资源和各种AssetBundle资源。所以我们需要对资源做加密,以保证至少无法用工具简单地解包。
一般Unity项目的很多资源都打成了AssetBundle,所以需要对AssetBundle做加密。很容易想到的方式是:
1 构建打AssetBundle包时,对资源做对称加密
2 运行期加载时,先把AssetBundle加载到内存,用key解密,得到解密后的AssetBundle内存
3 调用AssetBundle.LoadFromMemory(Async)接口从内存中加载资源,初始化对象
这一切看起来很清晰完美。但不幸的是,用AssetBundle.LoadFromMemory(Async)加载资源,会导致内存使用量暴增。一份资源通过该接口加载,会在内存里出现三份拷贝,除了资源本身在系统层或GPU层有一份,还会在Native层和托管层里各有一份。如果是LZMA格式,会先解压缩再存储,内存消耗比资源原始资源尺寸更大。所以,官方其实不推荐使用该接口[3]。
那么,还有更简单的方式吗?也有,UWA提供了一个加密方式[4],通过给AssetBundle文件内容加一个偏移,就能做到无法用资源查看工具直接读取其内容。该方案的优点是简单高效,不耗额外内存,但缺点也很明显,它的防护强度很弱。
除了AssetBundle,ScriptableObject资源也没有简便的加密方式。所以,Unity在设计上就没有很好地支持资源加密,可能是因为国外没有我们国内市场的一些困扰。Unity中国团队针对我们的国情,出了个Unity增强版,接口上直接支持了AssetBundle的加密,使用起来很简单[5]。是否合适好用就由大家各自判断了。