Android 代码混淆零基础入门

内容提要

本篇文章主要有三个部分,让读者读完后能自己写规则混淆项目

  • 对Android代码怎么开启混淆做一个简单的介绍。
  • 对混淆规则做一个简单介绍;
  • 在混淆过后Crash日志反推代码工具retrace.bat、可视化反推工具GUI说明。

Proguard 混淆规则说明请参考《ProGuard 最全混淆规则说明》

对混淆的一个简单介绍:

Android SDK 自带了混淆工具Proguard。它位于SDK根目录\tools\proguard下面。如果开启了混淆,Proguard默认情况下会对所有代码,包括第三方包都进行混淆,可是有些代码或者第三方包是不能混淆的,这就需要我们手动编写混淆规则来保持不能被混淆的部分。
混淆有几个作用:

  • 【优化】它能优化java的字节码,使程序运行更快;
  • 【压缩】最直观的就是减少App大小,在混淆过程中它会找出未被使用过的类和类成员并删除他们;
  • 【混淆】这个功能使我们的java代码中的类、函数、变量名随机变成无意义的代号形如:a,b,c...之类的,即使我们的APP即使被反编译,也不容易理解了。

上面这几个功能都是默认打开的,要关闭他们只需配置规则:
-dontshrink :关闭压缩;
-dontoptimize:关闭优化;
-dontobfuscate:关闭混淆。

写在开始之前:
本编使用Android Studio工具作为开发环境,Eclipse类似;
Android Studio新建项目时会在项目根目录下自动生成一个混淆配置文件:
proguard-rules.pro;【Eclipse 下面的文件名为:proguard.cfg或者proguard.txt】里面的内容规则都是一样的;

1.现在开始混淆:

第一步:开启混淆,编辑项目配置文件build.gradle(Module: app)

*** minifyEnabled true;这个选项的意思为开启混淆
*** proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro';//配置文件
**

'proguard-android.txt' 是AndroidStudio默认自动导入的规则,这个文件位于Android SDK根目录\tools\proguard\proguard-android.txt。这里面是一些比较常规的不能被混淆的代码规则。
  
'proguard-rules.pro'
是针对我们自己的项目需要特别定义混淆规则,它位于项目根目录下面,里面的内容需要我们自己编写

zipAlignEnabled true 这个在打包时需要设置为true,能优化我们的java字节码,提高运行效率;

build.gradle编辑完成后如下图所示:(表示发布的release包,会按照指定规则来进行混淆)

android {
    compileSdkVersion 23
    buildToolsVersion "24.0.1"
    defaultConfig {
    }
    buildTypes {
        release { //主要看这部分:
            zipAlignEnabled true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
 ....
} 
第二步:现在就可以开始编辑自己的规则了

下面是一个比较简单的proguard-rules.pro文件内容,做完这些就可以打包发布版本了

1.png

将混淆过的APK反编译,你会发现里面的代码变成了a、b、c之类的没有意义的代号了,而且APK也变小了。

2.重点来了【规则介绍】

现在对混淆规则做一个介绍:

先来看一看Android studio 默认包含的规则文件(proguard-android.txt 文件)里面的类容,我们选出几个重点说明:

#混淆时不生成大小写混合的类名
-dontusemixedcaseclassnames
#不忽略非公共的类库
-dontskipnonpubliclibraryclasses
#混淆过程中打印详细信息
-verbose

#关闭优化
-dontoptimize
#不预校验
-dontpreverify

# Annotation注释不能混淆
-keepattributes *Annotation*
#对于NDK开发 本地的native方法不能被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}
#保持View的子类里面的set、get方法不被混淆(*代替任意字符)
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

#保持Activity子类里面的参数类型为View的方法不被混淆,如被XML里面应用的onClick方法
# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

#保持枚举类型values()、以及valueOf(java.lang.String)成员不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

#保持实现Parcelable接口的类里面的Creator成员不被混淆
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

#保持R类静态成员不被混淆
-keepclassmembers class **.R$* {
    public static <fields>;
}

#不警告support包中不使用的引用
-dontwarn android.support.**
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
#保持使用了Keep注解的方法以及类不被混淆
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}
#保持使用了Keep注解的成员域以及类不被混淆
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}
上面默认的规则中指示了些需要保持不能别混淆的代码,包括:
  1. 继承至Android组件(Activity, Service...)的类。
  2. 自定义控件,继承至View的类(被xml文件引用到的,名字已经固定了的)
  3. enum 枚举
  4. 实现了 android.os.Parcelable 接口的
  5. Android R文件
  6. 数据库驱动...
  7. Android support 包等
  8. Android 的注释不能混淆
    -keepattributes *Annotation*
  9. 对于NDK开发 本地的native方法不能被混淆
    -keepclasseswithmembernames class * { native <methods>; }
对于特定的项目还有很多不能被混淆的,需要我们自己写规则来指示,将在下面来说明:
  • 对一些符号做一些说明

现在来看一下需要我们自己编写的proguard-rules.pro:

#压缩级别0-7,Android一般为5(对代码迭代优化的次数)
-optimizationpasses 5 

#不使用大小写混合类名
-dontusemixedcaseclassnames 

 #混淆时记录日志
-verbose

#不警告org.greenrobot.greendao.database包及其子包里面未应用的应用
-dontwarn org.greenrobot.greendao.database.**
-dontwarn rx.**
-dontwarn org.codehaus.jackson.**
......
#保持jackson包以及其子包的类和类成员不被混淆
-keep class org.codehaus.jackson.** {*;}
#--------重要说明-------
#-keep class 类名 {*;}
#-keepclassmembers class 类名{*;}
#一个*表示保持了该包下的类名不被混淆;
# -keep class org.codehaus.jackson.*
#二个**表示保持该包以及它包含的所有子包下的类名不被混淆
# -keep class org.codehaus.jackson.** 
#------------------------
#保持类名、类里面的方法和变量不被混淆
-keep class org.codehaus.jackson.** {*;}
#不混淆类ClassTwoOne的类名以及类里面的public成员和方法
#public 可以换成其他java属性如private、public static 、final等
#还可以使<init>表示构造方法、<methods>表示方法、<fields>表示成员,
#这些前面也可以加public等java属性限定
-keep class com.dev.demo.two.ClassTwoOne {
    public *;
}
#不混淆类名,以及里面的构造函数
-keep class com.dev.demo.ClassOne {
    public <init>();
}
#不混淆类名,以及参数为int 的构造函数
-keep class com.dev.demo.two.ClassTwoTwo {
    public <init>(int);
}
#不混淆类的public修饰的方法,和private修饰的变量
-keepclassmembers class com.dev.demo.two.ClassTwoThree {
    public <methods>;
    private <fields>;
}
#不混淆内部类,需要用$修饰
#不混淆内部类ClassTwoTwoInner以及里面的全部成员
-keep class com.dev.demo.two.ClassTwoTwo$ClassTwoTwoInner{*;}
......
对一些规则的解释:

-keepattributes {name}  保护给定的属性不被混淆,
  如:-keepattributes *Annotation*


-dontwarn {name}  不要警告指定库中找不到的引用。混淆在默认情况下会检查每个库的引用是否正确,但是有些第三方库里面会有用不到的类,有些没有正确引用,所以需要对第三方库取消警告 否则会报错,而且有可能混淆时间会很长。
  如:-dontwarn org.codehaus.jackson.**


-keep {Modifier} {class_specification}  保留指定的类名、类成员不被混淆


-keepclassmembers {modifier} {class_specification}   保留指定的类成员不被混淆


-keepclasseswithmembers {class_specification}   保留指定的类名、类成员不被混淆


-keepnames {class_specification}   保留指定的类名、类成员的名称不被混淆


-keepclasseswithmembernames {class_specification}   保留指定的类名、类成员名称不被混淆(如果存在的话)


3.混淆后根据Crash崩溃日志反推代码

成功打包后会在目录*** app\build\outputs\mapping\release ***下生成几个文件:

2.png

说明:
dump.txt 混淆后类的内部结构说明;
mapping.txt 混淆前与混淆后名称对应关系;
seeds.txt 经过了一系列keep语句的保持,没有被混淆的类,成员的名称列表文件。
usage.txt 经过压缩后被删除的没有使用的代码,方法...等的名称的列表文件

retrace工具:
混淆反推工具为retrace.sh(Mac平台)或者retrace.bat(Windows平台)
该工具在sdk根目录\tools\proguard\bin\retrace.sh
(Windows平台类似);
命令格式:
*** ./retrace.sh [mapping.txt目录] [崩溃日历目录]
(Windows平台:retrace.bat [mapping.txt目录] [崩溃日历目录]) ***

  • 第一步:保存Crash日志如下:截取日志保存为txt文件如:bug.txt。
03-21 03:09:32.389: E/AndroidRuntime(3582): FATAL EXCEPTION: main
03-21 03:09:32.389: E/AndroidRuntime(3582): Process: com.dev.demo, PID: 3582
03-21 03:09:32.389: E/AndroidRuntime(3582): java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
03-21 03:09:32.389: E/AndroidRuntime(3582):     at com.dev.demo.two.b.b(Unknown Source)
03-21 03:09:32.389: E/AndroidRuntime(3582):     at com.dev.demo.a.a.printTest(Unknown Source)
03-21 03:09:32.389: E/AndroidRuntime(3582):     at com.dev.demo.MainActivity.i(Unknown Source)
03-21 03:09:32.389: E/AndroidRuntime(3582):     at com.dev.demo.MainActivity.a(Unknown Source)
03-21 03:09:32.389: E/AndroidRuntime(3582):     at com.dev.demo.MainActivity$1.onClick(Unknown Source)
  • 第二步:删掉Crash日志中03-21 03:09:32.389: E/AndroidRuntime(3582):部分,否则无法反推还原。删除后如下:
FATAL EXCEPTION: main
Process: com.dev.demo, PID: 3582
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
at com.dev.demo.two.b.b(Unknown Source)
at com.dev.demo.a.a.printTest(Unknown Source)
at com.dev.demo.MainActivity.i(Unknown Source)
at com.dev.demo.MainActivity.a(Unknown Source)
at com.dev.demo.MainActivity$1.onClick(Unknown Source)
  • 第三步:打开命令窗口输入命令:如:*** ./retrace.sh mapping.txt bug.txt ***
    如下:
3.png

按确认后得到结果如下:

FATAL EXCEPTION: main
Process: com.dev.demo, PID: 3582
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
at com.dev.demo.two.ClassTwoThree.void test()(Unknown Source)
at com.dev.demo.one.ClassOneOne.void printTest()(Unknown Source)
at com.dev.demo.MainActivity.void printWifi()(Unknown Source)
at com.dev.demo.MainActivity.void access$000(com.dev.demo.MainActivity)(Unknown Source)
at com.dev.demo.MainActivity$1.void onClick(android.view.View)(Unknown Source) ```

除了有retrace.sh工具外还有可视化工具,和retrace.sh同目录下proguardgui.sh,这是一块Progurad的可视化工具:
(Windows平台类似)
![4.png](http://upload-images.jianshu.io/upload_images/3589324-f0d492d9da432138.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

>有时为了Crash日志更容易定位可以在规则里面添加:

-keepattributes SourceFile, LineNumberTable

**这样Crash日志里面就能保留类名称 和行号了**

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

推荐阅读更多精彩内容