Android 混淆
主要摘自于公司内一个同事的分享
混淆是什么
混淆是 “在 Android 项目进行打包时,将程序中的资源、代码以某种规则转换成功能上等价,但是难以阅读和理解的形式” 的行为。
混淆的作用
- 可以使得工程难以被逆向,增加反编译的成本,提升安全性
- 减小 Apk 的大小
混淆的分类
- 代码混淆
- 资源混淆
代码混淆
Java 代码混淆
ProGuard
早期Android中一般使用 ProGuard 进行Java代码的混淆:
Java 编译器将源代码编译为 Java 字节码(.class)。ProGuard 可以选择优化此代码,从而生成更小,更快的 Java 字节码。dx 编译器最终可以将此 Java 字节码转换为 Dalvik 字节码(.dex)。 Dalvik 字节码打包在 apk 文件中,最终安装在设备上。
R8
当使用 Android Gradle 插件 3.4.0 或更高版本编译项目时,该插件不再使用 ProGuard 执行编译时代码优化,而是使用 R8 编译器处理任务。
R8 是 D8 的衍生产品,旨在集成 ProGuard 和 D8(dx编译器的替代品) 的功能,一步到位地完成了所有的代码优化和转换成 Dalvik 字节码的过程。R8 和ProGuard 相比,R8 可以更快地缩减代码,同时改善输出大小。R8 支持所有现有 ProGuard 规则文件,因此在更新 Android Gradle 插件以使用 R8 时,不需要更改现有规则。
开启混淆
在 moudule 对应的 build.gradle 文件中找到下面的代码,将 minifyEnabled
修改为 true 即可在 release 包开启混淆。开启混淆开关后就可以在 proguard-rules.pro
文件中配置混淆内容。
1 | buildTypes { |
开启混淆后:
- 压缩代码:从应用及其依赖的库中检测并安全地移除未使用的类、字段、方法和属性(这使其成为一个用来规避 64k 引用限制的非常有用的工具)。例如,如果工程里仅使用某个库依赖项的少数几个 API,压缩功能可以识别应用未使用的库代码,从应用中移除这部分代码。
- 混淆:缩短类和成员的名称,从而减小 Dex 文件大小。
- 优化 :检查并重写代码,以进一步减小应用 DEX 文件的大小。例如,如果 R8 检测到从未采用过给定 if/else 语句的
else {}
分支,R8 便会移除else {}
分支的代码。
混淆配置
上面说了,混淆配置是写在 proguard-rules.pro
文件中的,那么怎么写呢?Google 给了我们示例,在 android-sdk/tools/proguard/proguard-android.txt
中进行展示,有需要的可以去查看下官方建议配置,这里给出常用的配置:
1 | // 指定代码的压缩级别 |
做 SDK 的时候,我们往往需要告诉接入方要在
proguard-rules.pro
文件添加哪些配置。但是可以通过在 module 下的consumer-rules.pro
文件中填写混淆配置,减少接入成本。
解码混淆后的堆栈信息
R8 每次运行时都会创建一个 mapping.txt
文件,其中列出了混淆过的类、方法和字段名称与原始名称的映射关系。此映射文件还包含用于将行号映射回原始源文件行号的信息。R8 将此文件保存在 <module- name>/build/outputs/mapping/<build-type>/
目录中。
进入到 Android SDK 路径的 /tools/proguard/bin
目录中,运行proguardgui.sh
脚本(Window下为 proguardgui.bat
),选择 ReTrace,并添加我们项目中混淆生成的 mapping.txt
文件以及混淆后的崩溃信息即可还原出我们的崩溃日志信息:
工作中也接触到一个需求,在端上实现堆栈(已经实现拦截错误堆栈的功能)反混淆的能力,这个只需要使用 Retrace 库 即可,核心代码如下:
我当时使用库的版本:implementation ‘net.sf.proguard:proguard-retrace:6.2.2’
Native 代码混淆
面仅仅是对 Java 代码的混淆,而 C/C++ 写的 native 代码却没有进行混淆。OLLVM 提供一套开源的针对 LLVM 的代码混淆工具,以增加逆向工程的难度。其提供了三种混淆方式:
利用 OLLVM 进行 Android native 代码混淆
- 下载并编译ollvm
- 配置ndk以支持ollvm
- 使用ollvm进行编译
资源混淆
上面涉及到的仅仅是对代码进行的混淆,并不能对资源文件进行混淆。资源文件的命名可以使得破解者根据该名称揣测这个文件的用途,做到破坏性的修改,所以我们也需要对资源做混淆达到相对的安全。
背景知识
AAPT
AAPT(Android Asset Packaging Tool)。在打包过程中使用AAPT对APK中用到的资源进行打包,可以把资源编译为二进制文件,并生成 resources.arsc。下图是 AAPT 打包的流程:
AAPT这个工具在打包过程中主要做了下列工作:
- 把
assets
、res/raw
目录下的所有资源进行打包(根据不同的文件后缀选择压缩 or 不压缩) - 编译
AndroidManifest.xml
成二进制的 XML 文件 res/
目录下的资源进行编译或者其他处理(具体处理方式视文件后缀不同而不同,例如:.xml
会编译成二进制文件,.png
文件会进行优化等等)后才进行打包- 对除了
assets
资源之外所有的资源赋予一个资源 ID 常量,并且会生成一个资源索引表resources.arsc
- 把上述步骤中生成结果保存在一个
*.ap_
文件(zip包),并把各个资源ID常量定义在一个R.java
中
Android 查找资源过程
assets 资源可直接使用
AssetManager
通过文件名来加载AAPT 工具在打包过程中生成的
Resources.arsc
文件是存放在 APK 包中的,其本身是一个资源的索引表,里面维护着资源 ID、Name 等对应关系,Resources
是通过resources.arsc
把Resource
的 ID 转化成资源文件的名称,然后交由AssetManager
来加载的,找到这个资源对应的文件或者数据
资源混淆核心处理过程
- 修改AAPT在处理资源文件相关的源码,对资源文件名字、资源文件路径进行混淆,并将映射关系输出到
mapping
文件中。例如将res/drawable/hello.png
混淆为r/s/a.png
- 生成新的
resources.arsc
文件,并打包进 apk
生成混淆后的资源文件目录:
生成新的 resources.arsc
:
逆向介绍
工具文档:逆向工具/反编译工具 集合
这里主要想说一个我用过的一个工具:jadx,可以将 Apk 反编译,查看相关代码。(比如查看线上包是否带了不该带的代码)
下载好后,直接点击下图中的 jadx-gui 就会弹出一个页面让你选择 Apk,选好了就可以了。