混淆的另一重境界

前言

今天给大家推荐的是『巴掌』的投稿,讲解了一个Gradle插件的实现方法和原理,对于想深入了解Android打包编译,gradle插件实现的开发者来说,绝对是一篇不错的案例。

Mess介绍

众所周知,我们开混淆打包后生成的apk里,Activity、自定义View、Service等出现在xml里的相关Java类默认都会被keep住,那么这对于app的保护是不足够好的,Mess就是来解决这个问题,把即使出现在xml文件中的Java类照样混淆。

使用

此外,Mess还提供一个可选配置,ignoreProguard,由于有些依赖库本身也配置了相关混淆配置,如com.android.support:recyclerview-v7com.jakewharton:butterknife等,那么这些文件都将会被添加到proguardFiles中,导致依赖库无法被混淆,所以ignoreProguard配置就是来解决这个问题的。

比如忽视com.android.support:recyclerview-v7的混淆配置文件,则直接

实现原理

先来看看Android gradle plugin在构建时最后所走的几个task:

其中有几个关键性的task,可以看到:app:transformClassesAndResourcesWithProguardForRelease是走在:app:packageRelease之前的,那么我们就在打包前对混淆的task做些操作来实现我们的目的。

  • hook transformClassesAndResourcesWithProguardFor${variant.name.capitalize()}
  • hook ProcessAndroidResources Task,将生成的aapt_rules.txt中内容清空
  • 如果需要混淆依赖库,则删除依赖库中的proguard.txt文件
  • 遍历一遍mapping.txt获取所有Java类名的的映射关系得到一个Map
  • 拿映射Map替换AndroidManifest.xml里的Java原类名
  • 拿映射Map替换layout、menu和value文件夹下的xml的Java原类名
  • 重新跑ProcessAndroidResources Task
  • 恢复之前删除依赖库中的proguard.txt文件

以上就是Mess干的关键性的东西,接下来依次说明。

hook transformClassesAndResourcesWithProguardFor${variant.name}

这个task是处理类和资源混淆的,也是我们的突破口,Mess中大部分自定义task都是围绕在这个task执行的,之后会有详解。

hook ProcessAndroidResources Task,将生成的aapt_rules.txt中内容清空

这一步是虽说只是把aapt_rules.txt文件中的内容清空,但是确实Mess Plugin能成功的最关键的一步。

ProcessAndroidResources task会生成一个aapt_rules.txt,可见源码ProcessAndroidResources.groovy,aapt_rules.txt里会keep住我们在xml里所书写的那些Activity、自定义View等Java类名部分,还可以看到JackTask.java里的相关代码:

其中getProcessAndroidResourcesProguardOutputFile方法所对应的文件就是我们所需要清空的aapt_rules.txt,可以在VariantScope.java中查看。

很明显,aapt_rules.txt所keep住的所有内容都将会添加到最后的混淆配置中,因此,我们需要在ProcessAndroidResources这个Task执行之后清空aapt_rules.txt中的内容,以保证编译出的main.jar中的所有.class都是混淆后的。

相关代码如下:

如果需要混淆依赖库,则删除依赖库中的proguard.txt文件

这一步就是删除依赖库中所保护的内容,具体proguard.txt文件位于app目录下/build/intermediates/exploded-aar/依赖库maven名/proguard.txt

Mess中直接将proguard.txt文件名最后加上~,如proguard.txt~,在linux中表示备份,以便之后文件的恢复。

相关代码如下:

遍历一遍mapping.txt获取所有Java类名的的映射关系得到一个Map

之前第一步已经将生成的main.jar中所有的.class文件做相关混淆了,那么我们之前所在xml里写的还是原来的Java类名,因此,我们想要替换xml里的Java类名,就得先知道原先的类名被替换成什么了,这个时候就得依赖mapping.txt了。

直接遍历:

这样后map里就存有所有类名的映射关系了,但是有个小问题要注意,假如存在这种情况,me.ele.foo -> me.ele.a,me.ele.fooNew -> me.ele.b,也就是恰巧有类名是另一个类名的开始部分,那么这样对我们之后的替换是会有bug的,会导致fooNew被替换成了aNew。因此,拿到map后需要对map做一次原类名长度的降序排序(也就是map中的key),以避免这个bug发生。相关代码如下:

至此,一个正确的map已经拿到,接下来就是靠这个map来对相关的xml文件做替换了。

拿映射Map替换AndroidManifest.xml里的Java原类名

细心活,拿到AndroidManifest.xml一行一行读取,匹配到相关字符串则进行替换,但这里有个小坑,由于Java内部类的类名是用$符号分割的,刚好它又是正则表达式表示匹配字符串的结尾,因此对于内部类,我们应该现将$符号先替换成其他字符串,然后再做类名的替换,Mess中是替换成inner,相关代码如下:

拿映射Map替换layout、menu和value文件夹下的xml的Java原类名

前一步已经把AndroidManifest.xml中的对应Java类名替换了,这一步就是替换layout、menu和value这三个文件夹下的xml内容,感谢groovy语法让整件事情变得非常简单。layout、menu文件夹大家能立马理解,那么value呢?其实就是behavior引入后才存在的,所以value文件夹千万别忽视。

相关代码如下:

至此,整个工程的main.jar中的.class文件以及资源文件都替换成相互匹配的混淆后的名称了。

重新跑ProcessAndroidResources Task

前些步骤hook后ProcessAndroidResources Task之后我们已经把静态的文件都替换好了,那么接下来就还得依靠Android gradle plugin的原有tasks了,于是乎我们重新执行ProcessAndroidResources Task。

恢复之前删除依赖库中的proguard.txt文件

有头有尾。

尾语

想要写出Mess这样的plugin,对Android整个打包流程是要相当熟悉的,这样才能知道什么时候该hook什么task,平常开发过程中尽量不要直接点击run按钮,应该直接通过gradle assemble** 构建,这样无数次的看构建过程中经历哪些task,然后去阅读相关task源码,这样对整个打包流程才会越来越胸有成竹。

Mess有个小遗憾,那就是ButterKnife这个库在绝大多数app中都使用了,但是ButterKnife的混淆规则中有对使用注解的方法名和变量名做保护,这样就比较尴尬了,会导致Mess对使用ButterKnife库的app而言是没多大作用的。

但是不要灰心,ButterMess这个Lib就来解决这个问题,接下来会写篇详解ButterMess的文章,先放个ButterMess的链接:https://github.com/peacepassion/ButterMess

原文发布于微信公众号 - Android群英传(android_heroes)

原文发表时间:2017-02-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏北京马哥教育

Python imports指南

来源:Python程序员 ID:pythonbuluo 声明:如果你每天写Python,你会发现这篇文章中没有新东西。 这是专为那些像运维人员等偶尔使用Pyt...

2565
来自专栏java 成神之路

vmstat 命令详解

3897
来自专栏社区的朋友们

在共享内存实现 Redis(下)

从实现方式入手,设计了一种综合二者优点的方案:将 Redis 做成数据逻辑分离,数据存放共享内存,进程只负责存储逻辑,同时解决 Redis 长命令卡顿和 for...

3840
来自专栏Android 研究

Android跨进程通信IPC之2——Bionic

Bionic库是Android的基础库之一,也是连接Android系统和Linux系统内核的桥梁,Bionic中包含了很多基本的功能模块,这些功能模块基本上都是...

1883
来自专栏Python攻城狮

Redis的安装及基本使用1.Redis2.Redis安装3.redis常见配置4.redis数据操作5.redis发布订阅6.主从双备

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。它支持多种类型的数据结构,如 字符串(strings), ...

661
来自专栏性能与架构

console.log() 之外的调试技巧

console.log( ) 是JS开发时常用的小工具,输出一些信息来辅助调试,console 还有很多有用的方法,下面介绍几个方便调试的用法 跟踪堆栈 例如想...

3689
来自专栏屈定‘s Blog

工作--如何封装第三方服务?

业务开发中经常会对接某某第三方服务,因此会经常写一些SDK供服务使用,一种比较好的做法就是使用命令模式封装第三方服务,命令模式对于调用方来说简洁明了,也正是封装...

1922
来自专栏小詹同学

Python 4 种不同的存取文件骚操作

前言:最近开始学习tensorflow框架,选修课让任选一种框架实现mnist手写数字的识别分类。小詹也就随着大流选择了 tf 框架,跟着教程边学边做,小詹用了...

1513
来自专栏magicsoar

C++ socket网络爬虫(1)

C++写的socket网络爬虫,代码会在最后一次讲解中提供给大家,同时我也会在写的同时不断的对代码进行完善与修改 我首先向大家讲解如何将网页中的内容,文本,图片...

4825
来自专栏不想当开发的产品不是好测试

#测试框架推荐# test4j,数据库测试

# 背景 后端都是操作DB的,这块的自动化测试校验的话,是需要数据库操作的,当然可以直接封装方法来操作数据,那么有没有开源框架支持数据操作,让我们关注写sql语...

59612

扫码关注云+社区

领取腾讯云代金券