Android混淆

一、爲什麼要混淆

爲了避免apk在發佈後被用戶通過反編譯拿到源代碼和資源文件,然後修改資源和代碼之後就變成一個新的apk。而經過混淆後的APK,即使被反編譯,也難以閱讀,注意混淆不是讓apk不能閱讀,而是加大閱讀的難度,爲了避免勞動成果被竊取,也避免出現安全漏洞和隱患,所以在apk發佈之前一定要進行混淆。

二、混淆的原理

Java是一種跨平臺、解釋型語言,Java源代碼編譯成的class文件中有大量包含語義的變量名、方法名的信息,很容易被反編譯爲Java源代碼。爲了防止這種現象,我們可以對Java字節碼進行混淆。混淆不僅能將代碼中的類名、字段、方法名變爲無意義的名稱,保護代碼,也由於移除無用的類、方法,並使用簡短名稱對類、字段、方法進行重命名縮小了程序的大小。

ProGuard由shrink、optimize、obfuscate和preverify四個步驟組成,每個步驟都是可選的,需要哪些步驟都可以在腳本中配置。參見ProGuard官方介紹。
  壓縮(Shrink):默認開啓,偵測並移除代碼中無用的類、字段、方法和特性,減少應用體積,並且會在優化動作執行之後再次執行(因爲優化後可能會再次暴露一些未使用的類和成員)。
    -dontshrink 關閉混淆
  優化(Optimize):默認開啓,分析和優化字節碼,讓應用運行的更快。
    -dontoptimize 關閉優化,默認混淆配置文件開始
    -optimizationpasses n 表示proguard對代碼進行迭代優化的次數,Android一般爲5
  混淆(Obfuscate):默認開啓,使用a、b、c、d這樣簡短而無意義的名稱,對類、字段和方法進行重命名,增大反編譯難度。
    -dontobfuscate 關閉混淆
上面三個步驟使代碼大小更小、更高效,也更難被逆向工程。
  預檢(Preverify):在java平臺上對處理後的代碼進行預檢。
混淆流程圖:


Proguard讀入input jars(or wars,zip or directories),經過四個步驟生成處理之後的jars(or wars,ears,zips or directories),Optimization步驟可選擇多次進行。
爲了確定哪些代碼應該被保留,哪些代碼應該被移除或混淆,需要確定一個或多個Entry Point。Entry Point經常是帶有main methods,applets,midlets的classes,它們在混淆過程中會被保留。
Proguard的幾個步驟如何處理Entry Points。
  (1).在壓縮階段,Proguard從上述Entry Points開始遍歷搜索哪些類和類成員被使用。其他沒有被使用的類和類成員會移除。
  (2).在優化階段,Proguard進一步設置非Entry Point的類和方法爲private、static和final來進行優化,不使用的參數會被移除,某些方法會被標記爲內聯。
  (3).在混淆階段,Proguard重命名非Entry Points的類和類成員。
  (4).預檢階段是唯一沒有觸及Entry Points的階段。

三、Android Studio默認的混淆方案及字段解讀

1.開啓混淆    

在build.gradle文件內相應的構建類型中添加minifyEnabled true即可。
除了minifyEnable屬性外,還有用於定義ProGuard規則的proguardFiles屬性:

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

官方文檔介紹:
  getDefaultProguardFile('proguard-android.txt')方法可從Android SDK tools/proguard/文件夾獲取默認的ProGuard設置。要想做進一步的代碼壓縮,請嘗試使用位於同一位置的proguard-android-optimize.txt文件。它包括相同的Proguard規則,但還包括其他在字節碼一級(方法內和方法間)執行分析的優化,以進一步減少APK大小和幫助提高其運行速度。
proguard-rules.pro文件用於添加自定義Proguard規則。默認情況下,該文件位於模塊根目錄(build.gradle文件旁),內容爲空。
在gradle 2.2之後,defaultProguardFile沒有使用sdk目錄下的proguard-android.txt,而是使用了gradle自帶的proguard-android.txt,不同的gradle版本帶有不同的默認混淆文件,在項目根目錄的build/intermediates/proguard-files/proguard-android.txt-2.3.1。
混淆配置文件不檢查規則是否重複,如果兩條規則衝突,則採用白名單的,比如設置了開啓優化和不優化兩個選項後,不論順序,最終都會執行不優化的操作。

2.構建輸出

