一介褥、為什么進(jìn)行混淆
Java 是一種跨平臺的座掘、解釋型語言,Java 源代碼編譯成中間”字節(jié)碼”存儲于 class 文件中柔滔。由于跨平臺的需要溢陪,Java 字節(jié)碼中包括了很多源代碼信息,如變量名睛廊、方法名形真,并且通過這些名稱來訪問變量和方法,這些符號帶有許多語義信息超全,很容易被反編譯成 Java 源代碼咆霜。為了防止這種現(xiàn)象,我們可以使用 Java 混淆器對 Java 字節(jié)碼進(jìn)行混淆嘶朱。
通過代碼混淆可以將項(xiàng)目中的類蛾坯、方法、變量等信息進(jìn)行重命名疏遏,變成一些無意義的簡短名字偿衰,同時(shí)也可以移除未被使用的類挂疆、方法、變量等下翎。所以直觀的看缤言,通過混淆可以提高程序的安全性,增加逆向工程的難度视事,同時(shí)也有效縮減了apk的體積胆萧。
二、原理
對發(fā)布出去的程序進(jìn)行重新組織和處理俐东,使得處理后的代碼與處理前代碼完成相同的功能跌穗,而混淆后的代碼很難被反編譯,即使反編譯成功也很難得出程序的真正語義虏辫。被混淆過的程序代碼蚌吸,仍然遵照原來的檔案格式和指令集,執(zhí)行結(jié)果也與混淆前一樣砌庄,只是混淆器將代碼中的所有變量羹唠、函數(shù)、類的名稱變?yōu)楹喍痰挠⑽淖帜复柭ィ谌狈ο鄳?yīng)的函數(shù)名和程序注釋的況下佩微,即使被反編譯,也將難以閱讀萌焰。同時(shí)混淆是不可逆的哺眯,在混淆的過程中一些不影響正常運(yùn)行的信息將永久丟失,這些信息的丟失使程序變得更加難以理解扒俯。
三奶卓、如何進(jìn)行混淆
Android SDK 自帶了混淆工具Proguard。它位于<Android SDK目錄>/tools/proguard下面撼玄。
proguard 就是可以把方法夺姑,字段,包和類這些java 元素的名稱改成無意義的名稱互纯,這樣代碼結(jié)構(gòu)沒有變化瑟幕,還可以運(yùn)行磕蒲,但是想弄懂代碼的架構(gòu)卻很難的混淆工具留潦。它可以分析一組class 的結(jié)構(gòu),根據(jù)用戶的配置辣往,然后把這些class 文件的可以混淆java 元素名混淆掉兔院。在分析class 的同時(shí),他還有其他兩個(gè)功能站削,刪除無效代碼(Shrinking 收縮)坊萝,和代碼進(jìn)行優(yōu)化 (Optimization Options)。
如果開啟了混淆,Proguard默認(rèn)情況下會(huì)對所有代碼十偶,包括第三方包都進(jìn)行混淆菩鲜,可是有些代碼或者第三方包是不能混淆的,這就需要我們手動(dòng)編寫混淆規(guī)則來保持不能被混淆的部分惦积。
直接在android項(xiàng)目的build.gradle 文件中配置就可以混淆代碼了:
android {
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
1> 代碼混淆:
配置如 minifyEnabled true接校。
代碼混淆包括4個(gè)步驟:壓縮, 優(yōu)化狮崩, 混淆蛛勉, 預(yù)校驗(yàn)。
①壓縮(shrink):移除無用的類睦柴,類的成員诽凌,方法,屬性等坦敌;
②優(yōu)化(optimize):分析和優(yōu)化二進(jìn)制代碼侣诵,根據(jù)Proguard-android-optimize.txt 中的描述,優(yōu)化可能會(huì)造成一定的風(fēng)險(xiǎn)恬试,不能保證在所有版本上的Dalvik正常運(yùn)行窝趣。因此android項(xiàng)目建議關(guān)閉該項(xiàng)。
③混淆(obfuscate):把類名训柴,屬性名哑舒,方法名替換為簡短且無意義的名稱:使用a、b幻馁、c洗鸵、d這樣簡短而無意義的名稱,對類仗嗦、字段和方法進(jìn)行重命名膘滨。
④預(yù)校驗(yàn)(previrfy):添加預(yù)校驗(yàn)信息。這個(gè)預(yù)校驗(yàn)是作用在java平臺的稀拐,android平臺不需要這個(gè)功能火邓,去掉之后還可以加快混淆速度。因此android項(xiàng)目關(guān)閉該功能德撬。
這4個(gè)流程默認(rèn)是開啟的铲咨,我們需要在android的混淆配置文件中關(guān)閉代碼優(yōu)化和預(yù)校驗(yàn)功能,即 -dontoptimize蜓洪, -dontpreverify纤勒。(在 proguard-android.txt中默認(rèn)已經(jīng)關(guān)閉了這2項(xiàng))
2> 資源混淆(刪除沒有引用資源):
配置如 shrinkResources true
÷√矗可以減少apk的大小摇天,一般建議開啟粹湃。
在應(yīng)用構(gòu)建打包的過程中自動(dòng)刪除沒有引用的資源,對依賴的庫同樣有效泉坐。shrinkResources必須和minifyEnabled配合使用達(dá)到減少包體積的作用为鳄,只有刪除了無用的代碼之后,才能知道哪些資源是無用的腕让。
資源處理包括2個(gè)流程:合并資源济赎,移除資源;
①android studio打包時(shí)會(huì)自動(dòng)merge資源记某,不受參數(shù)控制司训。
②移除資源,需要配置參數(shù)液南。(建議可以先用lint自行過一遍)
四壳猜、默認(rèn)基本混淆配置
-
proguard-android.txt
代表系統(tǒng)默認(rèn)的混淆規(guī)則配置文件,該文件在<Android SDK目錄>/tools/proguard
下滑凉,這里面是一些比較常規(guī)的不能被混淆的代碼規(guī)則统扳。一般不要更改該配置文件,因?yàn)橐矔?huì)作用于其它項(xiàng)目畅姊,除非你能確保所做的更改不影響其它項(xiàng)目的混淆咒钟。 -
proguard-rules.pro
代碼表當(dāng)前project
的混淆配置文件,是針對我們自己的項(xiàng)目需要特別定義混淆規(guī)則若未。它在app module
下朱嘴,可以通過修改該文件來添加適用當(dāng)前項(xiàng)目的混淆規(guī)則。
在<Android SDK目錄>/tools/proguard/
proguard-android.txt這個(gè)文件在Android Gradle插件2.2+以上不再不再維護(hù)和使用了粗合。相反萍嬉,Android Gradle插件在構(gòu)建時(shí)生成默認(rèn)規(guī)則,并將它們存儲在build目錄中隙疚。所以說現(xiàn)在混淆的默認(rèn)規(guī)則是由Gradle自動(dòng)生成的壤追。(例如:...\build\intermediates\proguard-files\proguard-android.txt-4.2.2這個(gè)文件就是gradle版本生成的,在新版本中供屉,通過
proguardFiles getDefaultProguardFile('proguard-android.txt')獲取到的就是這個(gè)文件中的混淆規(guī)則
)
系統(tǒng)的proguard-android.txt 中內(nèi)容:
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
# 混淆時(shí)不使用大小寫混合類名
-dontusemixedcaseclassnames
# 不跳過library中的非public的類
-dontskipnonpubliclibraryclasses
# 打印混淆的詳細(xì)信息
-verbose
# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
# 關(guān)閉優(yōu)化(原因見上邊的原英文注釋)
-dontoptimize
# 不進(jìn)行預(yù)校驗(yàn)行冰,可加快混淆速度
-dontpreverify
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.
# 保留注解中的參數(shù)
-keepattributes *Annotation*
# 不混淆如下兩個(gè)谷歌服務(wù)類
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
# 不混淆包含native方法的類的類名以及native方法名
-keepclasseswithmembernames class * {
native <methods>;
}
# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
# 不混淆View中的setXxx()和getXxx()方法,以保證屬性動(dòng)畫正常工作
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
# We want to keep methods in Activity that could be used in the XML attribute onClick
# 不混淆Activity中參數(shù)是View的方法伶丐,例如悼做,一個(gè)控件通過android:onClick="clickMethodName"綁定點(diǎn)擊事件,混淆后會(huì)導(dǎo)致點(diǎn)擊事件失效
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
# 不混淆枚舉類中的values()和valueOf()方法
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 不混淆Parcelable實(shí)現(xiàn)類中的CREATOR字段撵割,以保證Parcelable機(jī)制正常工作
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
# 不混淆R文件中的所有靜態(tài)字段贿堰,以保證正確找到每個(gè)資源的id
-keepclassmembers class **.R$* {
public static <fields>;
}
# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
# 不對android.support包下的代碼警告(如果我們打包的版本低于support包下某些類的使用版本辙芍,會(huì)出現(xiàn)警告的問題)
-dontwarn android.support.**
# Understand the @Keep support annotation.
# 不混淆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>;
}
# 如果類中有使用了注解的構(gòu)造函數(shù),則不混淆類和類成員
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
五庶灿、混淆配置的語法
可以從上面的代碼中看出proguard-android.txt
主要作用是防止指定內(nèi)容被混淆纵搁,其中使用了以-
開頭,結(jié)合keep
類關(guān)鍵字往踢,*
腾誉、<>
等通配符的語法。
下面我們來看看混淆相關(guān)項(xiàng):
1> 混淆常見關(guān)鍵字:
關(guān)鍵字 | 含義 |
---|---|
keep | 保留 類和類成員峻呕,防止混淆或移除 |
keepnames | 保留 類和類和類成員利职,防止被混淆,但是沒有引用的類成員會(huì)被移除 |
keepclassmembers | 只保留 類成員瘦癌,防止混淆或移除 |
keepclassmembernames | 只保留 類成員猪贪,防止混淆,但沒有引用的類成員會(huì)被移除 |
keepclasseswithmembers | 保留 類和類成員 讯私,防止被混淆或移除热押,如果指定的類成員不存還是會(huì)被混淆 |
keppclasseswithmembernames | 保留 類和類成員,防止被混淆斤寇,如果指定類成員不存在還是會(huì)被混淆桶癣,沒有被引用的類成員會(huì)被移除 |
dontwarn | dontwarn 基本和keep同時(shí)出現(xiàn),尤其是在引入 library時(shí)娘锁,為了忽略library的警告牙寞,保證build的正常進(jìn)行 |
-dontwarn 主要是避免警告,-keep 主要是保留不被混淆</pre>
2> 相關(guān)通配符:
通配符 | 含義 |
---|---|
* | 匹配任意長度字符莫秆,但不包含分隔符 . 捞稿。例如一個(gè)類全路徑是com.heytap.test.demo瓶堕,使用com.heytap.test .* 就可以匹配,但是com.heytap.* 就不能匹配 |
** | 匹配任意長度字符,并包含分隔符 . 滔岳。例如: com.heytap.test.**可以匹配包下的所有內(nèi)容 |
*** | 匹配任意參數(shù)類型。例如: *** getName(***)可以匹配String getName (String) |
... | 匹配任意長度的任意參數(shù)類型澡绩。 例如: void setName(...) 可以匹配 void setName(String name0,String name1,String name2) |
<fileds> | 匹配類豹爹、接口中所有字段 |
<methods> | 匹配類、接口中所有方法 |
<init> | 匹配所有的構(gòu)造函數(shù) |
六党巾、混淆注意問題
混淆配置官方文檔:gradle example
下面是關(guān)于混淆配置時(shí)要注意的問題:
1萎庭、運(yùn)用了反射的類也不進(jìn)行混淆。因?yàn)榇a混淆齿拂,類名驳规、方法名、屬性名都改變了署海,而反射它還是按照原來的名字去反射吗购,結(jié)果程序崩潰医男。
2、注解不能混淆捻勉。因?yàn)樽⒔庖灿玫搅薺ava反射镀梭,所以不能混淆。
3踱启、Activity不能混淆报账。因?yàn)锳ndroidManifest.xml文件中是完整的名字
4、自定義View不能混淆埠偿。因?yàn)楸籄ndroid Resource 文件引用到的透罢,名字已經(jīng)固定,也不能混淆冠蒋。自定義view是帶了包名寫在xml布局中的琐凭。
5、使用了 Gson 之類的工具要使 JavaBean 類即實(shí)體類不能混淆浊服。因?yàn)閖son轉(zhuǎn)換用到了java反射统屈。
6、泛型不能混淆牙躺。
7愁憔、自定義控件類的get/set方法和構(gòu)造函數(shù)不能混淆。
8孽拷、內(nèi)部類吨掌,如果會(huì)被外部調(diào)用到,那么也不能混淆脓恕。
9膜宋、使用了枚舉要保證枚舉不被混淆
10、對第三方庫中的類不進(jìn)行混淆
11炼幔、不混淆任何包含native方法的秋茫,否則找不到本地方法。
12乃秀、屬性動(dòng)畫兼容庫不能混淆肛著。
13、在引用第三方庫的時(shí)候跺讯,一般會(huì)標(biāo)明庫的混淆規(guī)則的枢贿,建議在使用的時(shí)候就把混淆規(guī)則添加上去,免得到最后才去找
14刀脏、有用到 WebView 的 JS 調(diào)用也需要保證寫的接口方法不混淆局荚,原因和第一條一樣
15、Parcelable 的子類和 Creator 靜態(tài)成員變量不混淆,否則會(huì)產(chǎn)生 Android.os.BadParcelableException 異常
16耀态、不混淆Rxjava/RxAndroid
17轮傍、對于Android APP來說四大組件和Application不能混淆。現(xiàn)在的系統(tǒng)已經(jīng)配置為混淆時(shí)候會(huì)保留Android系統(tǒng)組件
18茫陆、數(shù)據(jù)庫驅(qū)動(dòng)
19、其他Anroid 官方建議 不混淆的擎析,如
android.app.backup.BackupAgentHelper
android.preference.Preference
com.android.vending.licensing.ILicensingService
注意:如果你的Android SDK Tools版本足夠高(>24)簿盅,那么在proguard-rules.pro文件其實(shí)不用做任何改動(dòng),因?yàn)镚oogle已經(jīng)幫我們在proguard-android.txt文件配置好了(如果較低就把下面代碼拷貝到proguard-android.txt中)揍魂。
混淆代碼配置參考:
1.>不混淆桨醋,需要保留的東西:
#
保留了繼承自Activity、Application這些類的子類
#
因?yàn)檫@些子類有可能被外部調(diào)用
#
比如第一行就保證了所有Activity的子類不要被混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-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.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
#
如果有引用android-support-v4.jar包现斋,可以添加下面這行
-keep public class com.null.test.ui.fragment.** {*;}
#
保留Activity中的方法參數(shù)是view的方法喜最,
#
從而我們在layout里面編寫onClick就不會(huì)影響
-keepclassmembers class * extends android.app.Activity {
public void * (android.view.View);
}
#
保留自定義控件(繼承自View)不能被混淆
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set(*);
*** get ();
}
#
保留Serializable 序列化的類不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
對于帶有回調(diào)函數(shù)onXXEvent的,不能混淆
-keepclassmembers class * {
void *
(**
On*
Event);
}
2.>第三方和自己的bean文件是不需要混淆:
-keep public class com.heytap.test.yourBeanPackageName.** {
//全部忽略
*
;
}
-keep public class com.heytap.test.yourBeanPackageName.** {
//忽略get和set方法
public void set*
(***
);
public ***
get*
();
public ***
is*
();
}
//以上兩種任意一種都行
3.>內(nèi)部類混淆處理:
-keep class com.heytap.test.MainActivity$* {
*;
}
4.>避免泛型混淆:
-keepattribute Signature
5.>帶有throws的混淆:
-keepattribute Exceptions
七庄蹋、代碼混淆方案
1.組件化混淆
在創(chuàng)建一個(gè)Android Module的時(shí)候瞬内,在Module的build.gradle,會(huì)生成 proguard-android-optimize.txt 和 proguard-rules.pro限书。
配置如下:
buildTypes {
release { minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
defaultConfig { ...
consumerProguardFiles 'consumer-rules.pro'
}
-
proguardFiles
配置的proguard文件不會(huì)被打進(jìn)aar中虫蝶,只作用于庫文件代碼,只在編譯發(fā)布aar的時(shí)候有效倦西。 -
consumerProguardFiles
配置的proguard會(huì)被打進(jìn)aar包中能真,在你將庫文件作為一個(gè)模塊添加到app模塊中后,庫文件中consumerProguardFiles
配置的proguard文件則會(huì)追加到app模塊的Proguard配置文件中扰柠,作用于整個(gè)app代碼粉铐。
1> 混淆方案1:在app模塊中管理所有的混淆規(guī)則
優(yōu)點(diǎn):所有混淆規(guī)則在app模塊的proguard-rule.pro
文件中統(tǒng)一管理
缺點(diǎn):移除某些模塊后,需手動(dòng)移除app模塊中的混淆規(guī)則卤档。理論上混淆規(guī)則添加多了不會(huì)造成崩潰或者編譯不通過蝙泼,但是會(huì)影響編譯效率
2> 混淆方案2:各個(gè)組件模塊各自管理混淆規(guī)則
優(yōu)點(diǎn):將混淆文件解耦到每個(gè)模塊中,并且不會(huì)影響編譯效率
組件化混淆解耦:
我們可以將固定的第三方混淆放到common模塊的consumer-rules.pro
文件中劝枣,每個(gè)模塊獨(dú)有的第三方引用庫混淆放到各自的consumer-rules.pro
文件中踱承,在app模塊的proguard-rule.pro
文件中放入Android通用的混淆聲明,如四大組件和全局的混淆等配置哨免。這樣可以最大限度的完成混淆解耦操作茎活。
2.SDK代碼混淆
SDK代碼的混淆方案和組件化混淆方案大致相同,sdk代碼一般會(huì)經(jīng)過兩次混淆過程:
1> 內(nèi)部混淆
主要是將相關(guān)核心代碼混淆琢唾,將對外暴露的類keep住载荔,混淆配置文件寫proguardFiles
配置文件中。
2>外部混淆
當(dāng)外部依賴我們的SDK時(shí)采桃,開啟混淆后懒熙,也會(huì)使得SDK內(nèi)部的類被混淆丘损,可能會(huì)導(dǎo)致反射調(diào)用報(bào)ClassNotFoundException異常,所以我們需要將這些需要反射的類keep住工扎,這些混淆配置需要寫在SDK內(nèi)部的consumerProguardFiles
配置文件中徘钥,這樣我們在外部業(yè)務(wù)方就不需要再次配置混淆文件了。
特別注意:
①我們在開發(fā)SDK的時(shí)候經(jīng)常會(huì)用到compileOnly這樣的依賴方式肢娘,通過這種依賴方式引用SDK呈础,SDK內(nèi)部consumerProguardFiles
配置的proguard也會(huì)被引入
② 在打包aar的過程中,consumerProguardFiles中的混淆配置規(guī)則不對aar混淆起作用橱健。
八而钞、混淆后奔潰調(diào)試
當(dāng)項(xiàng)目混淆代碼后,安裝release版本的apk到手機(jī)有時(shí)根本運(yùn)行不起來拘荡,有時(shí)啟動(dòng)了瞬間崩潰臼节,其實(shí)原因很簡單我們應(yīng)用的庫或者第三方j(luò)ar被混淆了導(dǎo)致無法正常調(diào)用,那怎么查找是哪些不該混淆了的被混淆了珊皿?
在Android studio 中生成release包的同時(shí) build\outputs\mapping\release文件夾下也生成了4個(gè)文件:
① configuration.txt :總的混淆規(guī)則网缝。這個(gè)文件包含了打包過程中所有混淆規(guī)則的匯總。
② mapping.txt :列出了原始的類蟋定,方法途凫,和字段名與混淆后代碼之間的映射。
③ seeds.txt :列出了未被混淆的類和成員溢吻。
④ usage.txt : 列出了從apk中刪除的代碼维费。
一般情況下,我們直接看mapping 和seeds這2個(gè)文件夾就可以了促王,在調(diào)試過程中犀盟,使用notedpad打開mapping文件,有時(shí)日志比較多蝇狼,可能將近幾萬行阅畴。
mapping文件記錄了所有的混淆前后的映射關(guān)系。比如:你安裝運(yùn)行apk迅耘,根本跑步起來贱枣。你檢查mapping會(huì)有可能發(fā)現(xiàn) 是你把不應(yīng)該混淆的第三方包給混淆了,在mapping文件的映射中第三方包 也變成abc了颤专,這里就是出錯(cuò)的根本纽哥,一一找出,在proguard-rules.pro中-dontwarn 包名栖秕,-keep class 包名 就可以順利解決春塌。幾萬行并不是讓你一行一行去看的,點(diǎn)擊對應(yīng)包的關(guān)鍵字滾動(dòng)查看。一一過濾只壳。
seeds文件夾是幫你查看是否需要混淆的類沒有被混淆俏拱,當(dāng)然都是可以修改的,查看過程中會(huì)有errormessage,搜索一下吼句,看看是否有不應(yīng)該混淆的被你混淆掉锅必。
也可以使用工具查看報(bào)錯(cuò)問題:
<SDK目錄>\tools\proguard\bin
下的proguardgui.bat腳本將Crash堆棧信息還原到混淆前的狀態(tài)。步驟如下
①雙擊打開腳本惕艳,選擇左邊的ReTrace選項(xiàng)
②選擇Mapping file文件搞隐,也就是混淆后打包后在app module/build/outputs/mapping/release
下生成的mapping.txt
③拷貝混淆后的堆棧信息
④點(diǎn)擊右下角的ReTrace!按鈕,完成Crash堆棧信息的追溯
如下圖中間部分就是追溯到的原Crash堆棧信息: