溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android開發(fā)混淆使用手冊

發(fā)布時間:2020-07-23 21:51:06 來源:網(wǎng)絡(luò) 閱讀:587 作者:未來程序員 欄目:移動開發(fā)

一、Android混淆最佳實踐

  1. 混淆配置。

  2. 自定義混淆規(guī)則。

  3. 檢查混淆結(jié)果。

  4. 解出混淆棧。

二、混淆簡介

  1. 代碼壓縮。

  2. 資源壓縮。

三、自定義混淆規(guī)則

  1. 常見混淆命令。

  2. 保持元素不參與混淆的規(guī)則。

  3. 常用的自定義混淆規(guī)則。

四、自定義資源保持規(guī)則

  1. keep.xml。

  2. 移除替代資源。


綜述

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

首先,這里說的的混淆其實是包括了代碼壓縮、代碼混淆以及資源壓縮等的優(yōu)化過程。依靠 ProGuard,混淆流程將主項目以及依賴庫中未被使用的類、類成員、方法、屬性移除,這有助于規(guī)避64K方法數(shù)的瓶頸;同時,將類、類成員、方法重命名為無意義的簡短名稱,增加了逆向工程的難度。而依靠 Gradle 的 Android 插件,我們將移除未被使用的資源,可以有效減小 apk 安裝包大小。

本文由兩部分構(gòu)成,第一部分給出混淆的最佳實踐,力求讓零基礎(chǔ)的新手都可以直接使用混淆;第二部分會介紹一下混淆的整體、自定義混淆規(guī)則的語法與實踐、自定義資源保持的規(guī)則等。

一、Android混淆最佳實踐

1. 混淆配置

一般情況下,app module 的 build.gradle 文件默認(rèn)會有如下結(jié)構(gòu):

android {
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile
           ('proguard-android.txt'), 'proguard-rules.pro'        }    } }

因為開啟混淆會使編譯時間變長,所以debug模式下不應(yīng)該開啟。我們需要做的是: 
1. 將releaseminifyEnabled的值改為true,打開混淆; 
2. 加上shrinkResources true,打開資源壓縮。

修改后文件內(nèi)容如下:

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

2. 自定義混淆規(guī)則

在 app module 下默認(rèn)生成了項目的自定義混淆規(guī)則文件 proguard-rules.pro,多方調(diào)研后,一份適用于大部分項目的混淆規(guī)則最佳實踐如下:

#指定壓縮級別
-optimizationpasses 5

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

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

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

#優(yōu)化時允許訪問并修改有修飾符的類和類的成員 -allowaccessmodification

#將文件來源重命名為“SourceFile”字符串 -renamesourcefileattribute SourceFile

#保留行號 -keepattributes SourceFile,LineNumberTable

#保持所有實現(xiàn) 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

# 保持測試相關(guān)的代碼 -dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**

真正通用的、需要添加的就是上面這些,除此之外,需要每個項目根據(jù)自身的需求添加一些混淆規(guī)則: 
- 第三方庫所需的混淆規(guī)則。正規(guī)的第三方庫一般都會在接入文檔中寫好所需混淆規(guī)則,使用時注意添加。 
- 在運行時動態(tài)改變的代碼,例如反射。比較典型的例子就是會與 json 相互轉(zhuǎn)換的實體類。假如項目命名規(guī)范要求實體類都要放在model包下的話,可以添加類似這樣的代碼把所有實體類都保持住:-keep public class **.*Model*.** {*;} 
JNI中調(diào)用的類。 
WebViewJavaScript調(diào)用的方法 
Layout布局使用的View構(gòu)造函數(shù)、android:onClick等。

3. 檢查混淆結(jié)果

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

一方面,需要從代碼層面檢查。使用上文的配置進行混淆打包后在 <module-name>/build/outputs/mapping/release/ 目錄下會輸出以下文件: 
dump.txt 
描述APK文件中所有類的內(nèi)部結(jié)構(gòu) 
mapping.txt 
提供混淆前后類、方法、類成員等的對照表 
seeds.txt 
列出沒有被混淆的類和成員 
usage.txt 
列出被移除的代碼

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

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

4. 解出混淆棧

混淆后的類、方法名等等難以閱讀,這固然會增加逆向工程的難度,但對追蹤線上 crash 也造成了阻礙。我們拿到 crash 的堆棧信息后會發(fā)現(xiàn)很難定位,這時需要將混淆反解。

在 <sdk-root>/tools/proguard/ 路徑下有附帶的的反解工具(Window 系統(tǒng)為 proguardgui.bat,Mac 或 Linux 系統(tǒng)為 proguardgui.sh)。

這里以 Window 平臺為例。雙擊運行 proguardgui.bat 后,可以看到左側(cè)的一行菜單。點擊 ReTrace,選擇該混淆包對應(yīng)的 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 涉及到的類已經(jīng)自動被保持,因此不用特意去添加這塊混淆規(guī)則。(很多老的混淆文件里會加,現(xiàn)在已經(jīng)沒必要)

2) proguard-android.txt 已經(jīng)存在一些默認(rèn)混淆規(guī)則,沒必要在 proguard-rules.pro 重復(fù)添加,該文件具體規(guī)則見附錄1:

二、混淆簡介

Android中的“混淆”可以分為兩部分,一部分是 Java 代碼的優(yōu)化與混淆,依靠 proguard 混淆器來實現(xiàn);另一部分是資源壓縮,將移除項目及依賴的庫中未被使用的資源(資源壓縮嚴(yán)格意義上跟混淆沒啥關(guān)系,但一般我們都會放一起講)。

1. 代碼壓縮

Android開發(fā)混淆使用手冊

代碼混淆是包含了代碼壓縮、優(yōu)化、混淆等一系列行為的過程。如上圖所示,混淆過程會有如下幾個功能: 
1. 壓縮。移除無效的類、類成員、方法、屬性等; 
2. 優(yōu)化。分析和優(yōu)化方法的二進制代碼;根據(jù)proguard-android-optimize.txt中的描述,優(yōu)化可能會造成一些潛在風(fēng)險,不能保證在所有版本的Dalvik上都正常運行。 
3. 混淆。把類名、屬性名、方法名替換為簡短且無意義的名稱; 
4. 預(yù)校驗。添加預(yù)校驗信息。這個預(yù)校驗是作用在Java平臺上的,Android平臺上不需要這項功能,去掉之后還可以加快混淆速度。

這四個流程默認(rèn)開啟。

在 Android 項目中我們可以選擇將“優(yōu)化”和“預(yù)校驗”關(guān)閉,對應(yīng)命令是-dontoptimize、-dontpreverify(當(dāng)然,默認(rèn)的 proguard-android.txt 文件已包含這兩條混淆命令,不需要開發(fā)者額外配置)。

2. 資源壓縮

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

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

“合并資源”流程中,名稱相同的資源被視為重復(fù)資源會被合并。需要注意的是,這一流程不受shrinkResources屬性控制,也無法被禁止, gradle 必然會做這項工作,因為假如不同項目中存在相同名稱的資源將導(dǎo)致錯誤。gradle 在四處地方尋找重復(fù)資源: 
src/main/res/ 路徑 
- 不同的構(gòu)建類型(debug、release等等) 
- 不同的構(gòu)建渠道 
- 項目依賴的第三方庫

合并資源時按照如下優(yōu)先級順序:

依賴 -> main -> 渠道 -> 構(gòu)建類型

舉個例子,假如重復(fù)資源同時存在于main文件夾和不同渠道中,gradle 會選擇保留渠道中的資源。

同時,如果重復(fù)資源在同一層次出現(xiàn),比如src/main/res/ 和 src/main/res2/,則 gradle 無法完成資源合并,這時會報資源合并錯誤。

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

三、自定義混淆規(guī)則

在上文“混淆配置”中有這樣一行代碼

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

這行代碼定義了混淆規(guī)則由兩部分構(gòu)成:位于 SDK 的 tools/proguard/ 文件夾中的 proguard-android.txt 的內(nèi)容以及默認(rèn)放置于模塊根目錄的 proguard-rules.pro 的內(nèi)容。前者是 SDK 提供的默認(rèn)混淆文件(內(nèi)容見附錄1),后者是開發(fā)者自定義混淆規(guī)則的地方。

