Android 代碼混淆零基礎入門

內容提要

本篇文章主要有三個部分,讓讀者讀完后能自己寫規(guī)則混淆項目

  • 對Android代碼怎么開啟混淆做一個簡單的介紹。
  • 對混淆規(guī)則做一個簡單介紹;
  • 在混淆過后Crash日志反推代碼工具retrace.bat、可視化反推工具GUI說明。

Proguard 混淆規(guī)則說明請參考《ProGuard 最全混淆規(guī)則說明》

對混淆的一個簡單介紹:

Android SDK 自帶了混淆工具Proguard。它位于SDK根目錄\tools\proguard下面。如果開啟了混淆,Proguard默認情況下會對所有代碼,包括第三方包都進行混淆,可是有些代碼或者第三方包是不能混淆的,這就需要我們手動編寫混淆規(guī)則來保持不能被混淆的部分。
混淆有幾個作用:

  • 【優(yōu)化】它能優(yōu)化java的字節(jié)碼,使程序運行更快;
  • 【壓縮】最直觀的就是減少App大小,在混淆過程中它會找出未被使用過的類和類成員并刪除他們;
  • 【混淆】這個功能使我們的java代碼中的類、函數、變量名隨機變成無意義的代號形如:a,b,c...之類的,即使我們的APP即使被反編譯,也不容易理解了。

上面這幾個功能都是默認打開的,要關閉他們只需配置規(guī)則:
-dontshrink :關閉壓縮;
-dontoptimize:關閉優(yōu)化;
-dontobfuscate:關閉混淆。

寫在開始之前:
本編使用Android Studio工具作為開發(fā)環(huán)境,Eclipse類似;
Android Studio新建項目時會在項目根目錄下自動生成一個混淆配置文件:
proguard-rules.pro;【Eclipse 下面的文件名為:proguard.cfg或者proguard.txt】里面的內容規(guī)則都是一樣的;

1.現在開始混淆:

第一步:開啟混淆,編輯項目配置文件build.gradle(Module: app)

*** minifyEnabled true;這個選項的意思為開啟混淆;
*** proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro';//配置文件
**

'proguard-android.txt' 是AndroidStudio默認自動導入的規(guī)則,這個文件位于Android SDK根目錄\tools\proguard\proguard-android.txt。這里面是一些比較常規(guī)的不能被混淆的代碼規(guī)則。
??
'proguard-rules.pro'
是針對我們自己的項目需要特別定義混淆規(guī)則,它位于項目根目錄下面,里面的內容需要我們自己編寫

zipAlignEnabled true 這個在打包時需要設置為true,能優(yōu)化我們的java字節(jié)碼,提高運行效率;

build.gradle編輯完成后如下圖所示:(表示發(fā)布的release包,會按照指定規(guī)則來進行混淆)

android {
    compileSdkVersion 23
    buildToolsVersion "24.0.1"
    defaultConfig {
    }
    buildTypes {
        release { //主要看這部分:
            zipAlignEnabled true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
 ....
} 
第二步:現在就可以開始編輯自己的規(guī)則了

下面是一個比較簡單的proguard-rules.pro文件內容,做完這些就可以打包發(fā)布版本了

1.png

將混淆過的APK反編譯,你會發(fā)現里面的代碼變成了a、b、c之類的沒有意義的代號了,而且APK也變小了。

2.重點來了【規(guī)則介紹】

現在對混淆規(guī)則做一個介紹:

先來看一看Android studio 默認包含的規(guī)則文件(proguard-android.txt 文件)里面的類容,我們選出幾個重點說明:

#混淆時不生成大小寫混合的類名
-dontusemixedcaseclassnames
#不忽略非公共的類庫
-dontskipnonpubliclibraryclasses
#混淆過程中打印詳細信息
-verbose

#關閉優(yōu)化
-dontoptimize
#不預校驗
-dontpreverify

# Annotation注釋不能混淆
-keepattributes *Annotation*
#對于NDK開發(fā) 本地的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類靜態(tài)成員不被混淆
-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>(...);
}
上面默認的規(guī)則中指示了些需要保持不能別混淆的代碼,包括:
  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開發(fā) 本地的native方法不能被混淆
    -keepclasseswithmembernames class * { native <methods>; }
對于特定的項目還有很多不能被混淆的,需要我們自己寫規(guī)則來指示,將在下面來說明:
  • 對一些符號做一些說明

現在來看一下需要我們自己編寫的proguard-rules.pro:

#壓縮級別0-7,Android一般為5(對代碼迭代優(yōu)化的次數)
-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{*;}
......
對一些規(guī)則的解釋:

-keepattributes {name}??保護給定的屬性不被混淆,
??如:-keepattributes *Annotation*


-dontwarn {name}??不要警告指定庫中找不到的引用?;煜谀J情況下會檢查每個庫的引用是否正確,但是有些第三方庫里面會有用不到的類,有些沒有正確引用,所以需要對第三方庫取消警告 否則會報錯,而且有可能混淆時間會很長。
??如:-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日志更容易定位可以在規(guī)則里面添加:

-keepattributes SourceFile, LineNumberTable

**這樣Crash日志里面就能保留類名稱 和行號了**

**好了,所有終于完了,希望能幫助到你!謝謝!**
***
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評論 25 708
  • Android 開發(fā)中為了代碼安全一般都會使用 ProGuard 進行代碼混淆,它可以把類名、屬性名和方法名變?yōu)楹?..
    JohnnyShieh閱讀 4,465評論 2 13
  • 聲明 這篇文章更多的是做一個整理,內容來自于ProGuard官方文檔以及各種博客等,相關文章的鏈接在參考目錄里,感...
    夷陵小祖閱讀 3,805評論 0 23
  • 概述 混淆是Android Apk打包過程中的一個重要步驟,默認情況下,打包都是需要混淆過程的。?Android ...
    androidjp閱讀 2,756評論 1 13
  • 懷孕期間,曾斷斷續(xù)續(xù)寫下數十篇日記。還留存了好多照片,不時翻看,美好至極。趁產假時間充裕,整理記錄在此,留作紀念!...
    夜光罩著你閱讀 403評論 0 0

友情鏈接更多精彩內容