Android修煉之混淆

自嘲時刻

做爲Java和Android開發者,你們應該都對混淆很熟悉了。網上也有各路大神提供的混淆模板,基本上直接拿來用就好。但我仍是想捋一捋,由於工做中被混淆這傢伙「玩弄」了好幾回,必須把它記在小本本上。javascript

介紹

基本概念

混淆,字面上來講就是把項目中的包名、類名、方法名和變量名等進行更改,用以迷惑別人。但混淆其實包含了代碼壓縮、優化、校驗等過程,把混淆稱做ProGuard更合適。html

ProGuard

PS:ProGuard官網
stuff.mit.edu/afs/sipb/pr…java

ProGuard就是Java對Class文件進行「混淆」的工具。直接貼圖吧:linux

ProGuard過程
(看到這個圖,你們有沒有想到什麼?——設計模式中的 責任鏈模式。)

  1. shrink(壓縮):ProGuard會遞歸地肯定哪些類和類成員被使用,而其餘的則被丟棄。
  2. optimize(優化):ProGuard會進一步分析和優化方法。好比一些無用的參數會被丟棄,一些方法會作內聯。
  3. obfuscate(混淆):這個過程就是進行重命名了,把原來包含註釋意義的類名、方法名等進行無心義重命名。
  4. preverify(預校驗):這個步驟是將預校驗信息添加到類中。

這四個步驟其實都是可選的。固然,Android中通常狀況下咱們都會保留前三個步驟,而忽略preverify過程,這樣能夠加快混淆速度。android

Android中的ProGuard

Android中默認集成了ProGuard工具,在sdk目錄的/tools/proguard中。混淆開啓方法:web

android {
    ...
    // AS自動生成
    buildTypes {
        release {
            // 混淆開關
            minifyEnabled true
            // 移除無用的resource文件
            shrinkResources true
            // proguard-android.txt表示默認的混淆規則
            // proguard-rules.pro表示自定義的混淆規則(文件名和後綴能夠修改)
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } 複製代碼

默認的proguard-android.txt文件在sdk目錄/tools/proguard中。該目錄下還有個proguard-android-optimize.txt文件。而咱們自定義的proguard-rules.pro文件中有部分基礎混淆規則就是來自proguard-android-optimize.txt算法

混淆語法

混淆的語法均可以在上述的ProGuard官網中找到,這裏只介紹一些經常使用的語法規則。設計模式

一、保留類和類成員安全

保留 反之被刪除或重命名 防止被重命名
類和類成員 -keep -keepnames
僅類成員 -keepclassmembers -keepclassmembernames
若是擁有某成員,保留類和類成員 -keepclasseswithmembers -keepclasseswithmembernames

二、類成員中的一些符號bash

符號 做用
<init> 匹配全部構造器
<fields> 匹配全部域
<methods> 匹配全部方法
* 匹配全部域和方法

三、一些經常使用通配符

通配符 做用
* 匹配任意長度字符,但不含包名分隔符(.)
** 匹配任意長度字符,而且包含包名分隔符(.)
*** 匹配任意參數類型
... 匹配任意長度的任意類型參數
% 匹配任何原始類型
? 匹配類名中的任何單個字符

其餘的一些語法基本上屬於無變化的那種,在混淆規則中基本都固定使用了,有須要的話能夠自行在官方查詢。

自定義混淆模板

嗯...這模板基本都是大同小異的,這裏就直接貼出經常使用的混淆規則了。

# 優化算法,通常不用修改,來自Google
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# 代碼混淆壓縮比,在0~7之間,默認爲5,通常不作修改
-optimizationpasses 5
# 混合時不使用大小寫混合,混合後的類名爲小寫
-dontusemixedcaseclassnames
# 不去忽略非公共庫的類
-dontskipnonpubliclibraryclasses
# 優化時容許訪問並修改有修飾符的類和類的成員 
-allowaccessmodification
# 項目混淆後產生映射文件
-verbose
# 不作預校驗
-dontpreverify

# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
# 拋出異常時保留代碼行號
-keepattributes SourceFile,LineNumberTable

# 保留四大組件,自定義的Application等
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-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.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# 保留native方法
-keepclasseswithmembernames class * {
    native <methods>;
}

# 保留在Activity中的方法參數是view的方法,
# 這樣以來咱們在layout中寫的onClick就不會被影響
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}

# 保留自定義View
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
   public <init>(android.content.Context);
   public <init>(android.content.Context, android.util.AttributeSet);
   public <init>(android.content.Context, android.util.AttributeSet, int);
}