1. 常見混淆命令:

  • optimizationpasses

  • dontoptimize

  • dontusemixedcaseclassnames

  • dontskipnonpubliclibraryclasses

  • dontpreverify

  • dontwarn

  • verbose

  • optimizations

  • keep

  • keepnames

  • keepclassmembers

  • keepclassmembernames

  • keepclasseswithmembers

  • keepclasseswithmembernames

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

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

2. 保持元素不參與混淆的規(guī)則

形如:

[保持命令] [類] {    [成員] 
}

“類”代表類相關(guān)的限定條件,它將最終定位到某些符合該限定條件的類。它的內(nèi)容可以使用: 
- 具體的類 
- 訪問修飾符(public、protected、private) 
- 通配符*,匹配任意長度字符,但不含包名分隔符(.) 
- 通配符**,匹配任意長度字符,并且包含包名分隔符(.) 
extends,即可以指定類的基類 
implement,匹配實現(xiàn)了某接口的類 
- $,內(nèi)部類

“成員”代表類成員相關(guān)的限定條件,它將最終定位到某些符合該限定條件的類成員。它的內(nèi)容可以使用: 
- 匹配所有構(gòu)造器 
- 匹配所有域 
- 匹配所有方法 
- 通配符*,匹配任意長度字符,但不含包名分隔符(.) 
- 通配符**,匹配任意長度字符,并且包含包名分隔符(.) 
- 通配符***,匹配任意參數(shù)類型 
,匹配任意長度的任意類型參數(shù)。比如void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b) 這些方法。 
- 訪問修飾符(publicprotected、private

舉個例子,假如需要將name.boby.test包下所有繼承Activitypublic類及其構(gòu)造函數(shù)都保持住,可以這樣寫:

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

3. 常用的自定義混淆規(guī)則

  • 不混淆某個類

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

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

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

-keep public class **.*model*.** {*;}
  • 不混淆某個接口的實現(xiàn)

-keep class * implements name.boby.example.TestInterface { *; }
  • 不混淆某個類的構(gòu)造方法

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

-keepclassmembers class name.boby.example.Test { 
    public void test(java.lang.String); }
  • 不混淆某個類的內(nèi)部類

-keep class name.boby.example.Test$* {        
   *; }

四、自定義資源保持規(guī)則

1. keep.xml

shrinkResources true開啟資源壓縮后,所有未被使用的資源默認(rèn)被移除。假如你需要定義哪些資源必須被保留,在 res/raw/ 路徑下創(chuàng)建一個 xml 文件,例如 keep.xml。

通過一些屬性的設(shè)置可以實現(xiàn)定義資源保持的需求,可配置的屬性有: 
tools:keep 定義哪些資源需要被保留(資源之間用“,”隔開) 
tools:discard 定義哪些資源需要被移除(資源之間用“,”隔開) 
tools:shrinkMode 開啟嚴(yán)格模式

當(dāng)代碼中通過 Resources.getIdentifier() 用動態(tài)的字符串來獲取并使用資源時,普通的資源引用檢查就可能會有問題。例如,如下代碼會導(dǎo)致所有以“img_”開頭的資源都被標(biāo)記為已使用。

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

我們可以設(shè)置 tools:shrinkMode 為 strict 來開啟嚴(yán)格模式,使只有確實被使用的資源被保留。

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

<?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"/>

2. 移除替代資源

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

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

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

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

參考資料

  • Shrink Your Code and Resources

  • proguard

  • Android安全***戰(zhàn),反編譯與混淆技術(shù)完全解析(下)

  • Android混淆從入門到精通

  • Android代碼混淆之ProGuard

附錄

  1. proguard-android.txt文件內(nèi)容

#包名不混合大小寫
-dontusemixedcaseclassnames

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

#混淆時記錄日志
-verbose

#關(guān)閉預(yù)校驗
-dontpreverify

#不優(yōu)化輸入的類文件
-dontoptimize

#保護注解
-keepattributes *Annotation*

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

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

#保持Activity中View及其子類入?yún)⒌姆椒?br />-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文件的靜態(tài)成員
-keepclassmembers class **.R$* {    public static <fields>; } -dontwarn android.support.**

#keep相關(guān)注解
-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>(...); }



向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI