1.What and why引几?
-
What?
代碼混淆(Obfuscated code)亦稱花指令,是將計算機程序的代碼挽铁,轉(zhuǎn)換成一種功能上等價伟桅,但是難于閱讀和理解的形式的行為。
-
Why?
混淆的目的是為了加大反編譯的成本,但是并不能徹底防止反編譯.
2.How叽掘?
ProGuard由shrink楣铁、optimize、obfuscate和preverify四個步驟組成更扁,每個步驟都是可選的盖腕,需要哪些步驟都可以在腳本中配置。參見ProGuard官方介紹浓镜。
Entry Points(入口點):
為了確定哪些代碼應該被保留溃列,哪些代碼應該被移除或混淆,需要確定一個或多個Entry Point膛薛。Entry Point經(jīng)常是帶有main methods,applets,midlets的classes,它們在混淆過程中會被保留听隐。
What does each step do?
shrink: Proguard從上述EntryPoints開始遍歷搜索哪些類和類成員被使用哄啄。其他沒有被使用的類和類成員會移除雅任。
optimize: 優(yōu)化代碼,非EntryPoints類會加上private/static/final, 沒有用到的參數(shù)會被刪除咨跌,一些方法可能會變成內(nèi)聯(lián)代碼沪么。
obfuscate: 使用短又沒有語義的名字重命名非EntryPoints的類名,變量名锌半,方法名禽车。EntryPoints的名字保持不變。
preverify: 預校驗代碼是否符合Java1.6或者更高的規(guī)范(唯一一個與入口類不相關的步驟)
3.Usage
要執(zhí)行proguard,可以直接執(zhí)行命令:
java -jar proguard.jar options ...
如果有Android SDK的同學可以在{ANDROID_SDK_ROOT}/tools/proguard/lib/目錄下找到proguard.jar這個jar包刊殉⊙乘ぃ或者,也可以在{ANDROID_SDK_ROOT}/tools/proguard/bin目錄下直接使用腳本執(zhí)行命令冗澈。
我們也可以把proguard的參數(shù)寫到一個配置文件中钦勘,比如說proguard.cfg。那我們的命令可以這樣寫:
java -jar proguard.jar @proguard.cfg
這個文件也就是我們在Android Studio中經(jīng)常配置的混淆文件了亚亲。我們在編譯正式包的時候打包腳本自動幫我們執(zhí)行了這條命令彻采。通過這個腳本可以避免重復輸入?yún)?shù)。
當然捌归,我們也可以配置文件與命令行參數(shù)混用肛响,例如:
java -jar proguard.jar @proguard.cfg -verbose
AndroidStudio中開啟混淆
參考Android官方文檔
如果需要開啟混淆,在build.gradle文件中相應的BuildType下將minifyEnabled 設置為true惜索,開啟混淆會降低構(gòu)建速度特笋,因此避免在debug版本中開啟混淆。
以下是以release版本為例巾兆,開啟混淆的gradle腳本片段:
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
...
}
其中proguardFiles屬性用于定義 ProGuard 規(guī)則猎物,與上文中直接使用proguard.jar進行混淆時指定的文件選項是一個意思虎囚。
- getDefaultProguardFile(‘proguard-android.txt’) 方法可從 Android SDK tools/proguard/ 文件夾獲取默認的 ProGuard 設置。要想做進一步的代碼壓縮蔫磨,請嘗試使用位于同一位置的 proguard-android-optimize.txt 文件淘讥。它包括相同的 ProGuard 規(guī)則,但還包括其他在字節(jié)碼一級(方法內(nèi)和方法間)執(zhí)行分析的優(yōu)化堤如,以進一步減小 APK 大小和幫助提高其運行速度蒲列。
- proguard-rules.pro 文件用于添加自定義 ProGuard 規(guī)則。默認情況下搀罢,該文件位于模塊根目錄(build.gradle 文件旁)蝗岖,內(nèi)容為空。
構(gòu)建輸出
構(gòu)建時Proguard都會輸出下列文件:
- dump.txt 說明APK中所有類文件的內(nèi)部結(jié)構(gòu)
- mapping.txt 提供原始與混淆過的類榔至、方法和字段名稱之間的轉(zhuǎn)換
- seeds.txt 列出未進行混淆的類和成員
- usage.txt 列出從APK移除的代碼
這些文件保存在 <module-name>/build/outputs/mapping/release/目錄下抵赢。
每新發(fā)布一個版本,都會產(chǎn)生新的 mapping.txt文件洛退,所以要保存好相應的 mapping.txt文件瓣俯,方便解碼混淆過的stack trace。
解碼混淆過的stack trace
使用位于 <sdk-root>/tools/proguard/目錄下的retrace腳本兵怯,將混效果的stack trace 和mapping.txt作為輸入彩匕,可以使輸出已解碼的stack trace.
例如:
retrace.bat -verbose mapping.txt obfuscated_trace.txt
proguard-android.txt 解讀
不使用大小寫混寫類名,默認情況下混淆的類名可以包含大小寫字符的混合媒区,以防止在大小寫不敏感的系統(tǒng)驼仪,比如windows上出現(xiàn)問題。
-dontusemixedcaseclassnames
不忽略公共類庫
-dontskipnonpubliclibraryclasses
關閉optimize和preverify選項袜漩,因為Android的dex并不像Java虛擬機需要optimize(優(yōu)化)和previrify(預檢)兩個步驟绪爸。
-dontoptimize
-dontpreverify
指定哪個屬性不要混淆,可一次指定多個屬性
-keepattributes [attribute_filter]
通常Exceptions, Signature, Deprecated, SourceFile, SourceDir, LineNumberTable, LocalVariableTable, LocalVariableTypeTable, Synthetic, EnclosingMethod, RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations, RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations, and AnnotationDefault屬性需要被保留宙攻,根據(jù)項目具體使用情況保留池充。
這里需要特別注意的一點是,gradle默認的keepattributes屬性不全韵吨,只保留了Annotation,Signature,InnerClasses,EnclosingMethod,為了混淆之后定位csh代碼方便逾礁,我們需要在proguard_rules.pro中手動添加拋出異常時保留代碼行號,并且重命名拋出異常時的文件名稱,這樣能方便定位問題:
拋出異常時保留代碼行號
-keepattributes SourceFile,LineNumberTable
重命名拋出異常時的文件名稱
-renamesourcefileattribute SourceFile
Keep配置
***-keep [,modifier, ...] class_specification ***
指定類和類成員(變量和方法)不被混淆
指定類名不被改變
-keep public class com.google.vending.licensing.ILicensingService
指定使用了Keep注解的類和類成員都不被改變
-keep @android.support.annotation.Keep class * {*;}
-keepclassmembers
指定類成員不被混淆,類名會被混淆
eg.keep setters in views 使得animations仍然能夠工作
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
***-keepclasseswithmembers ***
指定類和類成員都不被混淆
eg.包含native方法的類名和native方法都不能被混淆霹购,如果native方法未被調(diào)用,則被移除溢陪。由于native方法與對應so庫中的方法名稱對應萍虽,方法名被混淆會導致調(diào)用出現(xiàn)問題,所以native方法不能被混淆形真。
-keepclasseswithmembernames class * {
native <methods>;
}
-keepnames
是 -keep,allowshrinking class_pecification 的簡寫杉编。指定一些類名受到保護,前提是他們在shrink這一階段沒有被去掉。也就是說沒有被入口節(jié)點直接或間接引用的類還是會被刪除邓馒。
-keepclassmembernames
與-keepclassmember相似嘶朱。保護指定的類成員,前提是這些成員在shrink階段沒有被刪除绒净。
-keepclasseswithmembernames
與-keepclasseswithmembers類似见咒。保護指定的類,如果它們沒有在shrink階段被刪除挂疆。
注意
If you specify a class, without class members, ProGuard only preserves the class and its parameterless constructor as entry points. It may still remove, optimize, or obfuscate its other class members.
以上六種keep配置類型,以names結(jié)尾的配置不保證被Keep的類或者成員不被刪除下翎,只有在obfuscation 這一階段有效缤言,如果不確定使用哪種,只需要使用不帶names結(jié)尾的Keep配置即可视事,因為不帶names的keep在shrink階段有效胆萧,可以保證被Keep的類或者屬性不被刪除。
通用Options:
-verbose 打印混淆詳細信息
-dontnote:指定不去輸出打印該類產(chǎn)生的錯誤或遺漏
-dontnote com.android.vending.licensing.ILicensingService
-dontnote android.support.**
-dontwarn:指定不去warn unresolved references和其他重要的problem
-dontwarn android.support.**
自定義混淆文件
Keep配置后面要如何寫類的信息俐东?
[@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname
[extends|implements [@annotationtype] classname]
[{
[@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> |
(fieldtype fieldname);
[@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> |
<init>(argumenttype,...) |
classname(argumenttype,...) |
(returntype methodname(argumenttype,...));
[@annotationtype] [[!]public|private|protected|static ... ] *;
...
}]
Filters
? matches any single character in a name.(匹配一個字符)
* matches any part of a name not containing the directory separator.(匹配一個名字跌穗,除了目錄分隔符外的任意部分)
** matches any part of a name, possibly containing any number of directory separators.(匹配任意名,可能包含任意路徑分隔符)
! exclude
<field> 匹配類中的所有字段
<method> 匹配類中所有的方法
<init> 匹配類中所有的構(gòu)造函數(shù)
-keep class com.lily.test.** 本包和所包含子包下的類名都保持
-keep class com.lily.test.* 保持該包下的類名
-keep class com.lily.test.** {*;} 保持包和子包的類名和里面的內(nèi)容均不被混淆
-keepclassmembers class **.R$* {
public static <fields>;
}
assumenosideeffects選項
指定一些方法被刪除也沒有影響(盡管這些方法可能有返回值),在optimize階段虏辫,如果確定這些方法的返回值沒有使用蚌吸,那么就會刪除這些方法的調(diào)用。proguard會自動的分析你的代碼砌庄,但不會分析處理類庫中的代碼羹唠。例如,可以指定System.currentTimeMillis(),這樣在optimize階段就會刪除所有的它的調(diào)用娄昆。還可以用它來刪除打印Log的調(diào)用佩微。這條配置選項只在optimizate階段有用。
注意:Only use this option if you know what you’re doing!
eg:
# 刪除代碼中Log相關的代碼
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
下面是自定義混淆文件的一個范例,四大組件,native方法萌焰,反射用到的類哺眯,一些引入的第三方庫等,都不能進行混淆:
# 代碼混淆壓縮比,在0~7之間
-optimizationpasses 5# 混合時不使用大小寫混合扒俯,混合后的類名為小寫
-dontusemixedcaseclassnames
# 指定不去忽略非公共庫的類
-dontskipnonpubliclibraryclasses
# 不做預校驗奶卓,preverify是proguard的四個步驟之一,Android不需要preverify陵珍,去掉這一步能夠加快混淆速度寝杖。
-dontpreverify
-verbose
#google推薦算法
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# 避免混淆Annotation、內(nèi)部類互纯、泛型瑟幕、匿名類
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
# 重命名拋出異常時的文件名稱
-renamesourcefileattribute SourceFile
# 拋出異常時保留代碼行號
-keepattributes SourceFile,LineNumberTable
# 處理support包
-dontnote android.support.**
-dontwarn android.support.**
# 保留四大組件,自定義的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
# 保留本地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 *;
}
#第三方jar包不被混淆
-keep class com.github.test.** {*;}
#保留自定義的Test類和類成員不被混淆
-keep class com.lily.Test {*;}
#保留自定義的xlog文件夾下面的類、類成員和方法不被混淆
-keep class com.test.xlog.** {
<fields>;
<methods>;
}
#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 *;
}
下面的Proguard的思路可以參考:5分鐘搞定android混淆
主要將自定義Proguard分成幾個區(qū)域:
#--------------------------------定制化區(qū)域------------------------------
#---------------------------------1.實體類--------------------------------
#-------------------------------------------------------------------------
#---------------------------------2.第三方包-------------------------
#-------------------------------------------------------------------------
#---------------------------------3.與js互相調(diào)用的類----------------
#-------------------------------------------------------------------------
#---------------------------------4.反射相關的類和方法-----------------
#----------------------------5.基本不用動區(qū)域(可參考上文進行區(qū)分)-------------
4.資源文件的混淆
上面講述了如何進行代碼混淆只盹,再來講講如何對資源文件進行混淆辣往。對資源文件進行混淆操作本質(zhì)上是通過修改resources.arsc(參見文末鏈接詳見resources.arsc作用及文件格式)。現(xiàn)針對兩種資源混淆方案進行簡要說明殖卑。第一種是微信的資源混淆方案站削,第二種是美團的資源混淆方案,兩篇文章中都對原理進行了詳細的闡述孵稽。
5.混淆時常見的問題解決
TroubleShooting
Error:Uncaught translation error: com.android.dex.util.ExceptionWithContext: name already added: string{"a"}
參考:
Proguard官方文檔中的一些關于Android混淆的例子