稀有猿诉

十年磨一剑,历炼出锋芒,说话千百句,不如码二行。

Android逆向技术高阶大法

安卓应用是一个客户端,与传统软件类似,需要把软件打包,然后通过某种渠道(应用市场)分发给用户,这是常规的发布方式,它的更新节奏很慢,从你在应用市场上更新后,到用户真正的执行升级,这中间很慢的,而且很多用户根本不会升级新版本,这对于互联网来说是极不友好的。传统的互联网,用户刷新一下网页后,就能看得到更新了,但对于客户端,这行不通,要想实现小时级别的发布和分钟级别的问题修复,正规的发布渠道是做不到的。于是各路大神和专家开始研究客户端的前端化,也就是运用各种技术能让发布,特别是一些问题修复性的小规模发布可以更快的传递到用户手中。

这与正向方法不一样,谷歌或者水果针对 应用市场有明确 的流程的,这是常规发布也即是正向方式。今天我们来聊一聊非正向方法,非常规方式,来实现小模块的发布和热修复。

核心技术原理

任何一项技术都离不开编程语言和操作系统上的支持,对于插件化技术来说,最为核心的原理就是Java支持反射,这是一种运行时修改代码的技术,另外就是动态代理,这是插件化可行的根本技术支撑。

说到底,Java仍是一种解释型语言,它的核心是JVM,即也虚拟机,我们所熟悉的Java编程语言,本质上是套在JVM上的一层语法规则,换了一种语言规则也是可行的。就好比Kotlin,Scala和Groovy它们的语法与Java相差很大,但它们编译过后的字节码是完全符合JVM规范的,可以直接运行在JVM之上。

其他的纯解释型语言,如Python和JavaScript,它们在运行时可以动态的加载一段源码,这即是动态化,可以实现真正的插件化,运行时直接加载运行一段代码。Java略变态一些,但它本质上是JVM,而JVM通过反射和动态代理,在一定程度上支持了类似的动态化,就是通过ClassLoader来动态加载一些编译好的Class。

此为插件化的核心原理。

动态代理机制,可以读这几篇文章:

Hook大法

有了核心原理,才有可行的方案。Hook主要研究三方面内容,一是研究ClassLoader,因为不同的dex分属于不同的层级,它们的ClassLoader不一样,反射的第一步就是要能加载到想要的Class,这个要靠找到合适的ClassLoader;二是动态代理机制,hook的核心原理就是用动态代理机制,创建一个Mock对象用以替换掉原来的,所以接口Interface是关键,原系统设计中必须使用大量接口,并且是以标准方式使用的(没有强制向下转型downcast),这样你创建出来的动态代理去替换才是安全的;三就是学习安卓系统核心组件 的流程,以找到最佳的hook地点。

其实,第3条才是对大部分人最为有益的。

具体如何做hook,可以参考以下文章:

由于安卓版本升级的原因,上面这几个文章都失效了,例子行不通了。但是这几遍对于原理解释的还是相当清楚的。

以下文章对于新版本也是适用的。

需要注意的是,hook这件事情,最基础的技术很简单,就通过反射来替换对象,把系统中的对象替换为仿造的,仿造有三种方式,一是直接创建,这需要类是比较简单的情况,并不需要开放出来,通过反射一切皆可创建;二是继续,这个对于复杂对象也能仿造,如Instrumentation,但是需要类是开放出来的;三是接口,通过动态代理 创建仿造对象(也即代理 )。核心技术就这些。其他的,全是对于系统代码的理解,找到可行的关键点来进行hook。

另外就是,谷歌对逆向方法限制越来越严了,反射系统的东西,会有限制,有时仅是打印日志,但指不定哪天就不给反射了。

1
Accessing hidden field Landroid/app/ActivityManager;->IActivityManagerSingleton:Landroid/util/Singleton; (light greylist, reflection)

插件化原理

学习一门技术最好的方式就是去研读优秀的开源库的源码,对于插件化,现在有很多比较成熟 的开源框架存在了,可以挑几个比较有代表性的来研究 一下。

DroidPlugin

这个基于动态代理创建的插件方法,较为流行,里面有大量的hook技术,网络上也有很多解析此框架的文章,可以帮助理解。

它用了大量的hook,优点就是插件本身可以是正常的apk,无太多的限制,就用常规的app开发方式开发就好,这是它的最大优势,因为对插件无限制,所以框架本身就需要做大量的hook,是学习hook技法的良好例子。

DL : Apk动态加载框架

这个是以静态代理为基础创建的插件框架,并没有大量的hook,可以参看它的解析文章

