Android混淆

weixin_33686714發表於2018-06-13

混淆介紹

Proguard是一個Java類檔案壓縮器、優化器、混淆器、預校驗器。壓縮環節會檢測以及移除沒有用到的類、欄位、方法以及屬性。優化環節會分析以及優化方法的位元組碼。混淆環節會用無意義的短變數去重新命名類、變數、方法。這些步驟讓程式碼更精簡,更高效,也更難被逆向(破解)。

混淆後預設會在工程目錄app/build/outputs/mapping/release(debug)下生成一個mapping.txt檔案,這就是混淆規則,我們可以根據這個檔案把混淆後的程式碼反推回源本的程式碼,所以這個檔案很重要,注意保護好。原則上,程式碼混淆後越亂越無規律越好,但有些地方我們是要避免混淆的,否則程式執行就會出錯。

ProGuard常用操作

壓縮(Shrinking)

壓縮(Shrinking):預設開啟,用以減小應用體積,移除未被使用的類和成員,並且會在優化動作執行之後再次執行(因為優化後可能會再次暴露一些未被使用的類和成員)。

#關閉壓縮
-dontshrink
複製程式碼

優化(Optimization)

優化(Optimization):預設開啟,在位元組碼級別執行優化,讓應用執行的更快。

#關閉優化
#-dontoptimize  

#表示proguard對程式碼進行迭代優化的次數,Android一般為5
-optimizationpasses n  
複製程式碼

混淆(Obfuscation)

混淆(Obfuscation):預設開啟,增大反編譯難度,類和類成員會被隨機命名,除非用keep保護。

-dontobfuscate  #關閉混淆
複製程式碼

-Keep

一顆星表示只是保持該包下的類名,而子包下的類名還是會被混淆;

-keep class pr.tongson.bean.*
複製程式碼

兩顆星表示把本包和所含子包下的類名都保持;

-keep class pr.tongson.bean.**
複製程式碼

(上面兩種方式保持類後,會發現類名雖然未混淆,但裡面的具體方法和變數命名還是變了)

既可以保持該包下的類名,又可以保持類裡面的內容不被混淆;

-keep class pr.tongson.bean.*{*;}
複製程式碼

既可以保持該包及子包下的類名,又可以保持類裡面的內容不被混淆;

-keep class pr.tongson.bean.**{*;}
複製程式碼

保持某個類名不被混淆(但是內部內容會被混淆)

-keep class pr.tongson.bean.KeyBoardBean
複製程式碼

保持某個類的 類名及內部的所有內容不會混淆

-keep class pr.tongson.bean.KeyBoardBean{*;}
複製程式碼

保持類中特定內容,而不是所有的內容可以使用如下:

-keep class pr.tongson.bean.KeyBoardBean{
  #匹配所有構造器
  <init>;
  #匹配所有域
  <fields>;
  #匹配所有方法
  <methods>;
}
複製程式碼

(上面就保持住了KeyBoardBean這個類中的所有的構造方法、變數、和方法)

可以在或前面加上private 、public、native等來進一步指定不被混淆的內容

-keep class pr.tongson.algorithm.Calculate{
  #保持該類下所有的共有方法不被混淆
  public <methods>;
  #保持該類下所有的共有內容不被混淆
  public *;
  #保持該類下所有的私有方法不被混淆
  private <methods>;
  #保持該類下所有的私有內容不被混淆
  private *;
  #保持該類的String型別的構造方法
  public <init>(java.lang.String);
}
複製程式碼

在方法後加入引數,限制特定的方法(經測試:僅限於構造方法可以混淆)

-keep class pr.tongson.algorithm.Calculate{
      public <init>(String);
}
複製程式碼

要保留一個類中的內部類不被混淆需要用 $ 符號

#保持Calculate中的MyClass不被混淆
-keep class pr.tongson.algorithm.Calculate$MyClass{*;}
複製程式碼

使用Java的基本規則來保護特定類不被混淆,比如用extends,implement等這些Java規則,如下:保持Android底層元件和類不要混淆

-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.view.View
複製程式碼

如果不需要保持類名,只需要保持該類下的特定方法保持不被混淆,需要使用keepclassmembers,而不是keep,因為keep方法會保持類名。

#保持ProguardTest類下test(String)方法不被混淆
-keepclassmembernames class pr.tongson.algorithm.Calculate{
  public void test(java.lang.String);
}
複製程式碼

如果擁有某成員,保留類和類成員

-keepclasseswithmembernames class pr.tongson.algorithm.Calculate
複製程式碼

注意事項

jni方法

jni方法不可混淆,因為native方法是要完整的包名類名方法名來定義的,不能修改,否則找不到;

#保持native方法不被混淆
-keepclasseswithmembernames class * {    
  native <methods>; 
}
複製程式碼

反射

反射用到的類混淆時需要注意:只要保持反射用到的類名和方法即可,並不需要將整個被反射到的類都進行保持

AndroidMainfest中的類

AndroidMainfest中的類不混淆,所以四大元件和Application的子類和Framework層下所有的類預設不要進行混淆。

自定義的View

自定義的View預設也不會被混淆

JSON物件類

與服務端互動時,使用GSON、fastjson等框架解析服務端資料時,所寫的JSON物件類不混淆,否則無法將JSON解析成對應的物件;

第三方

使用第三方開源庫或者引用其他第三方的SDK包時,如果有特別要求,也需要在混淆檔案中加入對應的混淆規則;官方文件一般都有混淆規則的,複製貼上下即可。

