Android 代碼混淆 混淆方案

本篇文章:本身在混淆的時候整理出比較全面的混淆方法,比較實用,本身走過的坑,淌出來的路。請你們不要再走回頭路,可能只要咱們代碼加混淆,一點不對就會致使項目運行崩潰等後果,有許多人發現沒有打包運行好好地,打包完成之後而又不不能夠了,致使了許多困惑,本片文章來問你們解決困惑,但願對你們有幫助。html

Android混淆最佳實踐

1. 混淆配置

android{

buildTypes {
        release {
            buildConfigField "boolean", "LOG_DEBUG", "false" //不顯示log
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.config
            }
        }
}

由於開啓混淆會使編譯時間變長,因此debug模式下不開啓。咱們須要作的是:
1.將release下minifyEnabled的值改成true,打開混淆;
2.加上shrinkResources true,打開資源壓縮。
3.buildConfigField 不顯示log日誌
4.signingConfig signingConfigs.config 配置簽名文件文件java

自定義混淆規則

自定義混淆方案適用於大部分的項目android

#指定壓縮級別
-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.**

真正通用的、須要添加的就是上面這些,除此以外,須要每一個項目根據自身的需求添加一些混淆規則:算法

第三方庫所需的混淆規則。正規的第三方庫通常都會在接入文檔中寫好所需混淆規則,使用時注意添加。json

在運行時動態改變的代碼,例如反射。比較典型的例子就是會與 json 相互轉換的實體類。假如項目命名規範要求實體類都要放在model包下的話,能夠添加相似這樣的代碼把全部實體類都保持住:-keep public class **.*Model*.** {*;}
JNI中調用的類。
WebView中JavaScript調用的方法
Layout佈局使用的View構造函數、android:onClick等。

檢查混淆結果

混淆過的包必須進行檢查,避免因混淆引入的bug。
一方面,須要從代碼層面檢查。使用上文的配置進行混淆打包後在 <module-name>/build/outputs/mapping/release/ 目錄下會輸出如下文件:
dump.txt
描述APK文件中全部類的內部結構
mapping.txt
提供混淆先後類、方法、類成員等的對照表
seeds.txt
列出沒有被混淆的類和成員
usage.txt
列出被移除的代碼
咱們能夠根據 seeds.txt 文件檢查未被混淆的類和成員中是否已包含全部指望保留的,再根據 usage.txt 文件查看是否有被誤移除的代碼。
另外一方面,須要從測試方面檢查。將混淆過的包進行全方面測試,檢查是否有 bug 產生。

解出混淆棧

混淆後的類、方法名等等難以閱讀,這當然會增長逆向工程的難度,但對追蹤線上 crash 也形成了阻礙。咱們拿到 crash 的堆棧信息後會發現很難定位,這時須要將混淆反解。
<sdk-root>/tools/proguard/ 路徑下有附帶的的反解工具(Window 系統爲 proguardgui.bat,Mac 或 Linux 系統爲 proguardgui.sh)。
這裏以 Window 平臺爲例。雙擊運行 proguardgui.bat 後,能夠看到左側的一行菜單。點擊 ReTrace,選擇該混淆包對應的 mapping 文件(混淆後在 <module-name>/build/outputs/mapping/release/ 路徑下會生成 mapping.txt 文件,它的做用是提供混淆先後類、方法、類成員等的對照表),再將 crash 的 stack trace 黏貼進輸入框中,點擊右下角的 ReTrace ,混淆後的堆棧信息就顯示出來了。
以上使用 GUI 程序進行操做,另外一種方式是利用該路徑下的 retrace 工具經過命令行進行反解,命令是
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]

例如:api

retrace.bat -verbose mapping.txt obfuscated_trace.txt

注意事項:

全部在 AndroidManifest.xml 涉及到的類已經自動被保持,所以不用特地去添加這塊混淆規則。(不少老的混淆文件裏會加,如今已經不必)
proguard-android.txt 已經存在一些默認混淆規則,不必在 proguard-rules.pro 重複添加

混淆簡介

Android中的「混淆」能夠分爲兩部分,一部分是 Java 代碼的優化與混淆,依靠 proguard 混淆器來實現;另外一部分是資源壓縮,將移除項目及依賴的庫中未被使用的資源(資源壓縮嚴格意義上跟混淆沒啥關係,但通常咱們都會放一塊兒用)。安全

代碼壓縮

代碼壓縮流程
代碼混淆是包含了代碼壓縮、優化、混淆等一系列行爲的過程。如上圖所示,混淆過程會有以下幾個功能:app

壓縮。移除無效的類、類成員、方法、屬性等;
優化。分析和優化方法的二進制代碼;根據proguard-android-optimize.txt中的描述,優化可能會形成一些潛在風險,不能保證在全部版本的Dalvik上都正常運行。
混淆。把類名、屬性名、方法名替換爲簡短且無心義的名稱;
預校驗。添加預校驗信息。這個預校驗是做用在Java平臺上的,Android平臺上不須要這項功能,去掉以後還能夠加快混淆速度。
這四個流程默認開啓。ide

Android 項目中咱們能夠選擇將「優化」和「預校驗」關閉,對應命令是-dontoptimize、-dontpreverify(固然,默認的 proguard-android.txt 文件已包含這兩條混淆命令,不須要開發者額外配置)。函數

資源壓縮

資源壓縮將移除項目及依賴的庫中未被使用的資源,這在減小 apk 包體積上會有不錯的效果,通常建議開啓。具體作法是在 build.grade 文件中,將 shrinkResources 屬性設置爲 true。須要注意的是,只有在用minifyEnabled true開啓了代碼壓縮後,資源壓縮纔會生效。

資源壓縮包含了「合併資源」和「移除資源」兩個流程。

「合併資源」流程中,名稱相同的資源被視爲重複資源會被合併。須要注意的是,這一流程不受shrinkResources屬性控制,也沒法被禁止, gradle 必然會作這項工做,由於假如不一樣項目中存在相同名稱的資源將致使錯誤。gradle 在四處地方尋找重複資源:

src/main/res/ 路徑
不一樣的構建類型(debugrelease等等)
不一樣的構建渠道
項目依賴的第三方庫
合併資源時按照以下優先級順序

依賴 -> main -> 渠道 -> 構建類型

假如重複資源同時存在於main文件夾和不一樣渠道中,gradle 會選擇保留渠道中的資源。

同時,若是重複資源在同一層次出現,好比src/main/res/ 和 src/main/res2/,則 gradle 沒法完成資源合併,這時會報資源合併錯誤。

「移除資源」流程則見名知意,須要注意的是,相似代碼,混淆資源移除也能夠定義哪些資源須要被保留,這點在下文給出。

自定義混淆規則

在上文「混淆配置」中有這樣一行代碼

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

這行代碼定義了混淆規則由兩部分構成:位於 SDK 的 tools/proguard/ 文件夾中的 proguard-android.txt 的內容以及默認放置於模塊根目錄的 proguard-rules.pro 的內容。前者是 SDK 提供的默認混淆文件,後者是開發者自定義混淆規則的地方。

常見的混淆指令

  • optimizationpasses
  • dontoptimize
  • dontusemixedcaseclassnames
  • dontskipnonpubliclibraryclasses
  • dontpreverify
  • dontwarn
  • verbose
  • optimizations
  • keep
  • keepnames
  • keepclassmembers
  • keepclassmembernames
  • keepclasseswithmembers
  • keepclasseswithmembernames

更多詳細的請到官網

須要特別介紹的是與保持相關元素不參與混淆的規則相關的幾種命令:

命令 做用
-keep 防止類和成員被移除或者被重命名
-keepnames 防止類和成員被重命名
-keepclassmembers 防止成員被移除或者被重命名
-keepnames 防止成員被重命名
-keepclasseswithmembers 防止擁有該成員的類和成員被移除或者被重命名
-keepclasseswithmembernames 防止擁有該成員的類和成員被重命名

保持元素不參與混淆的規則

[保持命令] [類] {
    [成員] 
}
「類」表明類相關的限定條件,它將最終定位到某些符合該限定條件的類。它的內容可使用:
  • 具體的類
  • 訪問修飾符(public、protected、private)
  • 通配符*,匹配任意長度字符,但不含包名分隔符(.)
  • 通配符**,匹配任意長度字符,而且包含包名分隔符(.)
  • extends,便可以指定類的基類
  • implement,匹配實現了某接口的類
  • $,內部類
「成員」表明類成員相關的限定條件,它將最終定位到某些符合該限定條件的類成員。它的內容可使用:
  • <init> 匹配全部構造器
  • <fields> 匹配全部域
  • <methods> 匹配全部方法
  • 通配符*,匹配任意長度字符,但不含包名分隔符(.)
  • 通配符**,匹配任意長度字符,而且包含包名分隔符(.)
  • 通配符*,匹配任意參數類型
  • …,匹配任意長度的任意類型參數。好比void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b) 這些方法。
  • 訪問修飾符(public、protected、private)

舉個例子,假如須要將com.biaobiao.test包下全部繼承Activity的public類及其構造函數都保持住,能夠這樣寫:

-keep public class com.biaobiao.test.** extends Android.app.Activity {
    <init>
}

經常使用自定義混淆規則

  • 不混淆某個類
-keep public class com.biaobiao.example.Test { *; }

不混淆某個包全部的類

-keep class com.biaobiao.test.** { *; }
}

不混淆某個類的子類

-keep public class * extends com.biaobiao.example.Test { *; }

不混淆全部類名中包含了「model」的類及其成員

-keep public class * extends com.biaobiao.example.Test { *; }

不混淆某個接口的實現

-keep class * implements com.biaobiao.example.TestInterface { *; }

不混淆某個類的構造方法

-keepclassmembers class com.biaobiao.example.Test { 
    public <init>(); 
}

不混淆某個類的特定的方法

-keepclassmembers class com.biaobiao.example.Test { 
    public void test(java.lang.String); 
}
}

不混淆某個類的內部類

-keep class com.biaobiao.example.Test$* {
        *;
 }

自定義資源保持規則

1. keep.xml

shrinkResources true開啓資源壓縮後,全部未被使用的資源默認被移除。假如你須要定義哪些資源必須被保留,在 res/raw/ 路徑下建立一個 xml 文件,例如keep.xml

經過一些屬性的設置能夠實現定義資源保持的需求,可配置的屬性有:

  • keep 定義哪些資源須要被保留(資源之間用「,」隔開)
  • discard 定義哪些資源須要被移除(資源之間用「,」隔開)
  • shrinkMode 開啓嚴格模式
  • 當代碼中經過 Resources.getIdentifier() 用動態的字符串來獲取並使用資源時,普通的資源引用檢查就可能會有問題。例如,以下代碼會致使全部以「img_」開頭的資源都被標記爲已使用。

當代碼中經過 Resources.getIdentifier() 用動態的字符串來獲取並使用資源時,普通的資源引用檢查就可能會有問題。例如,以下代碼會致使全部以「img_」開頭的資源都被標記爲已使用。

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());

咱們能夠設置 tools:shrinkMode strict 來開啓嚴格模式,使只有確實被使用的資源被保留。

以上就是自定義資源保持規則相關的配置,舉個例子:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/unused2"
    tools:shrinkMode="strict"/>

移除替代資源

一些替代資源,例如多語言支持的 strings.xml,多分辨率支持的 layout.xml 等,在咱們不須要使用又不想刪除掉時,可使用資源壓縮將它們移除。

咱們使用 resConfig 屬性來指定須要支持的屬性,例如
一些替代資源,例如多語言支持的 strings.xml,多分辨率支持的 layout.xml 等,在咱們不須要使用又不想刪除掉時,可使用資源壓縮將它們移除。

咱們使用 resConfig 屬性來指定須要支持的屬性,例如

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}

其餘未顯式聲明的語言資源將被移除。

最後附上一個我在實際項目中的混淆方案

proguard-android.txt 文件內容

# 代碼混淆壓縮比,在0~7之間
-optimizationpasses 5
# 混合時不使用大小寫混合,混合後的類名爲小寫
-dontusemixedcaseclassnames
# 指定不去忽略非公共庫的類
-dontskipnonpubliclibraryclasses
# 不作預校驗,preverify是proguard的四個步驟之一,Android不須要preverify,去掉這一步可以加快混淆速度。
-dontpreverify
-verbose
# 避免混淆泛型
-keepattributes Signature

# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
#google推薦算法
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# 避免混淆Annotation、內部類、泛型、匿名類
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
# 重命名拋出異常時的文件名稱
-renamesourcefileattribute SourceFile
# 拋出異常時保留代碼行號
-keepattributes SourceFile,LineNumberTable
# 處理support包
-dontnote android.support.**
-dontwarn android.support.**
# 保留繼承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**

# 保留R下面的資源
-keep class **.R$* {*;}
# 保留四大組件,自定義的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.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService

# 保留在Activity中的方法參數是view的方法,
# 這樣以來咱們在layout中寫的onClick就不會被影響
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}
# 對於帶有回調函數的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
    void *(**On*Event);
    void *(**On*Listener);
}
# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

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

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

-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();
}
#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 *
-keepclassmembers class * {
    @android.support.annotation.Keep *;
}
#3D 地圖 V5.0.0以前:

-dontwarn com.amap.api.**
-dontwarn com.autonavi.**
-keep class com.amap.api.**{*;}
-keep class com.autonavi.**{*;}

-keep   class com.amap.api.maps.**{*;}
-keep   class com.autonavi.amap.mapcore.*{*;}
-keep   class com.amap.api.trace.**{*;}

#3D 地圖 V5.0.0以後:
-keep   class com.amap.api.maps.**{*;}
-keep   class com.autonavi.**{*;}
-keep   class com.amap.api.trace.**{*;}

#定位
-keep class com.amap.api.location.**{*;}
-keep class com.amap.api.fence.**{*;}
-keep class com.autonavi.aps.amapapi.model.**{*;}

#搜索
-keep   class com.amap.api.services.**{*;}

#2D地圖
-keep class com.amap.api.maps2d.**{*;}
-keep class com.amap.api.mapcore2d.**{*;}

#導航
-keep class com.amap.api.navi.**{*;}
-keep class com.autonavi.**{*;}
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature

# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
    @retrofit2.http.* <methods>;
}

# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement

# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**

# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**

# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase

# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*

# OkHttp platform used only on JVM and when Conscrypt dependency is available.
-dontwarn okhttp3.internal.platform.ConscryptPlatform

#fastjson混淆
-keepattributes Signature
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.**{*;}
-keep class com.alibaba.fastjson.**{*; }
-keep public class com.ninstarscf.ld.model.entity.**{*;}

全部文章參考

相關文章
相關標籤/搜索