【轉(zhuǎn)載】寫給Android開(kāi)發(fā)者的混淆使用手冊(cè)

2016-10-23?光源?coder

轉(zhuǎn)載自 http://www.lxweimin.com/writer#/notebooks/7294201/notes/6726517

大家好,我是光源。

本文首發(fā)于我的個(gè)人公眾賬號(hào),同時(shí)會(huì)在個(gè)人博客上同步。假如有任何建議還請(qǐng)移步博客點(diǎn)評(píng),同時(shí)如果博客本身有修改或勘誤,也會(huì)在博客更新。

綜述

毫無(wú)疑問(wèn),混淆是打包過(guò)程中最重要的流程之一,在沒(méi)有特殊原因的情況下,所有 app 都應(yīng)該開(kāi)啟混淆。

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

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

一、Android混淆最佳實(shí)踐

1. 混淆配置

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

android {

buildTypes {

release {

minifyEnabled false

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

}

}

}

因?yàn)殚_(kāi)啟混淆會(huì)使編譯時(shí)間變長(zhǎng),所以debug模式下不應(yīng)該開(kāi)啟。我們需要做的是:

將release下minifyEnabled的值改為true,打開(kāi)混淆;

加上shrinkResources true,打開(kāi)資源壓縮。

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

android {

buildTypes {

release {

minifyEnabled true

shrinkResources true

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

}

}

}

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

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

#指定壓縮級(jí)別

-optimizationpasses 5

#不跳過(guò)非公共的庫(kù)的類成員

-dontskipnonpubliclibraryclassmembers

#混淆時(shí)采用的算法

-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

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

-useuniqueclassmembernames

#優(yōu)化時(shí)允許訪問(wèn)并修改有修飾符的類和類的成員

-allowaccessmodification

#將文件來(lái)源重命名為“SourceFile”字符串

-renamesourcefileattribute SourceFile

#保留行號(hào)

-keepattributes SourceFile,LineNumberTable

#保持所有實(shí)現(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中注冊(cè),需要額外保護(hù)下

-keep public class * extends android.support.v4.app.Fragment

-keep public class * extends android.app.Fragment

# 保持測(cè)試相關(guān)的代碼

-dontnote junit.framework.**

-dontnote junit.runner.**

-dontwarn android.test.**

-dontwarn android.support.test.**

-dontwarn org.junit.**

真正通用的、需要添加的就是上面這些,除此之外,需要每個(gè)項(xiàng)目根據(jù)自身的需求添加一些混淆規(guī)則:

第三方庫(kù)所需的混淆規(guī)則。正規(guī)的第三方庫(kù)一般都會(huì)在接入文檔中寫好所需混淆規(guī)則,使用時(shí)注意添加。

在運(yùn)行時(shí)動(dòng)態(tài)改變的代碼,例如反射。比較典型的例子就是會(huì)與 json 相互轉(zhuǎn)換的實(shí)體類。假如項(xiàng)目命名規(guī)范要求實(shí)體類都要放在model包下的話,可以添加類似這樣的代碼把所有實(shí)體類都保持住:-keep public class **.*Model*.** {*;}

JNI中調(diào)用的類。

WebView中JavaScript調(diào)用的方法

Layout布局使用的View構(gòu)造函數(shù)、android:onClick等。

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

混淆過(guò)的包必須進(jìn)行檢查,避免因混淆引入的bug。

一方面,需要從代碼層面檢查。使用上文的配置進(jìn)行混淆打包后在/build/outputs/mapping/release/目錄下會(huì)輸出以下文件:

dump.txt

描述APK文件中所有類的內(nèi)部結(jié)構(gòu)

mapping.txt

提供混淆前后類、方法、類成員等的對(duì)照表

seeds.txt

列出沒(méi)有被混淆的類和成員

usage.txt

列出被移除的代碼

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

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

4. 解出混淆棧

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

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

這里以 Window 平臺(tái)為例。雙擊運(yùn)行proguardgui.bat后,可以看到左側(cè)的一行菜單。點(diǎn)擊ReTrace,選擇該混淆包對(duì)應(yīng)的 mapping 文件(混淆后在/build/outputs/mapping/release/路徑下會(huì)生成mapping.txt文件,它的作用是提供混淆前后類、方法、類成員等的對(duì)照表),再將 crash 的stack trace黏貼進(jìn)輸入框中,點(diǎn)擊右下角的ReTrace,混淆后的堆棧信息就顯示出來(lái)了。

以上使用 GUI 程序進(jìn)行操作,另一種方式是利用該路徑下的retrace工具通過(guò)命令行進(jìn)行反解,命令是

retrace.bat|retrace.sh [-verbose] mapping.txt []

例如:

retrace.bat -verbose mapping.txt obfuscated_trace.txt

注意事項(xiàng):

1) 所有在AndroidManifest.xml涉及到的類已經(jīng)自動(dòng)被保持,因此不用特意去添加這塊混淆規(guī)則。(很多老的混淆文件里會(huì)加,現(xiàn)在已經(jīng)沒(méi)必要)

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

二、混淆簡(jiǎn)介

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

1. 代碼壓縮

代碼混淆是包含了代碼壓縮、優(yōu)化、混淆等一系列行為的過(guò)程。如上圖所示,混淆過(guò)程會(huì)有如下幾個(gè)功能:

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

優(yōu)化。分析和優(yōu)化方法的二進(jìn)制代碼;根據(jù)proguard-android-optimize.txt中的描述,優(yōu)化可能會(huì)造成一些潛在風(fēng)險(xiǎn),不能保證在所有版本的Dalvik上都正常運(yùn)行。

混淆。把類名、屬性名、方法名替換為簡(jiǎn)短且無(wú)意義的名稱;

預(yù)校驗(yàn)。添加預(yù)校驗(yàn)信息。這個(gè)預(yù)校驗(yàn)是作用在Java平臺(tái)上的,Android平臺(tái)上不需要這項(xiàng)功能,去掉之后還可以加快混淆速度。

這四個(gè)流程默認(rèn)開(kāi)啟。

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

2. 資源壓縮

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

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

“合并資源”流程中,名稱相同的資源被視為重復(fù)資源會(huì)被合并。需要注意的是,這一流程不受shrinkResources屬性控制,也無(wú)法被禁止,gradle 必然會(huì)做這項(xiàng)工作,因?yàn)榧偃绮煌?xiàng)目中存在相同名稱的資源將導(dǎo)致錯(cuò)誤。gradle 在四處地方尋找重復(fù)資源:

src/main/res/路徑

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

不同的構(gòu)建渠道

項(xiàng)目依賴的第三方庫(kù)

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

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

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

同時(shí),如果重復(fù)資源在同一層次出現(xiàn),比如src/main/res/和src/main/res/,則 gradle 無(wú)法完成資源合并,這時(shí)會(huì)報(bào)資源合并錯(cuò)誤。

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

三、自定義混淆規(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)容見(jiàn)附錄1),后者是開(kāi)發(fā)者自定義混淆規(guī)則的地方。

1. 常見(jiàn)混淆命令:

optimizationpasses

dontoptimize

dontusemixedcaseclassnames

dontskipnonpubliclibraryclasses

dontpreverify

dontwarn

verbose

optimizations

keep

keepnames

keepclassmembers

keepclassmembernames

keepclasseswithmembers

keepclasseswithmembernames

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

命令作用

-keep防止類和成員被移除或者被重命名

-keepnames防止類和成員被重命名

-keepclassmembers防止成員被移除或者被重命名

-keepnames防止成員被重命名

-keepclasseswithmembers防止擁有該成員的類和成員被移除或者被重命名

-keepclasseswithmembernames防止擁有該成員的類和成員被重命名

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

形如:

[保持命令] [類] {

[成員]

}

“類”代表類相關(guān)的限定條件,它將最終定位到某些符合該限定條件的類。它的內(nèi)容可以使用:

具體的類

訪問(wèn)修飾符(public、protected、private)

通配符*,匹配任意長(zhǎng)度字符,但不含包名分隔符(.)