# 保留枚舉
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保留Parcelable序列化對象
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

# 保留R文件中的成員
-keepclassmembers class **.R$* {
    public static <fields>;
}

# 保留Serializable序列化的類不被混淆
-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    !private <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# -------------------------------------------------------------------------------
# webView處理,項目中沒有使用到webView忽略便可
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
    public *;
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.webView, jav.lang.String);
}

# --------------------------保留JS接口-------------------------------------------

# --------------------------保留反射類-------------------------------------------

# --------------------------保留實體類-------------------------------------------

# --------------------------第三方庫的混淆規則-----------------------------------

複製代碼

以上都是一些經常使用的混淆規則,其中部分規則其實都在Android默認混淆文件中聲明過了。因此具體的自定義混淆規則仍是得根據項目進行變化。

混淆產物

混淆後通常都有下面幾個文件:

  1. dump.txt:描述APK文件中全部類的內部結構;
  2. mapping.txt:提供混淆先後類、方法、類成員等的對照表
  3. seeds.txt:列出沒有被混淆的類和成員
  4. usage.txt:列出被移除的代碼

遇到混淆問題時,我一般是經過查看mapping.txt文件來分析緣由的。
若是在進行Crash追蹤中遇到了困難,可使用sdk目錄下/tools/proguard/bin中的proguardgui.bat可視化工具進行混淆定位。示例如圖:

追蹤混淆Crash棧

混淆引起的「Xue案」

場景一
問題描述:業務方集成了咱們的一個SDK,SDK的一個頁面中會顯示Banner圖,而Banner圖所用的是SDK中自定義的ViewPager組件,結果業務方那邊沒法顯示Banner圖。
緣由分析:經過反編譯apk,比對mapping.txt文件,發現Banner圖的自定義Adapter中關鍵方法都被清除了。查看混淆文件,發現只keep了support v4的ViewPager,並無keep住PagerAdapter。而SDK中又是經過compileOnly(provided)方式依賴v4包的,致使自定義的Adapter在工程編譯的時候找不到被引用的關係,而後就被混淆給優化掉了。
經驗:針對compileOnly(provided)依賴的庫,必定要注意相關類的混淆,特別是SDK開發者,由於混淆致使的問題通常較難定位。

場景二
問題描述:業務方又集成了咱們的一個SDK(沒錯,是「又」),SDK有一個換膚功能,業務方能夠經過構造特定格式的資源文件來實現皮膚替換。但在業務經過公司編譯工具編譯後的apk中沒法實現換膚功能。
緣由分析:機智的我很快想到了混淆問題,因而讓業務方加上保留全部R文件的規則,結果仍是失敗。因而開始了長時間的打log看log過程,仍是肯定問題出在資源名的混淆上。最後發現業務方雖然在本地編譯環境中keep了資源名,但在公司的編譯環境中仍是對資源進行了混淆,致使換膚失敗。
經驗:對於和資源相關的功能,必定要注意R文件的混淆。不要輕易相信業務,由於他們常常有你預料不到的操做(手動滑稽...)。

工做中遇到過不少次混淆相關的問題,這裏就很少說了,只要你們細心點就行了。

總結

  1. 混淆其實不算是什麼新鮮的話題,但對於Java和Android開發者來講是必不可少的,仍是要對其有敬畏之心(嚴肅臉...)。
  2. 混淆的好處不言而喻增長了反編譯的難度,優化了代碼。但僅僅是增長難度而已,對於有心之人仍是能從反編譯中找到蛛絲馬跡。因此,對於敏感代碼和數據使用更安全的保護措施是很是必要的,好比加固(硬廣一波:360加固保)。

參考資料

  1. Android安全攻防戰,反編譯與混淆技術徹底解析(下)
  2. Android Studio混淆模板及經常使用第三方混淆
  3. 寫給 Android 開發者的混淆使用手冊
  4. ProGuard

關注微信公衆號,最新技術乾貨實時推送

image
相關文章
相關標籤/搜索