寫給 Android 開發人員的混淆使用手冊

來源:互聯網
上載者:User

標籤:bat   介紹   javascrip   刪除   ntp   res   ted   重要   路徑   

毫無疑問,混淆是打包過程中最重要的流程之一,在沒有特殊原因的情況下,所有 app 都應該開啟混淆。

首先,這裡說的的混淆其實是包括了代碼壓縮、代碼混淆以及資源壓縮等的最佳化過程。依靠 ProGuard,混淆流程將主專案以及依賴庫中未被使用的類、類成員、方法、屬性移除,這有助於規避64K方法數的瓶頸;同時,將類、類成員、方法重新命名為無意義的簡簡短名稱,增加了逆向工程的難度。而依靠 Gradle 的 Android 外掛程式,我們將移除未被使用的資源,可以有效減小 apk 安裝包大小。

本文由兩部分構成,第一部分給出混淆的最佳實務,力求讓零基礎的新手都可以直接使用混淆;第二部分會介紹一下混淆的整體、自訂混淆規則的文法與實踐、自訂資源保持的規則等。

一、Android混淆最佳實務

  1. 混淆配置

一般情況下,app module 的 build.gradle 檔案預設會有如下結構:

android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile(‘proguard-android.txt‘), ‘proguard-rules.pro‘
}
}
}
因為開啟混淆會使編譯時間變長,所以debug 模式下不應該開啟。我們需要做的是:

將release 下minifyEnabled 的值改為true ,開啟混淆;

加上shrinkResources true,開啟資源壓縮。

修改後檔案內容如下:

android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile(‘proguard-android.txt‘), ‘proguard-rules.pro‘
}
}
}

  1. 自訂混淆規則

在 app module 下預設產生了項目的自訂混淆規則檔案 proguard-rules.pro ,多方調研後,一份適用於大部分項目的混淆規則最佳實務如下:

#指定壓縮層級
-optimizationpasses 5

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

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

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

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

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

#保持所有實現 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 相互轉換的實體類。假如項目命名規範要求實體類都要放在model 包下的話,可以添加類似這樣的代碼把所有實體類都保持住:-keep public class .Model. {*;}

JNI 中調用的類。

WebView 中JavaScript 調用的方法

Layout 布局使用的View 建構函式、android:onClick 等。

  1. 檢查混淆結果

混淆過的包必須進行檢查,避免因混淆引入的bug。

一方面,需要從代碼層面檢查。使用上文的配置進行混淆打包後在 <module-name>/build/outputs/mapping/release/ 目錄下會輸出以下檔案:

dump.txt
描述APK檔案中所有類的內部結構

mapping.txt
提供混淆前後類、方法、類成員等的對照表

seeds.txt
列出沒有被混淆的類和成員

usage.txt
列出被移除的代碼

我們可以根據 seeds.txt 檔案檢查未被混淆的類和成員中是否已包含所有期望保留的,再根據 usage.txt 檔案查看是否有被誤移除的代碼。

另一方面,需要從測試方面檢查。將混淆過的包進行全方面測試,檢查是否有 bug 產生。

  1. 解出混淆棧

混淆後的類、方法名等等難以閱讀,這固然會增加逆向工程的難度,但對追蹤線上 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>]
例如:

retrace.bat -verbose mapping.txt obfuscated_trace.txt
注意事項:

1) 所有在 AndroidManifest.xml 涉及到的類已經自動被保持,因此不用特意去添加這塊混淆規則。(很多老的混淆檔案裡會加,現在已經沒必要)

2) proguard-android.txt 已經存在一些預設混淆規則,沒必要在 proguard-rules.pro 重複添加,該檔案具體規則見附錄1:

二、混淆簡介
Android中的“混淆”可以分為兩部分,一部分是 Java 代碼的最佳化與混淆,依靠 proguard 混淆器來實現;另一部分是資源壓縮,將移除項目及依賴的庫中未被使用的資源(資源壓縮嚴格意義上跟混淆沒啥關係,但一般我們都會放一起講)。

  1. 代碼壓縮

