关于Android混淆知识点的两篇好文章:写给 Android 开发者的混淆使用手册, Android混淆从入门到精通
参考的资料:饿了么全面混淆插件 Mess,android 防破解, 代码混淆,代码保护
首先,在之前学过的关于Android混淆知识中我们知道,Android默认是不会混淆四大组件和自定义View的,或者换一个说法,出现在 xml 里的相关 Java 类默认是不会被混淆的。
你不禁会问,为什么Android默认要这么做?
答:因为 Proguard 原本是为 Java 打造的,它无法搜索到我们 AndroidManifest、布局等文件中引用了哪些 Java 类,因此如果 Java 代码变了而 XML 文件中的引用没变,就会造成反射失败,所以android默认这些被 XML 使用到的类需要 keep 住。
关于这一点,我们可以简单的验证一下:
打开build/intermediates/proguard-files/路径下的proguard-android.txt文件,这里面帮我们声明了许多混淆规则内容,包括:keep 所有继承自 View 的类,keep 所有继承自 Activity 的类,keep 所有 JavascriptInterface、native 方法声明,以及 keep 一些注解了 @Keep 的内容,例如:
好,说了这么多,Mess这个插件的目的就是在混淆的时候把这些顽固分子一起收拾了,让你的应用混淆的更彻底,更安全!
Mess的项目地址:https://github.com/eleme/Mess
我把它下载到了本地,其主要一共就四个groovy文件:
MessExtension:用来存放需要被忽略的Proguard,比如那些本身也配置了相关混淆配置的依赖库,Mess会对这些依赖库 进行混淆
Util:混淆时的工具类,这里面提供了一些方法如下
renameProguardTxt(...):对Proguard文件进行重命名,然后存放到指定的路径
hideProguardTxt(...):备份要被删除的proguard.txt 文件,用于最后的恢复
recoverProguardTxt(...):恢复proguard.txt 文件
sortMapping(...):根据传进来的Map的key的长短来排序
RewriteComponentTask:根据映射文件 mapping.txt 找到原先的类名,然后替换AndroidManifest,layout,menu和
values 这些xml文件中的JAVA类,完成对其的混淆
MessPlugin:对transformClassesAndResourcesWithProguardForRelease这项Task的流程的调度,包括先后顺序等
- 清空aapt_rules.txt 中的内容
aapt_rules.txt中记录着所有在 xml 里声明的 java 类(四大组件和自定义View等),并将它们都 keep 住,这个文件最终会被添加到 app 的混淆配置中,所以既然Mess想实现更全面的混淆,就必须删除它。在build/intermediates/proguard-rules/release下,你可以找到aapt_rules.txt,截了一部分代码如下:
既然知道要删掉它,首先需要找到aapt_rules.txt是从哪里生成的,系统的ProcessAndroidResources类中有这样一段代码:
该方法的名字就是获得AndroidResources进程中的Proguard输出文件,继续追踪我们发现,该方法所在的是一个名为VariantScope的接口,在它的实现类VariantScopeImpl中我终于看到了下面的这段代码:
到这里aapt_rules.txt的出生点已经找到,拿到路径后,在MessPlugin中是这样删除它的:
2.如果需要混淆依赖库,则删除依赖库中的 proguard.txt 文件
Mess中采用的方式简单粗暴,直接将 proguard.txt 文件名最后加上 ~ ,这在linux中表示备份,以便之后文件的恢复。在Util中提供了如下方法:
对proguard.txt的备份和恢复的逻辑都在这里,然后在MessPlugin中先后调用:
3.hook transformClassesAndResourcesWithProguard 获取混淆后的类映射关系 Map
注:hook翻译过来是钩子的意思,它能够将自身的代码「融入」被勾住(Hook)的程序的进程中,成为目标进程的一个部分
在一开始没有混淆之前:xml中的java类←→class文件
经过前面的步骤之后:xml中的java类←→混淆过的class文件
所以现在需要做的就是找到混淆过的class在xml中的原有java类名,然后把它给替换掉,也就是把它给混淆了。
这个映射关系要从/build/outputs/mapping/release/路径下的mapping.txt中找,它里面存放着混淆前后类、方法、类成员等的对照表,如下:
这一个个的箭头就是代表着一一对应的关系:混淆前和混淆后。Mess在RewriteComponentTask中用Map解析并存储了这些映射关系,相关代码如下:
在这段代码下边有一段注释:
“如果我们不对这个map进行长短的排序,就会出现这样的情况:
me.ele.foo -> me.ele.a
me.ele.fooNew -> me.ele.b(本该是这样)
me.ele.fooNew -> me.ele.aNew(但不排序的话,会变成这样)
像这样,一个类的类名是另一个类名的开始部分,后面的替换就会出现bug。所以在存储完映射关系后,Mess调用了sortMapping(...)方法来进行排序,该方法的代码如下:
4.通过映射 Map 替换 AndroidManifest.xml 里的 Java 原类名
在RewriteComponentTask中,紧接着上面的步骤,你就会看到下面的代码:
从这里就开始着手替换 AndroidManifest.xml中的Java原类名了,writeLine(...)方法如下所示:
逐行读取,然后进行相应的替换。
5.通过映射 Map 替换 layout、menu 和 value 文件夹下的 xml 的 Java 原类名
还是在RewriteComponentTask中,有这样一个方法:
很明显,它的作用就是用来判断文件路径是否是 layout、menu 和 value,它在处理完AndroidManifest.xml后随即被调用:
上图中的第二个红色框部分执行了相应的替换工作。
6.再次执行 ProcessAndroidResources Task
经过前面的5个步骤之后,xml中的java类已经全部混淆,接下来需要再重新编译一次资源文件,即再次执行 ProcessAndroidResources Task。
到这里Mess六个步骤已经全部走完,再来回顾一下:
- 清空aapt_rules.txt 中的内容
- 如果需要混淆依赖库,则删除依赖库中的 proguard.txt 文件
- hook transformClassesAndResourcesWithProguard 获取混淆后的类映射关系 Map
- 通过映射 Map 替换 AndroidManifest.xml 里的 Java 原类名
- 通过映射 Map 替换 layout、menu 和 value 文件夹下的 xml 的 Java 原类名
- 再次执行 ProcessAndroidResources Task
说了这么多,那么Mess是什么时候开始工作的呢
答:Mess在 class 打包成 dex 前完成所有的工作