任大神的框架适配性较好,基本上是纯软件层的技术(静态代理),没怎么hook。当然缺点也相当明显,就是对于插件的开发要求很苛刻,必须实现框架本身自定义的一坨东西,与安卓标准的app开发差异较大,且越来越大,并且对于打包和开发过程并无工具支持,在实际应用过程中较为麻烦。退一步讲,并未有真正达到插化的目的,它对插件的限制较大。

现在已经基本没人用了,不过这属于开山之作。

Qigsaw

这个与其他插件框架的最大差别在于,它最接近于官方的东西(App bundle),它的重点在于项目模块化和打包上面,对于常规理解上的『插件』所做的事情特别少,hook特别少,安装和加载插件的过程比较很简单,接近原生,核心在于它的打包过程。这里有详细的介绍。

另外,包建强的书《Android插件化开发指南》也可以读一读的,书的好处在于,它毕竟是一个整体,从基础的技术原理到hook原理都有讲,还是相当不错的。不过书比较旧了 ,要结合作者的勘误,以及网上的文章一起来消化理解。

热修复原理

除了插件化,另外一个大厂热衷的技术便是热修复,这也是大厂头部应用的标配技术。其实插件化,也能实现热修复,比如某个插件,一般是厂里的一个业务,出问题了,紧急打包发布一个修复的版本,然后更新插件。不过,这略显笨重,相当于用牛刀去杀鸡了,总之就是效率不高。

真正的热修复技术讲究效率,且要小巧,针对 点对点式的修复。它的核心原理就是替换,用反射去替换类(修改dex classloader中的dex顺序),以及对方法的替换(侵入虚拟机中的method表,进行替换),还分冷生效(类替换一般是冷生效,也即下次启动时生效)和热生效(方法替换一般是热的,下次调用此方法时就生效了,因为它并不涉及classloader,无需要重新加载类),还有插桩式的,在代码中直接插桩,先检查有没有patch,有patch就先运行patch(这个思路最简单,适配性也好,但实行难度大,需要对现有代码进行插桩)。

这几篇文章有比较详细的讨论。

具体的热修复工具

xposed派系

也即原生的XposedXposed framework 以及大阿里的衍生版本dexposed

针对 方法可以热生效的hook,当年Dalvik时代,这个东西还是相当牛逼的,时过境迁虽然Art上无法用了,但不妨用来学习。

Andfix

原产自支付宝的与Xposed类似的方法级的hook工具,支持Dalvik与Art,值得使用和学习。

AndroidMethodHook

可以用来学习sophix,sophix是大阿里的东西,把andfix以及dexposed商业化了,不再开源免费用了。这个项目比较接近它们,可以用来学习。

Tinker

微信出品的Tinker,核心技术还是用dex替换实现的class替换,冷生效。

它的重点在于补丁dex的差量生成,以及发布平台,还做成了收费平台,变成一种服务。所以,你看核心技术是由目标平台(安卓)决定的,原理大家也都懂,各家也都大差不差的,也都有开源现成的方案可以用,但这远远不够,整个链路是值得深挖的,这也是能产生商业价值的地方。

HotFix

安卓App热补丁动态修复技术介绍

Nuwa

安卓热更新之Nuwa实现步骤

Robust

Android热更新方案Robust开源,新增自动化补丁工具

这个与Nuwa一样,都用了代码插桩,当然插桩过程,是用了字节码工具(如ASM),进行编译时自动化处理,最终字节码(APK)是受影响的,但源码层面是无感知的。

瓶颈在哪里

插件化这项技术,它的成本特别高,但收益有限,需要庞大的研发体系来支持,并且只有长期投入,才能产出一些价值。因此,现在来说只有头部大厂才真正玩得转。

技术本身并不是瓶颈

这项技术的可行性是由Java决定的,因此一直是可行的。但每年的Android版本,都会对核心组件进行不同程度的强化和升级,这会导致之前的一些方案可能一下子就失效了。另外,手机厂商可能也会做一些修改,不过一般都比较小。

安卓 版本升级,会对插件化有影响,甚至会让现有方案全部失效,但这个还真不是这项技术的瓶颈。因为安卓 升级较慢,正常一年一个版本,但是对核心组件大变化,通常几年才有一次,这个速度对比三方技术的演进还是相当慢的。前面说了这项技术头部大厂最为受益,因此他们会有专门的专家级别的人物在研究,谷歌出了政策,很快就会对策出来,一般用不了多久,插件化技术大拿们就能给出针对 新版本的解决方案。

由于开源和技术分享,很快便会在业界普及。因此,单就技术本身,绝不是瓶颈,并且由于开源的发展,核心业务本身都是开源的,大家都能很快使用最先进的技术。

