插件化之插件混淆的可行性探索

1、引言

为提高研发效能、业务迭代速度、版本覆盖率等指标,业界选择插件化解决方案的团队不在少数,并且有很好的输出,如手淘,滴滴,爱奇艺。方案要在效率,质量和安全等方面取得平衡。反编译APK发现,很多团队会对插件代码不作混淆处理,其中有一些考虑因素,诸如方便动态部署,快速实现版本覆盖等。如何在兼顾动态部署等需要的同时,对插件代码甚至资源进行混淆,加大逆向分析成本,提高代码安全,我司在去年进行了尝试和探索,并取得了一定的成效。

2、插件混淆

Android插件化,核心在于复杂业务下,宿主和插件类、组件、资源加载等实现,其次是围绕核心实现的诸如插件发包,动态部署,插件混淆,容器安全和监控等周边建设。下图是微店插件化架构示意图。

在微店插件化架构中,插件其实是一个APK,与atlas[1]类似,我司把插件伪装成SO包形式,放至APK libs目录中。要实现N+1个 APK(N个插件+1个宿主)的代码混淆一致性,存在以下约束:

  1. 保持插件之间相关API类代码不被混淆,一个简单的例子,商品详情插件依赖购物车插件相关API类,这些API类最好能不被混淆,否则可能引起类查找等问题;
  2. 保持一些三方库或基础SDK(微店把三方库和基础SDK暂时放至了宿主中,同时被插件进行provided依赖)被插件引用到的API类最好能不被混淆或apply同份mapping文件,否则可能引起类查找等问题;
  3. 混淆后的代码,N+1个插件不能存在重复类名,方法和属性,否则会引起类查找等问题; 针对上述问题,我们进行了可行性探索,下面给出实践方案。

2.1 保持插件之间相关API类代码不被混淆

保持插件API类代码不被混淆,原始的方法是让业务开发,手动编写progurd配置,这样会带来业务开发效率低下问题。我们的做法是编写一个注解类如@Export,注解了@Export的类会在混淆时进行自动keep。

1
2
3
4
5
6
7
8
9
// Export class 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Keep
public @interface Export {
}
// proguard file
-keep,allowobfuscation @interface com.weidian.framework.annotation.Export
-keep @com.weidian.framework.annotation.Export class *{*;}

2.2 保持三方库或基础SDK引用类代码不被混淆

与插件API类不同,三方库与基础SDK,除了SDK自身声明的混淆规则外,插件可能还会引用可以被混淆的类。在常规架构下,可以被混淆的类,放至插件化场景下,这些类最好被KEEP或N+1个插件apply同份mapping文件。为基础SDK配置@Export注解不太合适,会存在反向依赖,限制了SDK功能以及无法处理三方库。为兼顾效率与质量,我们做了下以尝试:
1.编写自动扫描插件源码脚本,为源码中import的类自动生成keep混淆声明,并让APP打包时,包含这些自动生成的混淆脚本。(现通过Dexdeps方式查找插件中引用的非打进包中的类和方法[2])
2.编写Gradle Task,在processReleaseResources Task后拿到proguardOutputFile,这个文件中包含了插件在layout中声明的view混淆规则,并让APP打包时,包含这些自动生成的混淆脚本。

1
2
3
4
5
6
7
8
9
def processResourceTask=project.tasks.getByName("processReleaseResources")
if (processResourceTask) {
processResourceTask.doLast {
File rules = processResourceTask.getProguardOutputFile()
if (rules.exists()) {
//export file
}
}
}

在实践过程,我们发现,如果能让N+1个插件apply同份mapping文件 ,APK的大小能减少3-4m左右,不过考虑到其他因素,我们暂时采用了KEEP类代码方案。

2.3 插件混淆后的类代码唯一

保持插件混淆后的类代码唯一性,这个其实progurad[3]已经提供了支持,采用-defaultpackage 或-flattenpackagehierarchy都可以解决代码混淆后冲突问题。我们采用了-flattenpackagehierarchy方式,为每个插件混淆类加入包名前缀。

1
2
-defaultpackage renamepakcage 将混淆的类的包名替换
-flattenpackagehierarchye prepackage 将混淆的类包名加上前缀包名

参考

[1] atlas:https://github.com/alibaba/atlas
[2] dexdeps:https://android.googlesource.com/platform/dalvik.git/+/master/tools/dexdeps/README.txt
[3] proguard:https://www.guardsquare.com/en/products/proguard/manual/usage