目前市面上各家加固厂商在对普通App的加固上已经做得比较成熟稳定,而且强度也很高了。但是似乎没有一个针对xposed插件加固的方案,笔者在试用了几家加固后,均会导致xposed插件的崩溃,要么就是插件功能失效,又或者运行性能大大影响导致App拦截的时候巨卡。
迫于无奈,对于本身就是安全从业者的笔者来说,只能自己动手想办法来解决了,以下提供两种思路,简单的demo笔者也已跑通了,但是可能稳定性上还需要打磨,这个需要大家自己去研究了。
当然,如果市面上已经出现了成熟的xposed插件的三方加固方案,也可以购买使用,毕竟现在是资源整合的时代,没必要每个环节都自己亲力亲为去开发实现,专注自己的业务就好。
第一种方案:内存动态加载模式
众所周知,xposed插件本质上还是一个APK文件,只不过会在assets目录下通过配置xposed_init来指定一个HOOK入口类,其实这个入口类还是在APK包的DEX文件中,我们想办法保护这个DEX文件即可。
如果把原包DEX文件加密保护起来,那xposed的HOOK入口类势必就会找不到,出现运行时问题。此时,我们对应地也把xposed_init中指定的入口类替换掉,替换成加固壳中的某个类即可。
当xposed插件注入发生时,首先进入的是加固壳的类,这样我们就接管了控制权。做一些初始化操作,动态解密原包DEX并动态加载,然后再把控制权交还给原始HOOK入口类,这样后续的HOOK操作便能保持有效了。
从直观上来看,加固后的xposed插件中的DEX文件已经不是原包的DEX了,所以在一定程度上保护了代码,而且也不能反编译,及时反编译,显示出来的代码也是外壳的代码,xposed插件的核心代码逻辑是被加密保护隐藏起来的。
而动态运行时,xposed插件的HOOK功能没有丢失,还能继续有效。
综上,这是一个可行的加固保护办法,能在一定程度上有效防止xposed插件被分析,核心代码被窃取的情况。但是,内存动态加载的方法还是有办法可以脱壳的,在运行时容易被dump出原始的DEX文件,强度上需要提升,下面我们看第二种方案。
第二种方案:java2c的native化模式
顾名思义,java2c实际上就是将原本DEX中可以被反编译为Java的代码,转换为native层的二进制指令,并不是源代码上将Java语言转变为C语言。
再通俗点讲,就是把DEX中的代码转变为SO文件中的二进制指令。
java2c的思想来源可以大致说一下:
最早的时候,Android机器的硬件性能有限,加之App通过Java语言编译而成,代码需要在Java虚拟机上跑,造成了大量的性能浪费,因此早期的安卓手机和App使用起来都不太流畅。完全没法和直接使用object-c语言开发App的iPhone手机相比,安卓手机的体验还是很糟糕的。
后来可能谷歌也发现了这一问题,于是在Android4.4系统开始尝试ART模式,在这个模式下,系统会先把DEX文件在一定程度上转换成与本机器指令关联度较大的ELF文件(可以简单理解为类似SO文件的二进制),这样速度就有了一个很大的提升。
在把手机切换到ART模式后重启,会发现系统会将所有App进行一次转换,这个转换过程就是在进行代码的二进制转换,是比较耗时的。
其思想本质是:把App运行时的耗时浪费,一次性在搬到转换阶段,所以转换阶段比较慢,但是一旦转换成功,后期再运行App就比较流畅了。
这个思想很好,但是有没有发现很蹩脚?那就是为什么在客户的终端手机上进行本地翻译转换,而不是直接在开发的时候打包阶段就转换好呢?
这个就涉及到Android生态的问题了,当初Android为了迅速抢占移动操作系统的市场,导致了市面上碎片化的机型非常庞大,庞大到谷歌也无法掌控的阶段。怎么个无法掌控呢?那就是如果在编译阶段就转换(可以假想为更改DEX包格式),直接编译打包一个接近native的二进制包,谷歌自己也无法保障能稳定地跑在那么庞大的碎片化机器上。如果导致不稳定,它会损失大量的移动操作系统市场和口碑,所以谷歌不能那么干,必须求稳,哪怕安卓机器没iPhone那么流畅都没有关系。
所以最终的策略是,让终端机器自己编译,这就是为什么切换ART模式的时候,系统需要进行App的转化处理,这个过程就是终端机器自己编译的过程(准确地说是转换翻译操作)。而且,还必须是双模式同时存在,即ART模式和dalvik模式共存,一旦ART模式下翻译转换失败或者转换的App运行有问题,用户还是可以随时切换回稳定的dalvik模式下。
随着时间流逝,Android系统版本已经迭代了好多代了,ART模式也逐渐稳定成熟了,而且确实对App的性能有实质性地改进,所以在Android的后续系统版本里,就一直默认是ART模式了。
而Android系统的稳定,以及Android操作系统市场占有率的稳定,导致了上面的问题又一次显露出来:为什么不把终端机器上的翻译过程拿出来,在打包阶段做掉?
基于这个想法,最早出现的就是加固厂商的java2c模式,就是把dex中的代码转化为native指令,抽取到SO中去跑。
很多年之后,才是华为的方舟编译器的催生,这个我们后面再说。先把xposed插件加固的java2c加固方案说完。
java2c的具体实现方案,就是参考早期ART模式下的转换过程,把这一过程从用户的终端机器上拿出来,放到加固的阶段去做掉。由于目前Android手机的CPU架构基本上都比较常见了,完全不是大碎片化时代的样子,所以兼容性就有了保障,转换后的指令很容易就能兼容,并且能稳定地跑起来。
然而java2c加固只是借助了上面的转换思想,因为目的是提高保护强度,不是追求性能,所以java2c在加固上性能虽然相比VMP加固(这个是另外一种加固模式,此处不展开讲)有很大提升(对应地,体积会变得大一些,也算是拿空间换了时间),但是还没有达到性能极限,而这个性能的极限谁来追取?那就是手机厂商,这就是后面要说的华为方舟编译器。
上面反复说过很多次,为什么不把终端机器上的翻译过程拿出来做掉?
拿出来放到哪个阶段去做呢?
如果是拿出来放到加固环节去做,那就是加固厂商需要做的事情,也就是java2c加固方案。
如果是拿出来放到编译器阶段去做(例如AndroidStudio打包的时候就是一个性能优化后的包,这是极好的),那就需要谷歌和IDE等诸多厂商去推,但是可能会比较难,因为你再次更新的格式是需要各个大的手机厂商认的,也就是它们的手机上要能跑的起来才行,即使谷歌单方面去推,那也可能是和之前的“ART模式同dalvik模式并存”类似,出现一个“NEW模式与ART模式并存”下慢慢过渡。
上面谷歌没做的,华为去做了,想要做这件事,需要很多条件的成熟。最重要的就是手机的市场占有率,华为作为一大手机厂商,应该是有足够的底气来去做的。然而华为做这个目的是什么?上面也反复提到了,那就是对性能最大化的追求,并依此来巩固和提高自己手机的市场竞争力,从而进一步提高自己的市场占有率。
这也是为什么,方舟一出,慌了其他手机厂商。小米亮出“悬浮计算器”对抗华为的“方舟编译器”,从这一点上看,小米应该是看清了华为的套路,所以也是没办法,总比眼睁睁看着一批批用户流失好吧,你总得对得起自己的品牌和粉丝。
所以对于开发者来说,没有必要慌,也没有必要去研究这个方舟编译器,当然有兴趣的除外。因为它的出现不是革开发者的命,也不是革谷歌的命,它是革其他手机厂商的命,它没有那么伟大,仅仅是提高自己竞争力的一张牌而已。
笔者爱国,笔者用小米,不喜勿喷。但是笔者同时非常看好华为,不是方舟编译器本身,而是方舟编译器背后透露的讯息:那就是华为舍得在基础技术上的研究投入,在资本逐利的时代,一切都向钱看,向眼前的利益看,很少有企业能布局长远,花费巨额进行技术研究,透过方舟编译器,它影射出华为是一家舍得投入进行基础技术研究的公司,所以它仍然是一家伟大的企业,也非常期望它走得更远!也希望国内的其他企业如同华为一样,做大做强!
所以无论从哪一方面讲,小米都得打出一张牌出来,否则只能被甩掉,无论是基础技术还是市场。
但是笔者觉得,这件事,最好还是谷歌发起,就像当初ART模式一样,有两个模式的过渡,来进一步提高Android系统的优势。当然华为这么做的话,也会有一个继续兼容主流App格式的模式。
结尾:
以上介绍了两种加固思路,demo笔者也已经跑通了,当然还可以结合字符串加密、防动态调试、防反编译、多种加密方法结合、增加花指令垃圾指令等辅助功能来增加强度。