网络和平台能力才是瓶颈

插件化这个事情,想要真正的用好,光有核心业务还是不够的。核心业务现在都有现成的开源库,拿过来就可以用,但这远远不够。

就从一个插件从开发人员手中到用户手中,并成功安装生效,这一过程拆来看需要多少东西吧:

  1. 插件的开发,需要一些辅助工具。理想的情况下,一个插件模块的开发,应该与常规应用开发是一样的,但毕竟它的构建目标是一个插件,而非标准的app,所以你需要针对核心业务插件框架适用的一些开发工具。这个一般开源框架中都有提供,但不见得有那么好。
  2. 构建和打包。如果是一个合格的插件化框架,一定会有怎么构建 打包的配套设施。
  3. 测试和调试。这里面的难点在于,如何能尽可能的模拟真实的流程,并且能方便的来实施测试和验证结果
  4. 发布上线控制。一些细节就是如何精准推送,如何做灰度发布,以及发现问题后如何快速回滚(你看,这哪一项涉及插件化技术)
  5. 下载。客户端的一个最大的问题就是,客户端在客户那里,我们发布的东西都在服务器上,如何能让插件顺利的送达到用户手中。别小看这个,网络问题永远是出错误原因里面最多的一个,而且容易被测试忽略,因为研发人员自己的网络环境一般简单且稳定。(一个最简单的测试就是,当你在电梯里,地铁里,高铁时,厕所里,山上,河里,村里,手机里面的应用还有几个能正常联网的?)
  6. 安装和生效。这个也是插件化的核心业务,框架都会支持的。难点在于校验,就是客户端拿到的插件是不是符合预期的,文件有没有损坏,有没被篡改。
  7. 降级。这个通常插件化框架不会提供。降级的意思就是如果插件安装更新失败了,你怎么办?能否回滚,如果这个插件彻底废了,有没有H5页面可以用?

我们粗略来看,就能分出上面7个步骤,其实还有更细的。上面这些里,插件化开源框架一定能解决的是2和6,1和3会在一定程度上支持。而其他的只有靠自己了,当然 也可能会有一些开源软件可以用,但它们并不纯是为了插件化而做的。这些东西都属于研发效率平台,甚至是涉及软件流程,基本上都属于商业公司的核心业务机密了,基本上是不可能开源的,而且不同的公司文化制度流程都不一样,即使开源给你了,也不一定用得上。但这恰恰又是最能体现一个公司结合技术实力的地方,小公司或者综合能力差的公司,即使有现成的插件化框架方案给你,你也用不好,因为配套设施不行。再次佐证,插件化这东西只有头部大厂才能玩得转,并产生正收益。

这些才是真正的瓶颈。

这是逆向工程技术

插件化需要用到大量的反射和动态代理技术去hook安卓系统,从而实现官方并不直接支持的特性,这属于逆向工程,与官方倡导的方向并不一致。

而且,只有在国内圈子里面才比较流行,国外的一些大厂和专家似乎并不愿意花时间和精力搞这些事情。很难简单的用好与坏来评价,只能说文化不同。

逆向工程技术局限性较大,很难长久发展, 一旦官方把某个关键地方堵住(不能说是漏洞,而一些关键的对象和接口),很多插件框架可能就废掉了,当然了道高一尺,魔高一丈,总还是能找到可以hook的地方,仍总感觉怪怪的。

常规的技术,如编程范式(函数式编程,Reactive,RxJava),编程语言,平台框架和轮子(如Picasso,如OkHttp),这些是纯正的技术,不受制于任何平台,不但能长久发展,更能反过来推动官方进步(如OkHttp已被谷歌内置为安卓内部作为HTTP协议的实现)。

综合来说,除非你需要专门研究插件化,并能得到收益之外(对业务,对公司,对个人),对于插件化技术,了解一下就够了,而且这东西并不能真正的提升软件质量(它带来的问题比它解决的要多很多)。不如把时间花在业务上面,花在编程范式,花在编程语言,花在流行的框架和轮子上面,这更能提升软件质量,且是终生受益的。毕竟,假如代码质量够好,发出去的版本都可控,都能达到预期,也就没必要折腾插件化了(即使是对大厂头部应用来说,版本的发布仍主要是靠正常的apk发布,插件迭代一般用在正常版本来不及时使用比如电商的双11期间)。

研发工具(如Instant Run),调试工具(如获取 一些运行时的信息,在线调试),测试工具(如Mock),不侵入源码式编程(动态插桩,AOP和依赖注入)才是反射和动态代理以及Hook的最终归宿,是值得我们深入研究和学习的方向。

参考资料

Comments