有用到WebView的JS

有用到WebView的JS呼叫也需要保證寫的介面方法不混淆,原因和第一條一樣;

Parcelable的子類和Creator靜態成員變數

Parcelable的子類和Creator靜態成員變數不混淆,否則會產生Android.os.BadParcelableException異常;

-keep class * implements Android.os.Parcelable { 
  # 保持Parcelable不被混淆            
  public static final Android.os.Parcelable$Creator *;
}
複製程式碼

enum

使用enum型別時需要注意避免以下兩個方法混淆,因為enum類的特殊性,以下兩個方法會被反射呼叫,見第二條規則。

-keepclassmembers enum * {  
  public static **[] values();  
  public static ** valueOf(java.lang.String);  
}
複製程式碼

註解不能混淆

建議

建議: 釋出一款應用除了設minifyEnabled為ture,你也應該設定zipAlignEnabled為true,像Google Play強制要求開發者上傳的應用必須是經過zipAlign的,zipAlign可以讓安裝包中的資源按4位元組對齊,這樣可以減少應用在執行時的記憶體消耗。

混淆情況記錄

例子中使用:classA和classB,在加混淆的情況下多種結果:

  • 如果classA沒有被keep,則不會看到classA的class檔案

  • 如果classA沒有被keep,classB被保持,同時classB引用到了classA,這個時候能夠看到被混淆的classA的class檔案,如顯示為a

  • 如果classA中通過反射,獲取到classB,那麼classB的類名及反射用到的方法必須keep住

  • jar包混淆,暴露出的類、方法、方法的引數需要keep住

  • 情況說明:工程Demo依賴了小米渠道的依賴,小米依賴又依賴了Common,對Common進行混淆但是不對小米渠道混淆,那麼小米的依賴中使用到的Common中的類都需要keep住

參考

Proguard官方文件

Android混淆從入門到精通

Android混淆——瞭解這些就夠了

runoob

模版

#指定壓縮級別
-optimizationpasses 5

#不跳過非公共的庫的類成員
-dontskipnonpubliclibraryclassmembers

#混淆時採用的演算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

#把混淆類中的方法名也混淆了
-useuniqueclassmembernames

#優化時允許訪問並修改有修飾符的類和類的成員
-allowaccessmodification

#將檔案來源重新命名為“SourceFile”字串
-renamesourcefileattribute SourceFile
#保留行號
-keepattributes SourceFile,LineNumberTable
#保持泛型
-keepattributes Signature

#保持所有實現 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.**
複製程式碼

語法

-include {filename}    從給定的檔案中讀取配置引數 
-basedirectory {directoryname}    指定基礎目錄為以後相對的檔案名稱 
-injars {class_path}    指定要處理的應用程式jar,war,ear和目錄 
-outjars {class_path}    指定處理完後要輸出的jar,war,ear和目錄的名稱 
-libraryjars {classpath}    指定要處理的應用程式jar,war,ear和目錄所需要的程式庫檔案 
-dontskipnonpubliclibraryclasses    指定不去忽略非公共的庫類。 
-dontskipnonpubliclibraryclassmembers    指定不去忽略包可見的庫類的成員。

保留選項 
-keep {Modifier} {class_specification}    保護指定的類檔案和類的成員 
-keepclassmembers {modifier} {class_specification}    保護指定類的成員,如果此類受到保護他們會保護的更好
-keepclasseswithmembers {class_specification}    保護指定的類和類的成員,但條件是所有指定的類和類成員是要存在。 
-keepnames {class_specification}    保護指定的類和類的成員的名稱(如果他們不會壓縮步驟中刪除) 
-keepclassmembernames {class_specification}    保護指定的類的成員的名稱(如果他們不會壓縮步驟中刪除) 
-keepclasseswithmembernames {class_specification}    保護指定的類和類的成員的名稱,如果所有指定的類成員出席(在壓縮步驟之後) 
-printseeds {filename}    列出類和類的成員-keep選項的清單,標準輸出到給定的檔案 

壓縮 
-dontshrink    不壓縮輸入的類檔案 
-printusage {filename} 
-dontwarn   如果有警告也不終止
-whyareyoukeeping {class_specification}     

優化 
-dontoptimize    不優化輸入的類檔案 
-assumenosideeffects {class_specification}    優化時假設指定的方法,沒有任何副作用 
-allowaccessmodification    優化時允許訪問並修改有修飾符的類和類的成員 

混淆 
-dontobfuscate    不混淆輸入的類檔案 
-printmapping {filename} 
-applymapping {filename}    重用對映增加混淆 
-obfuscationdictionary {filename}    使用給定檔案中的關鍵字作為要混淆方法的名稱 
-overloadaggressively    混淆時應用侵入式過載 
-useuniqueclassmembernames    確定統一的混淆類的成員名稱來增加混淆 
-flattenpackagehierarchy {package_name}    重新包裝所有重新命名的包並放在給定的單一包中 
-repackageclass {package_name}    重新包裝所有重新命名的類檔案中放在給定的單一包中 
-dontusemixedcaseclassnames    混淆時不會產生形形色色的類名 
-keepattributes {attribute_name,...}    保護給定的可選屬性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and 

InnerClasses. 
-renamesourcefileattribute {string}    設定原始檔中給定的字串常量
複製程式碼

常見錯誤

相關文章