通配符**,匹配任意長(zhǎng)度字符,并且包含包名分隔符(.)

extends,即可以指定類的基類

implement,匹配實(shí)現(xiàn)了某接口的類

$,內(nèi)部類

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

匹配所有構(gòu)造器

匹配所有域

匹配所有方法

通配符*,匹配任意長(zhǎng)度字符,但不含包名分隔符(.)

通配符**,匹配任意長(zhǎng)度字符,并且包含包名分隔符(.)

通配符***,匹配任意參數(shù)類型

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

訪問(wèn)修飾符(public、protected、private)

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

-keep public class name.huihui.test.** extends Android.app.Activity {

}

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

不混淆某個(gè)類

-keep public class name.huihui.example.Test { *; }

不混淆某個(gè)包所有的類

-keep class name.huihui.test.** { *; }

不混淆某個(gè)類的子類

-keep public class * extends name.huihui.example.Test { *; }

不混淆所有類名中包含了“model”的類及其成員

-keep public class **.*model*.** {*;}

不混淆某個(gè)接口的實(shí)現(xiàn)

-keep class * implements name.huihui.example.TestInterface { *; }

不混淆某個(gè)類的構(gòu)造方法

-keepclassmembers class name.huihui.example.Test {

public();

}

不混淆某個(gè)類的特定的方法

-keepclassmembers class name.huihui.example.Test {

public void test(java.lang.String);

}

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

1. keep.xml

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

通過(guò)一些屬性的設(shè)置可以實(shí)現(xiàn)定義資源保持的需求,可配置的屬性有:

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

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

tools:shrinkMode開(kāi)啟嚴(yán)格模式

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

String name = String.format("img_%1d", angle + 1);

res = getResources().getIdentifier(name, "drawable", getPackageName());

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

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

tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"

tools:discard="@layout/unused2"

tools:shrinkMode="strict"/>

2. 移除替代資源

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

我們使用resConfig屬性來(lái)指定需要支持的屬性,例如

android {

defaultConfig {

...

resConfigs "en", "fr"

}

}

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

參考資料

Shrink Your Code and Resources

proguard

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

Android混淆從入門到精通

Android代碼混淆之ProGuard

附錄

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

#包名不混合大小寫

-dontusemixedcaseclassnames

#不跳過(guò)非公共的庫(kù)的類

-dontskipnonpubliclibraryclasses

#混淆時(shí)記錄日志

-verbose

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

-dontpreverify

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

-dontoptimize

#保護(hù)注解

-keepattributes *Annotation*

#保持所有擁有本地方法的類名及本地方法名

-keepclasseswithmembernames class * {

native;

}

#保持自定義View的get和set相關(guān)方法

-keepclassmembers public class * extends android.view.View {

void set*(***);

*** get*();

}

#保持Activity中View及其子類入?yún)⒌姆椒?/p>

-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;

}

-dontwarn android.support.**

#keep相關(guān)注解

-keep class android.support.annotation.Keep

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

-keepclasseswithmembers class * {

@android.support.annotation.Keep;

}

-keepclasseswithmembers class * {

@android.support.annotation.Keep;

}

-keepclasseswithmembers class * {

@android.support.annotation.Keep(...);

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • https://yq.aliyun.com/articles/62980?utm_campaign=wenzhan...
    x360閱讀 1,450評(píng)論 0 3
  • 概述 混淆是Android Apk打包過(guò)程中的一個(gè)重要步驟,默認(rèn)情況下,打包都是需要混淆過(guò)程的。?Android ...
    androidjp閱讀 2,612評(píng)論 1 13
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,264評(píng)論 25 708
  • 學(xué)習(xí)是一件苦差事啊 人丑就要多讀書 勸你還是繼續(xù)堅(jiān)持下去吧 洛施施。
    洛施施閱讀 128評(píng)論 0 0
  • 第一秒 魚愛(ài)上另一條魚 第二秒 魚說(shuō) 我愛(ài)你呀 第三秒 另一條魚高傲游走了 第四秒 魚很傷心 第五秒 魚得了抑郁癥...
    溦汐閱讀 417評(píng)論 3 11