構建時Proguard都會輸出下列文件:(build之後)
  (1)dump.txt --- 說明APK中所有類文件的內部結構
  (2)mapping.txt --- 提供原始與混淆過的類、方法和字段名稱之間的轉換
  (3)seeds --- 列出未進行混淆的類和成員
  (4)usage.txt --- 列出從APK移除的代碼
這些文件保存在/build/outputs/mapping/release目錄下。

3.解碼混淆過的堆棧追蹤

使用混淆後,保存好mapping文件,程序csh時通過腳本進行解碼。
retrace工具位於/tools/proguard/目錄下,解碼命令爲:

retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]

4.默認的混淆方案及字段解讀

(1)不適用大小混寫類名

-dontusemixedcaseclassnames

默認情況下混淆的類名可以包含大小寫字符的混合
(2)不忽略公共類庫

-dontskipnonpubliclibraryclasses

指定不去忽略非public的library classes。從Proguard 4.5開始,是默認的設置。
(3)不優化指定的文件與不預檢驗

-dontoptimize
-dontpreverify

默認optimize和preverify選項是關閉的,因爲Android的dex並不想Java虛擬機需要optimize(優化)和previrify(預檢)兩個步驟。
(4)指定哪個屬性不要混淆,可一次指定多個屬性

-keeppattributes [attribute_filter]

通常Exceptions,Signature,Deprecated,SourceFile,SourceDir,LineNumberTable,LocalVariableTable,LocalVariableTypeTable,Synthetic,EnclosingMethod,RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations,AnnotationDefault屬性需要被保留,根據項目具體使用情況保留。

gradle默認的keepattributes屬性不全,只保留了Annotations,Signature,InnerClasses,EnclosingMethod,爲了混淆之後定位csh代碼方便,需要在proguard_rules.pro中手動添加拋出異常時保留代碼行號,並且重命名超出異常時的文件名稱,這樣能方便定位問題:
#拋出異常時保留代碼行號

 -keeppattributes SourceFile,LineNumberTable

#重命名拋出異常時的文件名稱

  -renamesourcefileattribute SourceFile

keep選項制定了哪些類,哪些方法不被混淆,從而保證了程序的正常運行。
keep用法有6種:
  (1)-keep(names)選項 指定類和類成員(變量和方法)不被混淆
    -keep [,modifier,...] class_specification
    //指定類名不被改變

      -keep public class com.google.vending.licensing.ILicensingService

    //指定使用了Keep註解的類和類成員都不被改變

      -keep @android.support.annotation.Keep class * {*;}

  (2)-keepclassmembers(names) 指定類成員不被混淆,類名會被混淆

      //keep setters in views 使得animations仍然能夠工作

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

  (3)-keepclasseswithmembers(names) 指定類和類成員都不被混淆
    -keepclasseswithmembers [,modifier,...] class_specification
    //包含native方法的類名和native方法都不能被混淆,如果native方法未被調用,則被移除。由於native方法與對應so庫中的方法名稱對應,方法名被混淆會導致調用出現問題,所以native方法不能被混淆。

-keepclasseswithmembernames class * {
         native <methods>;
}

不帶names的選項爲From being removed or renames,既不會被移除或重命名,即使類或類成員未被使用。帶有names的選項爲From being renamed,不會被重命名,如果是無用的類或類成員,會被移除,移除是指在壓縮(Shrinking)時是否會被刪除。 
通用Options:
  (1)-verbose 打印混淆詳細信息
  (2)-dontnote選項:指定不去輸出打印該類產生的錯誤或遺漏
    -dontnote com.android.vending.licensing.ILicensingService
    -dontnote android.support.**
  (3)-dontwarn選項:指定不去warn unresolved references和其他重要的problem
    -dontwarn android.support.**
如上面(2)(3)所示,android.support的libraries需要保留。

四、自定義混淆文件

1.Filters

?      匹配一個字符
*       匹配一個名字,除了目錄外分隔符外的任意部分
**      匹配任意名,可能包含任意路徑分隔符
!     排除
<field>        匹配類中的所有字段
<method>   匹配類中所有的方法
<init>          匹配類中所有的構造函數

-keep class com.lily.test.**          本包和所包含子包下的類名都保持
-keep class com.lily.test.*           保持該包下的類名
-keep class com.lily.test.** {*;}    保持包和子包的類名和裏面的內容均不被混淆

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

2.-assumenosideeffects指令:

assumeosideeffects是Optimization過程中的選項,所以爲保證指令的有效,需要開啓optimization。這個指令的含義是Proguard會在optimization過程中刪除對這些方法的調用,需要注意:當你知道你在做什麼的時候才能使用它。

3.一個自定義文件

複製代碼

#代碼混淆壓縮比,在0~7之間
-optimizationpasses 5# 
#混淆時不適用大小寫混合,混合後的類名爲小寫
-dontusemixedcaseclassnames

#指定不去忽略非公共庫的類
-dontskipnonpubliclibraryclasses

#不做預校驗,preverify是proguard的四個步驟之一,Android不需要precerify,去掉這一步能夠加快混淆速度。
-dontpreverify

-verbose

#google推薦算法
-optimizations !code/simplification/arithmetic,!code/simplication/cast,!field/*,!class/mergin/*

#避免混淆Annotation、內部類、泛型、匿名類
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod

#重命名拋出異常時的文件名稱
-renamesourcefileattribute SourceFile

#拋出異常時保留代碼行號
-keepattributes SourceFile,LineNumberTable

#處理support包
-dontnote android.support.**
-dontwarn android.support.**

#保留四大組件,自定義的Application等這些類不被混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * entends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService

#保留本地native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>}

#保留枚舉類不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}

#第三方jar包不被混淆
-keep class com.github.test.** {*;}

#保留自定義的Test類和類成員不被混淆
-keep class class.lily.Test {*;}

#保留自定義的xlog文件夾下面的類、類成員和方法不被混淆
-keep class com.text.xlog.** {<fields>;<methods>;
}

#assume no side effects;刪除android.util.Log輸出的日誌
-assumenosideeffects class android.util.Log {
public static *** v(...);
public static *** d(...);
public static *** i(...);
public static *** w(...);
public static *** e(...);
}

#保留keep註解的類名和方法
-keep,allowobfuscation @interface android.support.annotation.Keep
-keep @android.support.annotation.Keep class *
-keepclassmember class * {
@android.support.annotation.Keep *;
}

複製代碼

五、常用到的不混淆

1.jni方法不混淆

jni方法不混淆,因爲方法需要和native方法保持一致。

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

2.反射不混淆

反射用到的類不混淆(否則混淆可能出現問題)。

-keepatrributes EnclosingMethod

3.AndroidMainfest中的類不混淆

AndroidMainfest中的類不混淆,所以四大組件和Application的子類和Framework層下所有的類默認不會進行混淆。自定義的View默認也不會被混下,所以排除自定義View,或者四大組件被混淆的規則在ndroid Studio中無需加入的,下面是兼容性比較高的規則:

複製代碼

-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content。BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * entends android.app.backup.BackupAgentHelper
-keep public class * entends android.preference.Preference

複製代碼

4.JSON對象類不混淆

與服務器交互時,使用GSON、fastjson等框架解析服務端數據時,所寫的JSON對象類不混淆,否則無法將JSON解析成對應的對象。

5.第三方開源或SDK包

使用第三方開源庫或者引用其他第三方的SDK包時,如果有特別要求,也需要在混淆文件中加入對應的混淆規則。

6.WebView的JS調用的接口方法不混淆

有用到WebView的JS調用也需要保證寫的接口方法不混淆,原則和第一條一樣。

-keepclassmembers classs fqcn.of.javascript.interface.for.webview {
    public *;
}

7.Parcelable的子類和Creator靜態成員變量不混淆

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

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

8.enum類型

使用enum類型時需要注意避免以下兩個方法混淆,因爲enum類的特殊性,以下兩個方法會被反射調用。

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

9.註解不混淆

-keepatrributes *Annotation*
-keep class * extends java.lang.annotation.Annotation {*;}

10.泛型不混淆

-keepattributes Signature

11.內部類不混淆

-keepattributes InnerClasses

六、第三方混淆參考規則

1.Gson

-dontwarn com.google.**
-keep class com.google.gson.** {*;}

2.otto

-keepattributes *Annotation*
-keepclassmembers class ** {
    @com.squareup.Subscribe public *;
    @com.squareup.otto.Produce public *;
}

3.universal-p_w_picpath-loader

-dontwarn com.nostra13.universalp_w_picpathloader.**
-keep class com.nostra13.universalp_w_picpathloader.** {*;}

4.友盟統計

複製代碼

-keepclassmembers class * {
    public <init> (org.json.JSONObject);
}
#友盟統計5.0.0以上SDK需要
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
#友盟統計R.java刪除問題
-keep public class com.gdhbgh.activity.R$*{
    public static final int *;
}

複製代碼

5.OkHttp

-dontwarn com.squareup.okhttp.**
-keep class com.squareup.okhttp.** {*;}
-keep interface com.squareup.okhttp.** {*;}
-dontwarn okio.**

6.nineoldandroids

-dontwarn com.nineoldandroids.*;
-keep class com.nineoldandroids.** {*;}

7.支付寶

複製代碼

-keep class com.alipay.android.app.IAlixPay{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;}
-keep class com.alipay.sdk.app.PayTask{
    public *;
}
-keep class com.alipay.sdk.app.AuthTask{
    public *;
}

複製代碼

8.Socket.io

-keep class socket.io-client.
-keepclasswithmembers,allowshrinking class socket.io-client.* {*;}
-keep class io.socket.
-keepclasseswithmembers,allowshrinking class io.socket.* {*;}

9.JPUSH

-dontwarn cn.jpush.**
-keep class cn.jpush.** {*;}

# protobuf(jpush依賴)
-dontwarn com.google.**
-keep class com.google.protobuf.** {*;}

10.友盟分享

複製代碼

-dontwarn com.umeng.**
-dontwarn com.tencent.weibo.sdk.**

-keep public interface com.tencent.**
-keep public interface com.umeng.socialize.**
-keep public interface com.umeng.socialize.sensor.**
-keep public interface com.umeng.scrshot.**

-keep public class com.umeng.socialize.* {*;}

-keep class com.umeng.scrshot.**
-keep public class com.tencent.** {*;}
-keep class com.umeng.socialize.sensor.**
-keep class com.umeng.socialize.handler.**
-keep class com.umeng.socialize.handler.*
-keep class com.tencent.mm.sdk.modelmsg.WXMediaMessage {*;}
-keep class com.tencent.mm.sdk.modelmsg.** implements com.tencent.mm.sdk.modelmsg.WXMediaMessage$IMediaObject {*;}

-keep class im.yixin.sdk.api.YXMessage {*;}
-keep class im.yixin.sdk.api.** implements im.yixin.sdk.api.YXMessage$YXMessageData{*;}

-keep class com.tencent.** {*;}
-dontwarn com.tencent.**
-keep public class com.umeng.soexample.R$*{
public static final int *;
}
-keep class com.tencent.open.TDialog$*
-keep class com.tencent.open.TDialog$* {*;}
-keep class com.tencent.open.PKDialog
-keep class com.tencent.open.PKDialog {*;}
-keep class com.tencent.open.PKDialog$*
-keep class com.tencent.open.PKDialog$* {*;}

-keep class com.sina.** {*;}
-dontwarn com.sina.**
-keep class com.alipay.share.sdk.** {*;}

複製代碼

七、常見的一些問題

1.網絡層混淆

一般網絡層都不進行混淆,可以經過劃分包後直接不混淆網絡層的包:

-keep class com.xxx.xxx.http.** {*;}

2.數據模型混淆

-keep class * implements java.io.Serializable {*;}
-keepclassmembers class * implements java.io.Serializable {*;}

有時候上面的這種方式可能會導致應用卡住,沒有任何錯誤提示,所以建議採用分包模式,吧所有bean放在同一個包中,直接對該包加白名單。

-keep class com.xxx.xxx.domain.xx {*;}

3.XML映射混淆

如果遇到一些空間無法Inflate,報NullPointException,比如ListView,NavigationView等等

-keep class *.** {*;}

4.混淆規則編寫方法

如果混淆後報錯,通過retrace後找到錯誤的問題後可以直接編寫規則來去掉混淆,但是如果報的錯誤莫名其妙,而且報錯的類沒有混淆,那麼可以採用極端的方法,加入下面的規則:

-keep class *.** {*;}

這條規則表示不混淆所有類及其中所有代碼,加了這條規則之後,還不能運行表示是其他問題,例如註解,內部類等等,可以運行後,可以通過反編譯,尋找所有包名,記錄下來,吧上述規則改爲:

-keep class android.** {*;}
-keep class com.** {*;}
-keep class org.** {*;}

一個個去掉檢查是否有報錯,例如查到

-keep class com.** {*;}

加了就沒有錯誤,則可以繼續一級級往下檢查。
但要注意,有時候可能是幾個包混合問題。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章