代碼混淆是包含了代碼壓縮、最佳化、混淆等一系列行為的過程。如所示,混淆過程會有如下幾個功能:

壓縮。移除無效的類、類成員、方法、屬性等;

最佳化。分析和最佳化方法的二進位代碼;根據proguard-android-optimize.txt中的描述,最佳化可能會造成一些潛在風險,不能保證在所有版本的Dalvik上都正常運行。

混淆。把類名、屬性名稱、方法名替換為簡短且無意義的名稱;

預校正。添加預校正資訊。這個預校正是作用在Java平台上的,Android平台上不需要這項功能,去掉之後還可以加快混淆速度。

這四個流程預設開啟。

在 Android 項目中我們可以選擇將“最佳化”和“預校正”關閉,對應命令是-dontoptimize 、-dontpreverify (當然,預設的 proguard-android.txt 檔案已包含這兩條混淆命令,不需要開發人員額外配置)。

  1. 資源壓縮

資源壓縮將移除項目及依賴的庫中未被使用的資源,這在減少 apk 包體積上會有不錯的效果,一般建議開啟。具體做法是在 build.grade 檔案中,將 shrinkResources 屬性設定為 true 。需要注意的是,只有在用minifyEnabled true開啟了代碼壓縮後,資源壓縮才會生效。

資源壓縮包含了“合并資源”和“移除資源”兩個流程。

“合并資源”流程中,名稱相同的資源被視為重複資源會被合并。需要注意的是,這一流程不受shrinkResources屬性控制,也無法被禁止, gradle 必然會做這項工作,因為假如不同項目中存在相同名稱的資源將導致錯誤。gradle 在四處地方尋找重複資源:

src/main/res/ 路徑

不同的構建類型(debug、release等等)

不同的構建渠道

項目依賴的第三方庫

合并資源時按照如下優先順序順序:

依賴 -> main -> 渠道 -> 構建類型
舉個例子,假如重複資源同時存在於main 檔案夾和不同渠道中,gradle 會選擇保留渠道中的資源。

同時,如果重複資源在同一層次出現,比如src/main/res/ 和 src/main/res/ ,則 gradle 無法完成資源合并,這時會報資源合并錯誤。

“移除資源”流程則見名知意,需要注意的是,類似代碼,混淆資源移除也可以定義哪些資源需要被保留,這點在下文給出。

三、自訂混淆規則
在上文“混淆配置”中有這樣一行代碼

proguardFiles getDefaultProguardFile(‘proguard-android.txt‘), ‘proguard-rules.pro‘
這行代碼定義了混淆規則由兩部分構成:位於 SDK 的 tools/proguard/ 檔案夾中的 proguard-android.txt 的內容以及預設放置於模組根目錄的 proguard-rules.pro 的內容。前者是 SDK 提供的預設混淆檔案(內容見附錄1),後者是開發人員自訂混淆規則的地方。

  1. 常見混淆命令:

optimizationpasses

dontoptimize

dontusemixedcaseclassnames

dontskipnonpubliclibraryclasses

dontpreverify

dontwarn

verbose

optimizations

keep

keepnames

keepclassmembers

keepclassmembernames

keepclasseswithmembers

keepclasseswithmembernames

在第一部分 Android 混淆最佳實務中已介紹部分需要使用到的混淆命令,這裡不再贅述,詳情請查閱官網。需要特別介紹的是與保持相關元素不參與混淆的規則相關的幾種命令:

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

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

形如:

[保持命令] [類] {
[成員]
}
“類”代表類相關的限定條件,它將最終定位到某些符合該限定條件的類。它的內容可以使用:

具體的類

存取修飾詞(public 、protected 、private )

萬用字元* ,匹配任意長度字元,但不含包名分隔字元(.)

萬用字元** ,匹配任意長度字元,並且包含包名分隔字元(.)

extends ,即可以指定類的基類

implement ,匹配實現了某介面的類

$,內部類

“成員”代表類成員相關的限定條件,它將最終定位到某些符合該限定條件的類成員。它的內容可以使用:

匹配所有構造器
匹配所有域
匹配所有方法
萬用字元* ,匹配任意長度字元,但不含包名分隔字元(.)

萬用字元** ,匹配任意長度字元,並且包含包名分隔字元(.)

萬用字元*** ,匹配任意參數類型

… ,匹配任意長度的任意型別參數。比如void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b) 這些方法。

存取修飾詞(public 、protected 、private )

舉個例子,假如需要將name.huihui.test 包下所有繼承Activity 的public 類及其建構函式都保持住,可以這樣寫:

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

  1. 常用的自訂混淆規則

不混淆某個類

-keep public class name.huihui.example.Test { *; }
不混淆某個包所有的類

-keep class name.huihui.test.* { ; }
不混淆某個類的子類

-keep public class extends name.huihui.example.Test { ; }
不混淆所有類名中包含了“model”的類及其成員

-keep public class .model. {*;}
不混淆某個介面的實現

-keep class implements name.huihui.example.TestInterface { ; }
不混淆某個類的構造方法

-keepclassmembers class name.huihui.example.Test {
public <init>();
}
不混淆某個類的特定的方法

-keepclassmembers class name.huihui.example.Test {
public void test(java.lang.String);
}
四、自訂資源保持規則

  1. keep.xml

用shrinkResources true開啟資源壓縮後,所有未被使用的資源預設被移除。假如你需要定義哪些資源必須被保留,在 res/raw/ 路徑下建立一個 xml 檔案,例如 keep.xml 。

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

tools:keep 定義哪些資源需要被保留(資源之間用“,”隔開)

tools:discard 定義哪些資源需要被移除(資源之間用“,”隔開)

tools:shrinkMode 開啟strict 模式

當代碼中通過 Resources.getIdentifier() 用動態字串來擷取並使用資源時,普通的資源引用檢查就可能會有問題。例如,如下代碼會導致所有以“img_”開頭的資源都被標記為已使用。

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());
我們可以設定 tools:shrinkMode 為 strict 來開啟strict 模式,使只有確實被使用的資源被保留。

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

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

  1. 移除替代資源

一些替代資源,例如多語言支援的 strings.xml ,多解析度支援的 layout.xml 等,在我們不需要使用又不想刪除掉時,可以使用資源壓縮將它們移除。

我們使用 resConfig 屬性來指定需要支援的屬性,例如

android {
defaultConfig {
...
resConfigs "en", "fr"
}
}
其他未顯式聲明的語言資源將被移除。

參考資料
Shrink Your Code and Resources

proguard

Android安全***戰,反編譯與混淆技術完全解析(下)

Android混淆從入門到精通

Android代碼混淆之ProGuard

附錄
proguard-android.txt 檔案內容

#包名不混合大小寫
-dontusemixedcaseclassnames

#不跳過非公用的庫的類
-dontskipnonpubliclibraryclasses

#混淆時記錄日誌
-verbose

#關閉預校正
-dontpreverify

#不最佳化輸入的類檔案
-dontoptimize

#保護註解
-keepattributes Annotation

#保持所有擁有本地方法的類名及本地方法名
-keepclasseswithmembernames class * {
native <methods>;
}

#保持自訂View的get和set相關方法
-keepclassmembers public class extends android.view.View {
void set
();
get*();
}

#保持Activity中View及其子類入參的方法
-keepclassmembers class extends android.app.Activity {
public void
(android.view.View);
}

#枚舉
-keepclassmembers enum * {
*[] $VALUES;
public
;
}

#Parcelable
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}

#R檔案的靜態成員
-keepclassmembers class *.R$ {
public static <fields>;
}

-dontwarn android.support.**

#keep相關註解
-keep class android.support.annotation.Keep

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

-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}

-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}

-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}

寫給 Android 開發人員的混淆使用手冊

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.