Android 混淆使用入门笔记

概述


混淆是Android Apk打包过程中的一个重要步骤,默认情况下,打包都是需要混淆过程的。
Android App混淆,包括:代码混淆、代码压缩、资源压缩
混淆过程具体做了什么?
  答:将主项目和依赖库中未被使用的类、类成员、方法、属性移除,有助于规避64K方法数的瓶颈;同时,将类和类成员、方法重命名为无意义的字段,增加逆向工程难度
工具:ProGuard

入门


(一)build.gradle的混淆配置

  1. 我们在AS中创建一个新的Project,得到的app module 的build.gradle中的原始配置如下:
///原始的app/build.gradle中的打包混淆相关配置
android{
.................
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
..................
}
  1. 我们需要:让release打包时开启混淆minifyEnabled true和资源压缩shrinkResources true、让debug方式打包时关闭混淆minifyEnabled false【减少编译时间】
///原始的app/build.gradle中的打包混淆相关配置
android{
.................
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            minifyEnabled false
        }
    }
..................
}

(二)自定义混淆文件proguard-rules.pro

  • 常用通用自定义混淆配置
#指定压缩级别
-optimizationpasses 5
#不跳过非公共的库的类成员
-dontskipnonpubliclibraryclassmembers
#混淆时采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#把混淆类中的方法名也混淆了
-useuniqueclassmembernames
#优化时允许访问并修改有修饰符的类和类的成员 
-allowaccessmodification
#将文件来源重命名为“SourceFile”字符串
-renamesourcefileattribute SourceFile
#保留行号
-keepattributes SourceFile,LineNumberTable
#保持所有实现 Serializable 接口的类成员
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}
#Fragment不需要在AndroidManifest.xml中注册,需要额外保护下
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment
# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**
# 忽略警告 
-ignorewarning 
# 记录生成的日志数据,gradle build时在本项目根目录输出 
# apk 包内所有 class 的内部结构 
-dump class_files.txt 
# 未混淆的类和成员 
-printseeds seeds.txt 
# 列出从 apk 中删除的代码 
-printusage unused.txt 
# 混淆前后的映射 
-printmapping mapping.txt
  • 根据项目不同需要具体再加的混淆规则:
    • 第三方库开发指南指定的混淆规则
    • 在运行时动态改变的代码,例如反射。比较典型的例子就是会与 json 相互转换的实体类。假如项目命名规范要求实体类都要放在model包下的话,可以添加类似这样的代码把所有实体类都保持住:-keep public class **.*Model*.** {*;}
    • JNI 调用的类
    • WebViewJavaScript调用的方法
    • layout中使用的View构造函数、android:onClick

(三)检查混淆结果

  • Generate Signed APK完成后,默认会到app/build/outputs/mapping/release/目录下生成一些信息日志文件

    • dump.txt:描述APK文件中所有类的内部结构
    • mapping.txt:提供混淆前后类、方法、类成员等的对照表
    • seeds.txt:列出没有被混淆的类和成员
    • usage.txt:列出被移除的代码
  • Proguard工具 与 release apk bug调试

    • 【以Mac为例】在<SDK_rootDir>/tools/proguard/bin/目录下,有着反解相关GUI工具proguardgui.sh(Windows下为proguardgui.bat) 和 反解命令行工具retrace.sh

      • GUI反解工具proguardgui.sh:
        1. 命令行输入./proguardgui.sh(Windows下直接双击)
        2. 在弹出的界面中,点击左侧菜单的“ReTrace”按钮,选择混淆打包好的apk对应的mapping.txt文件,并将自己调试过程中看到了被混淆了的exception信息粘贴到下方编辑框
        3. 混淆后的堆栈信息就显示出来
    • 命令行反解工具retrace.sh:

      • 直接命令行输入:
        retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
        
        例如:retrace.sh -verbose mapping.txt obfuscated_trace.txt

巩固 和 深入


(一)混淆过程深入

虽然 宏观在讲,Android混淆打包过程有:代码混淆和资源压缩两个部分,但是,资源压缩(移除项目及依赖的库中未被使用的资源)实际上与真正意义上的“混淆”没有关系

  • 代码压缩优化过程】整个代码混淆优化过程具体包括几个流程:
    代码压缩 -> 优化 -> 混淆 -> 预校验
    • 压缩:移除无效的类、类成员、方法、属性等。
    • 优化:分析和优化方法的二进制代码;根据proguard-android-optimize.txt中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行。
    • 混淆:把类名、属性名、方法名替换为简短且无意义的名称。
    • 预校验:添加预校验信息。这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度。

    注意:
    1. 以上四个流程默认执行。
    2. 相关命令:-dontoptimize【关闭“优化”流程】,-dontpreverify【关闭“预校验”流程】(当然,默认的
    proguard-android.txt文件已包含这两条混淆命令,不需要开发者额外配置)

  • 资源压缩过程】资源压缩过程也包括几个流程:
    资源合并 -> 资源移除
    • 资源合并:名称相同的资源被视为重复资源会被合并。【此过程不受shrinkResources属性控制,也无法禁止,<u>gradle 必然会做这项工作,因为假如不同项目中存在相同名称的资源将导致错误</u>】
      • gradle查找资源的目录有:
        • src/main/res/
        • 不同构造类型:release 和 debug 等
        • 不同构建渠道
        • 项目依赖的第三方库
    • 优先级:依赖 < main < 渠道 < 构建类型
      • 资源在不同优先级中重复出现,则合并并保留高优先级资源。(比如:重复资源存在于main文件夹和不同渠道中,则选择保留渠道中的资源)
      • 资源在相同优先级中重复出现,则报错(不同依赖库的src/main/res/中都有同一个资源,则无法合并)

(二)自定义混淆规则

详细的规则写法可以去官网查看:http://developer.android.com/guide/developing/tools/proguard.html

  • 保持相关元素不参与混淆的规则:
命令 作用
-keep 防止类和成员被移除或者被重命名
-keepnames 防止类和成员被重命名
-keepclassmembers 防止成员被移除或者被重命名
-keepnames 防止成员被重命名
-keepclasseswithmembers 防止拥有该成员的类和成员被移除或者被重命名
-keepclasseswithmembernames 防止拥有该成员的类和成员被重命名
  • 语法格式:

    [命令] [类] {
        [成员] 
    }
    ///其中:
    /// [类]: 最终定位到符合某个条件的类。
      // 包括:具体类、访问修饰符、 '*'【匹配任意长度字符】、'**'【匹配包含分隔符'.'的任意长度字符】 、'extends'、'implements'、'$'【匹配某个类的内部类】
    ///[成员]: 最终定位到符合某个条件的类成员。
      // 包括:具体类、访问修饰符、 '*'【匹配任意长度字符】、'**'【匹配包含分隔符'.'的任意长度字符】 、'extends'、'implements'、'$'【匹配某个类的内部类】、'***'【匹配任意参数类型】、'...'【匹配任意长度的任意参数类型】
    
    
    例子:
    -keep public class com.jp.test.** extends Android.app.Activity {
      <init>
    

}

* 常用的代码保留的混淆规则例子【其实有点像写正则表达式】
* 不混淆某个类:
`-keep public class com.jp.example.Test { *; }`
* 不混淆某个包所有的类:
`-keep class com.jp.test.** { *; }`
* 不混淆某个类的子类:
`-keep class * extends com.jp.example.Test {*;}`
* 不混淆所有类名中含有"model"的类及其成员
`-keep public class **.*model*.** {*;}`
* 不混淆某个接口的实现
`-keep class * implements com.jp.interface.ILogin {*;}`
* 不混淆某个类的构造方法

-keepclassmembers com.jp.example.Person{
public <init>();
}

* 不混淆某个类的特定方法

-keepclassmembers com.jp.example.Person{
public void speak(java.lang.String){
}
}

* 资源保留的混淆规则制定【如果想强行保留某些本应该被移除的资源】
* 默认情况下,`shrinkResources true`会让所有未被使用的资源都移除。
* 特殊情况:在`res/raw/`目录中创建一个xml文件如`keep.xml`,然后设置相关属性:
  * `tools:keep`:定义哪些资源需要被保留(资源之间用“,”隔开)
  * `tools:discard`:定义哪些资源需要被移除(资源之间用“,”隔开)
  * `tools:shrinkMode="strict"`:开启严格模式
    运用情景:用动态的字符串来获取并使用资源时,普通的资源引用检查就可能会有问题。例如,如下代码会导致所有以“img_”开头的资源都被标记为已使用:
    ```
    String name = String.format("img_%1d", angle + 1);
    res = getResources().getIdentifier(name, "drawable", getPackageName());
    ```
    此时,设置严格模式,使只有确实被使用的资源被保留。
* 例子:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/l_used_c,@layout/l_used_a,@layout/l_used_b"
tools:discard="@layout/unused2"
tools:shrinkMode="strict"/>

* 移除替代资源
  * 一些替代资源,如多语言支持的`strings.xml`和多分辨率支持的`layout.xml`(比如说像项目中的 `src/res/values/`和`src/res/values-w820dp/`中都存在的`dimens.xml`文件),在不需要使用到时,可以设置被移除,使用`resConfigs`:
  ```
  android {
     defaultConfig {
         ...
         resConfigs "en", "fr" ///只保留:英语、法语,其他未显式声明的语言资源将被移除
     }
  }
  ```



###(三)再看`build.gradle`的混淆配置代码
* 【默认的混淆配置文件内容】看看`proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'`,意思是:混淆规则默认会是读取`AndroidSDK目录/tools/proguard/proguard-android.txt`文件 和 `项目/app/proguard_rules.pro`文件,分别表示:默认混淆配置 和 自定义混淆规则配置。
 而默认的混淆规则`proguard-android.txt`内容解释如下:

包名不混合大小写

-dontusemixedcaseclassnames

不跳过非公共的库的类

-dontskipnonpubliclibraryclasses

混淆时记录日志

-verbose

关闭预校验

-dontpreverify

不优化输入的类文件

-dontoptimize

保护注解

-keepattributes Annotation

保持所有拥有本地方法的类名及本地方法名

-keepclasseswithmembernames class * {
native <methods>;
}

保持自定义View的get和set相关方法

-keepclassmembers public class * extends android.view.View {
void set(*);
*** get
();
}

保持Activity中View及其子类入参的方法

-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}

枚举

-keepclassmembers enum * {
**[] $VALUES;
public *;
}

Parcelable

-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}

R文件的静态成员

-keepclassmembers class .R$ {
public static <fields>;
}
-dontwarn android.support.
*

keep相关注解

-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}


# 注意事项
---
1. 所有在`AndroidManifest.xml`涉及到的类已经自动被保持,因此不用特意去添加这块混淆规则。(很多老的混淆文件里会加,现在已经没必要)。
2. `proguard-android.txt`已经存在一些默认混淆规则,没必要在`proguard-rules.pro` 重复添加。

## 参考文章
* https://yq.aliyun.com/articles/62980?utm_campaign=wenzhang&utm_medium=article&utm_source=QQ-qun&2017314&utm_content=m_13399
*
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容