Android高级混淆和代码保护技术

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:
本文讲的是 Android高级混淆和代码保护技术, 这是一篇关于 Android 代码保护的文章,旨在介绍代码混淆、防止逆向工程的各种高级技巧。大家都很忙,我也赶着回去继续开发我的新应用,因此话不多说,越干(gan, 一声)越好。

开始之前,值得一说的是,本文超过五千字,完全由我开发的「纯纯写作」书写而成,纯纯写作主打安全、写作体验和永不丢失内容,于是本着珍爱生命,我用纯纯写作来写这篇文章。

本文有两部分内容,一部分讲混淆,一部分介绍一些混淆之下的安全手段。基准原则都是:在保证不麻烦到自身 以及 能够正常阅读异常日志的前提下,尽可能提高混淆强度和保护代码安全。

本文原文地址:http://drakeet.me/android-advanced-proguard-and-security/

混淆

Android 官方集成了 Proguard 以供我们进行代码混淆工作,关于 Proguard 你可以搜索到各种它的 rules 解释,这些文章千篇一律,因此我不再赘述,只说一些特别的有用的技巧:

一般情况下,Android 的 gradle 中都会默认写着:

 
  1. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 

这一行代码很多人不了解。它的意思是,指定了两个 Proguard rules 文件,一个是通过getDefaultProguardFile() 方法获得官方自带的混淆规则文件路径,另一个是与当前 gradle 相同目录下的 proguard-rules.pro 文件路径。

后者就在我们项目中,由我们书写的,没什么好说的,我们要关注的是前者这个默认 Proguard 文件,它的内容是什么你有曾探究过吗?没有的话,你可以在你的系统文件里搜索proguard-android.txt 就应该能把它找出来,具体自己去看,我就说一些关键的,这个默认文件中帮我们声明了许多混淆规则内容,包括:keep 所有继承自 View 的类,keep 所有继承自 Activity 的类,keep 所有 JavascriptInterface、native 方法声明,以及 keep 一些注解了@Keep 的内容。

所以你知道为什么默认情况下,即使你自己一条规则都没有加入,你的自定义 View 和 Activity 都被保留下来了吧,至少类名都没有被混淆。

那么为什么官方默认会帮我们写下这些?为什么 View 和 Activity 默认情况下应该被保留呢?

简单来说,因为 Proguard 原本是为 Java 打造的,它无法搜索到我们 AndroidManifest、布局等文件中引用了哪些 Java 类,因此如果 Java 代码变了而 XML 文件中的引用没变,就会造成反射失败。所以这些被 XML 使用到的类需要 keep 住。

对于这个问题,饿了么 的团队提供了一个鲜为人知的 gradle 插件 用来无伤混淆 Activity 和 View,这个项目叫 Mess:https://github.com/eleme/Mess ,具体内容各位可以稍后自行去阅读其文档和教程,链接最后都还会附于末尾。简单来说,Mess 弥补了 Proguard 不能检索 XML 文件的缺点,帮 Proguard 完成了 Activity 和 View 的改名及 mapping。

话说回来,前面我建议各位都去逐行了解下默认混淆配置文件,因为只有这样,你才知道整个混淆工具帮你做了什么,了解清楚之后,我建议的一个做法是,把这个默认文件拷贝到你的项目目录之下,删掉 getDefaultProguardFile('proguard-android.txt'),再引入现存于你目录之下的原默认文件。这么做的好处是,方便你修改这个默认文件,因为它有些内容是不必要或者可以更改的。不过基本上我们可以保留其原样。复制过来的另一个好处是,避免其被外方更新导致你引用过来后产生变数。总之,proguardFiles 这个配置项(其实是一个 gradle 方法)可以接受无限个 rules 文件路径,它的参数是一个可变字符串参数,不过为了避免代码横向发展,我更愿意使用另一个方法,叫 proguardFile,注意,少了一个 s 有没有,它接受单个参数,相当于 add 一个 rules。对此,提供我的配置以供参考:

 
  1. release { 
  2.     debuggable false 
  3.     minifyEnabled true 
  4.     zipAlignEnabled true 
  5.     shrinkResources true 
  6.     signingConfig signingConfigs.release 
  7.     proguardFile 'proguard-common.pro' 
  8.     proguardFile 'proguard-rules.pro' 
  9.     proguardFile 'proguard-rules-google-ads.pro'

其中 proguard-common.pro 这个文件就是上述我说的复制过来的官方默认配置文件,它被我放在当前 module 目录之下和 proguard-rules.pro 并列。这么写很清楚而且便于复用。

讲完基本内容之后,我决定再介绍两条特别实用的 Proguard rules:

-repackageclasses

-repackageclasses 这条规则配置特别强大,它可以把你的代码以及所使用到的各种第三方库代码统统移动到同一个包下,可能有人知道这条配置,但仅仅知道它还不能发挥它最大的作用,默认情况下,你只要在 rules 文件中写上 -repackageclasses 这一行代码就可以了,它会把上述的代码文件都移动到根包目录下,即在 / 包之下,这样当有人反编译了你的 APK,将会在根包之下看到 成千上万 的类文件并列着,除此之外,由于我们有时不得不 keep 一些类文件,于是你应用的包名层次仍然会存在,有一些没被完全混淆的类将继续存留在你的包名之下,这些类文件就相对得不到很好的保护。于是我要介绍一个小技巧,就是 -repackageclasses 后跟上一个你应用的包名,如:

-repackageclasses com.drakeet.purewriter.debug

这么做以后,最终 Proguard 会将包括第三方库的所有类文件都移动到你的包名之下,所谓藏叶于林,这时候那些你未能完全混淆的类也可以藏身在这类文件大海之中,而且这些类文件名都会被混淆成 abcd 字母组合的名字。

需要注意的是,-repackageclasses + 你的包名 这种做法存在混淆 bug,而默认 -repackageclasses 不加包名不会出现 bug,所以初次使用此法需要进行测试,否则请退而求其次,关于这个 bug 的具体内容不多说,很赘述。

第二个实用 rules 配置项:-obfuscationdictionary

-obfuscationdictionary 后面加一个纯文本文件路径,它的作用是指定一个字典文件作为混淆字典。默认情况下我们的代码命名会被混淆成 abcdefg... 字母组合的内容,需要修改可以使用这个配置项将字典修改成乱码或中文内容。乱码命名可以令反编译者怀疑人生。中文命名则能够破坏一些反编译软件的正常工作,而且有的中文命名还能起到乱花渐欲迷人眼的效果,比如 GitHub 上较为流行的某长者的话语作为字典,在此不便贴出(可能会有人身危险),各位可以自行搜索,找不到别怪我。这些话语作为代码命名,可以令反编译者沉浸其中,无心分析代码 :P。

最后,关于混淆的内容,我们还有一块软肋,就是资源文件,Proguard 完全不会管我们的资源文件,因此如果资源文件名没有做保护的话,很容易被顺藤摸瓜找到关联的 Java 代码,对此,微信团队提供了一个好用的资源混淆工具,它不仅能帮你全面混淆资源文件,还能帮你缩减资源文件的整体体积,这个工具叫 AndResGuard,开源地址:https://github.com/shwenzhang/AndResGuard

好了,终于简单讲完了一些关于混淆的要点,关于混淆其实还有许多小内容,比如可以使用consumerProguardFiles 为一个 library 或 SDK 项目配置混淆文件,这样当某个 app 引用了你这个库,无需再配置相关混淆内容,该 app 就会自动从 consumerProguardFiles 配置的文件中读取需要进行的 keep 动作,这对于库开发者是很有用的一个功能。更多就不细说了,文章末尾我会附上我的混淆配置文件片段。

安全

有了代码混淆还不够,我们需要更多技巧来保护我们的代码,特别是对于需要做混淆但又需要暴露许多 API 的 SDK 开发者来说。混淆是基础,代码安全是意识。

首先我们要知道我们混淆代码是如何被攻破的,其实对于反编译者来说,最简单的入手点就是字符串搜索,我们硬编码留在代码里的字符串值都会在反编译过程中被原样恢复,因此这是我们首要关注对象。避免被通过字符串攻破,我们应该做到以下几点:

一,不要硬编码写入字符串值,即使你不得不这么做,也至少应该另起一个类,比如叫做HardStrings,用于静态存放这些硬编码的字符串。这样反编译者只能搜索到你这个常量类,而较难以搜索到这些字符串常量被哪里引用。

二,在 release 混淆过程中删除 Log 代码,使用 -assumenosideeffects 这个配置项可以帮我们在编译成 APK 之前把日志代码全部删掉,这么做不仅有助于提升性能,而且日志代码往往会保留很多我们的意图和许多可被反编译的字符串:

 
  1. -assumenosideeffects class android.util.Log { 
  2.     public static boolean isLoggable(java.lang.String, int); 
  3.     public static int d(...); 
  4.     public static int w(...); 
  5.     public static int v(...); 
  6.     public static int i(...); 

三,对于你不得不留下的一些硬编码和日志内容,可以采用编码形式替换,如 你可以规定 "4001" 代表某种错误,而不是在你的代码里写入这个错误的具体描述字符串。这么做的话,你需要有个地方记下这些编码映射的内容,关于此有个技巧:你可以再创建一个常量类,其内容是一堆静态字符串对象,针对上面那个例子,你可以把真正的错误信息作为一个字符串变量的名字,而把它的值写成一个编码,如下:

 
  1. public static final String SHOULD_REGISTER_FIRST_ERROR = "ssrrffe"

这样当你在看没混淆的代码引用这个静态变量,你能够一目了然它的意思。而反编译者看到的则是:

 
  1. public static final String abc = "ssrrffe"

命名看不懂,值也看不懂。

四,把 AppKey 之类特别敏感的字符串内容藏在 native so 文件中。

关于字符串技巧的内容差不多就这样了,能做到这些就不错了,还有一些极端做法不多说,为了阻碍黑客阅读,自己也变得非常麻烦,双刃剑,这不是我们想要的结果。

然后我们讲另一个混淆后代码的软肋,就是一些我们不得不 keep 的内容,如果是闭源 SDK 开发者,需要 keep 的内容将会更多,几乎只要是 public 的类、变量,方法,全部要 keep,那么针对这个问题,我们该怎么办?介绍一个方法:

给这些需要 keep 的内容设置委托者,然后将委托者投入大海之中。

很玄乎吧?哈哈,这么讲有助于记忆。其实和我们在混淆章节说的藏叶于林的思想是一样的。如果一个类不得不 keep,那就把它所做的全部内容都转交给一个 private 或 internal 的类对象去完成,这个委托类对象代码可以完全混淆,然后你再把这个委托类通过混淆工具藏在大量的代码之中,这样就足够给反编译者带来了很大的麻烦,相比直接获取逻辑代码,这么做以后要找到实体的逻辑代码将费劲得多。

因此,如果你知道有这么一个方式,其实你完全可以不使用饿了么提供的那个 Activity 和 View 混淆工具,也能很好地保护你的 Activity 和 View。

不过一般情况我们无需所有内容都保护,只要把关键、核心内容委托出去就可以了。

最后的最后,我们还需要做的就是防止反编译者重新打包,全方位绝人之路呀,能做的就是在代码中加入签名验证,并做双向依赖。关于此我写过一个类似阿里黑匣子的东西,能够在 native 检查签名和加解密内容,后续也有计划整理开源,这里暂且就不多说了。

除此之外,我专门写过一篇叫作《Android 密钥保护和 C/S 网络传输安全理论指南》 的文章,感兴趣可以之后移步阅读。

总之,代码安全和混淆是一个意识加技巧的问题,但都不难,掌握以上内容就已经十分好了。分享到此结束,如有疑问或问题欢迎来信交流。


本文作者:佚名

来源:51CTO

原文标题:Android高级混淆和代码保护技术
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
安全 Android开发 iOS开发
Android vs. iOS:构建生态差异与技术较量的深度剖析###
本文深入探讨了Android与iOS两大移动操作系统在构建生态系统上的差异,揭示了它们各自的技术优势及面临的挑战。通过对比分析两者的开放性、用户体验、安全性及市场策略,本文旨在揭示这些差异如何塑造了当今智能手机市场的竞争格局,为开发者和用户提供决策参考。 ###
|
2月前
|
安全 Android开发 iOS开发
安卓与iOS的较量:技术深度对比
【10月更文挑战第18天】 在智能手机操作系统领域,安卓和iOS无疑是两大巨头。本文将深入探讨这两种系统的技术特点、优势以及它们之间的主要差异,帮助读者更好地理解这两个平台的独特之处。
58 0
|
1月前
|
安全 搜索推荐 Android开发
揭秘安卓与iOS系统的差异:技术深度对比
【10月更文挑战第27天】 本文深入探讨了安卓(Android)与iOS两大移动操作系统的技术特点和用户体验差异。通过对比两者的系统架构、应用生态、用户界面、安全性等方面,揭示了为何这两种系统能够在市场中各占一席之地,并为用户提供不同的选择。文章旨在为读者提供一个全面的视角,理解两种系统的优势与局限,从而更好地根据自己的需求做出选择。
96 2
|
1月前
|
安全 搜索推荐 Android开发
揭秘iOS与安卓系统的差异:一场技术与哲学的较量
在智能手机的世界里,iOS和Android无疑是两大巨头,它们不仅定义了操作系统的标准,也深刻影响了全球数亿用户的日常生活。本文旨在探讨这两个平台在设计理念、用户体验、生态系统及安全性等方面的本质区别,揭示它们背后的技术哲学和市场策略。通过对比分析,我们将发现,选择iOS或Android,不仅仅是选择一个操作系统,更是选择了一种生活方式和技术信仰。
|
2月前
|
安全 Android开发 iOS开发
iOS与安卓:技术生态的双雄争霸
在当今数字化时代,智能手机操作系统的竞争愈发激烈。iOS和安卓作为两大主流平台,各自拥有独特的技术优势和市场地位。本文将从技术架构、用户体验、安全性以及开发者支持四个方面,深入探讨iOS与安卓之间的差异,并分析它们如何塑造了今天的移动技术生态。无论是追求极致体验的苹果用户,还是享受开放自由的安卓粉丝,了解这两大系统的内在逻辑对于把握未来趋势至关重要。
|
2月前
|
安全 搜索推荐 Android开发
揭秘iOS与Android系统的差异:一场技术与哲学的较量
在当今数字化时代,智能手机操作系统的选择成为了用户个性化表达和技术偏好的重要标志。iOS和Android,作为市场上两大主流操作系统,它们之间的竞争不仅仅是技术的比拼,更是设计理念、用户体验和生态系统构建的全面较量。本文将深入探讨iOS与Android在系统架构、应用生态、用户界面及安全性等方面的本质区别,揭示这两种系统背后的哲学思想和市场策略,帮助读者更全面地理解两者的优劣,从而做出更适合自己的选择。
|
2月前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
Android远程连接和登录FTPS服务代码(commons.net库)
32 1
|
2月前
|
安全 Android开发 iOS开发
安卓vs iOS:探索两种操作系统的独特魅力与技术深度###
【10月更文挑战第16天】 本文旨在深入浅出地探讨安卓(Android)与iOS这两种主流移动操作系统的特色、优势及背后的技术理念。通过对比分析,揭示它们各自如何塑造了移动互联网的生态,并为用户提供丰富多彩的智能体验。无论您是科技爱好者还是普通用户,都能从这篇文章中感受到技术创新带来的无限可能。 ###
58 2
|
2月前
|
机器学习/深度学习 人工智能 Android开发
安卓与iOS:技术演进的双城记
【10月更文挑战第16天】 在移动操作系统的世界里,安卓和iOS无疑是两个最重要的玩家。它们各自代表了不同的技术理念和市场策略,塑造了全球数亿用户的移动体验。本文将深入探讨这两个平台的发展历程、技术特点以及它们如何影响了我们的数字生活,旨在为读者提供一个全面而深入的视角,理解这两个操作系统背后的哲学和未来趋势。
36 2
|
2月前
|
Java Android开发 Swift
掌握安卓与iOS应用开发:技术比较与选择指南
在移动应用开发领域,谷歌的安卓和苹果的iOS系统无疑是两大巨头。它们不仅塑造了智能手机市场,还影响了开发者的日常决策。本文深入探讨了安卓与iOS平台的技术差异、开发环境及工具、以及市场表现和用户基础。通过对比分析,旨在为开发者提供实用的指导,帮助他们根据项目需求、预算限制和性能要求,做出最合适的平台选择。无论是追求高度定制的用户体验,还是期望快速进入市场,本文都将为您的开发旅程提供有价值的见解。