Android混淆
一、為什麽要混淆
為了避免apk在發布後被用戶通過反編譯拿到源代碼和資源文件,然後修改資源和代碼之後就變成一個新的apk。而經過混淆後的APK,即使被反編譯,也難以閱讀,註意混淆不是讓apk不能閱讀,而是加大閱讀的難度,為了避免勞動成果被竊取,也避免出現安全漏洞和隱患,所以在apk發布之前一定要進行混淆。
二、混淆的原理
Java是一種跨平臺、解釋型語言,Java源代碼編譯成的class文件中有大量包含語義的變量名、方法名的信息,很容易被反編譯為Java源代碼。為了防止這種現象,我們可以對Java字節碼進行混淆。混淆不僅能將代碼中的類名、字段、方法名變為無意義的名稱,保護代碼,也由於移除無用的類、方法,並使用簡短名稱對類、字段、方法進行重命名縮小了程序的大小。
ProGuard由shrink、optimize、obfuscate和preverify四個步驟組成,每個步驟都是可選的,需要哪些步驟都可以在腳本中配置。參見ProGuard官方介紹。
壓縮(Shrink):默認開啟,偵測並移除代碼中無用的類、字段、方法和特性,減少應用體積,並且會在優化動作執行之後再次執行(因為優化後可能會再次暴露一些未使用的類和成員)。
-dontshrink 關閉混淆
優化(Optimize):默認開啟,分析和優化字節碼,讓應用運行的更快。
-dontoptimize 關閉優化,默認混淆配置文件開始
-optimizationpasses n 表示proguard對代碼進行叠代優化的次數,Android一般為5
混淆(Obfuscate):默認開啟,使用a、b、c、d這樣簡短而無意義的名稱,對類、字段和方法進行重命名,增大反編譯難度。
-dontobfuscate 關閉混淆
上面三個步驟使代碼大小更小、更高效,也更難被逆向工程。
預檢(Preverify):在java平臺上對處理後的代碼進行預檢。
混淆流程圖:
Proguard讀入input jars(or wars,zip or directories),經過四個步驟生成處理之後的jars(or wars,ears,zips or directories),Optimization步驟可選擇多次進行。
為了確定哪些代碼應該被保留,哪些代碼應該被移除或混淆,需要確定一個或多個Entry Point。Entry Point經常是帶有main methods,applets,midlets的classes,它們在混淆過程中會被保留。
Proguard的幾個步驟如何處理Entry Points。
(1).在壓縮階段,Proguard從上述Entry Points開始遍歷搜索哪些類和類成員被使用。其他沒有被使用的類和類成員會移除。
(2).在優化階段,Proguard進一步設置非Entry Point的類和方法為private、static和final來進行優化,不使用的參數會被移除,某些方法會被標記為內聯。
(3).在混淆階段,Proguard重命名非Entry Points的類和類成員。
(4).預檢階段是唯一沒有觸及Entry Points的階段。
三、Android Studio默認的混淆方案及字段解讀
1.開啟混淆
在build.gradle文件內相應的構建類型中添加minifyEnabled true即可。
除了minifyEnable屬性外,還有用於定義ProGuard規則的proguardFiles屬性:
proguardFiles getDefaultProguardFile(‘proguard-android.txt‘), ‘proguard-rules.pro‘
官方文檔介紹:
getDefaultProguardFile(‘proguard-android.txt‘)方法可從Android SDK tools/proguard/文件夾獲取默認的ProGuard設置。要想做進一步的代碼壓縮,請嘗試使用位於同一位置的proguard-android-optimize.txt文件。它包括相同的Proguard規則,但還包括其他在字節碼一級(方法內和方法間)執行分析的優化,以進一步減少APK大小和幫助提高其運行速度。
proguard-rules.pro文件用於添加自定義Proguard規則。默認情況下,該文件位於模塊根目錄(build.gradle文件旁),內容為空。
在gradle 2.2之後,defaultProguardFile沒有使用sdk目錄下的proguard-android.txt,而是使用了gradle自帶的proguard-android.txt,不同的gradle版本帶有不同的默認混淆文件,在項目根目錄的build/intermediates/proguard-files/proguard-android.txt-2.3.1。
混淆配置文件不檢查規則是否重復,如果兩條規則沖突,則采用白名單的,比如設置了開啟優化和不優化兩個選項後,不論順序,最終都會執行不優化的操作。
2.構建輸出
構建時Proguard都會輸出下列文件:(build之後)
(1)dump.txt --- 說明APK中所有類文件的內部結構
(2)mapping.txt --- 提供原始與混淆過的類、方法和字段名稱之間的轉換
(3)seeds --- 列出未進行混淆的類和成員
(4)usage.txt --- 列出從APK移除的代碼
這些文件保存在/build/outputs/mapping/release目錄下。
3.解碼混淆過的堆棧追蹤
使用混淆後,保存好mapping文件,程序csh時通過腳本進行解碼。
retrace工具位於/tools/proguard/目錄下,解碼命令為:
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
4.默認的混淆方案及字段解讀
(1)不適用大小混寫類名
-dontusemixedcaseclassnames
默認情況下混淆的類名可以包含大小寫字符的混合
(2)不忽略公共類庫
-dontskipnonpubliclibraryclasses
指定不去忽略非public的library classes。從Proguard 4.5開始,是默認的設置。
(3)不優化指定的文件與不預檢驗
-dontoptimize
-dontpreverify
默認optimize和preverify選項是關閉的,因為Android的dex並不想Java虛擬機需要optimize(優化)和previrify(預檢)兩個步驟。
(4)指定哪個屬性不要混淆,可一次指定多個屬性
-keeppattributes [attribute_filter]
通常Exceptions,Signature,Deprecated,SourceFile,SourceDir,LineNumberTable,LocalVariableTable,LocalVariableTypeTable,Synthetic,EnclosingMethod,RuntimeVisibleAnnotations,RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotations,AnnotationDefault屬性需要被保留,根據項目具體使用情況保留。
gradle默認的keepattributes屬性不全,只保留了Annotations,Signature,InnerClasses,EnclosingMethod,為了混淆之後定位csh代碼方便,需要在proguard_rules.pro中手動添加拋出異常時保留代碼行號,並且重命名超出異常時的文件名稱,這樣能方便定位問題:
#拋出異常時保留代碼行號
-keeppattributes SourceFile,LineNumberTable
#重命名拋出異常時的文件名稱
-renamesourcefileattribute SourceFile
keep選項制定了哪些類,哪些方法不被混淆,從而保證了程序的正常運行。
keep用法有6種:
(1)-keep(names)選項 指定類和類成員(變量和方法)不被混淆
-keep [,modifier,...] class_specification
//指定類名不被改變
-keep public class com.google.vending.licensing.ILicensingService
//指定使用了Keep註解的類和類成員都不被改變
-keep @android.support.annotation.Keep class * {*;}
(2)-keepclassmembers(names) 指定類成員不被混淆,類名會被混淆
//keep setters in views 使得animations仍然能夠工作
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
(3)-keepclasseswithmembers(names) 指定類和類成員都不被混淆
-keepclasseswithmembers [,modifier,...] class_specification
//包含native方法的類名和native方法都不能被混淆,如果native方法未被調用,則被移除。由於native方法與對應so庫中的方法名稱對應,方法名被混淆會導致調用出現問題,所以native方法不能被混淆。
-keepclasseswithmembernames class * { native <methods>; }
不帶names的選項為From being removed or renames,既不會被移除或重命名,即使類或類成員未被使用。帶有names的選項為From being renamed,不會被重命名,如果是無用的類或類成員,會被移除,移除是指在壓縮(Shrinking)時是否會被刪除。
通用Options:
(1)-verbose 打印混淆詳細信息
(2)-dontnote選項:指定不去輸出打印該類產生的錯誤或遺漏
-dontnote com.android.vending.licensing.ILicensingService
-dontnote android.support.**
(3)-dontwarn選項:指定不去warn unresolved references和其他重要的problem
-dontwarn android.support.**
如上面(2)(3)所示,android.support的libraries需要保留。
四、自定義混淆文件
1.Filters
? 匹配一個字符
* 匹配一個名字,除了目錄外分隔符外的任意部分
** 匹配任意名,可能包含任意路徑分隔符
! 排除
<field> 匹配類中的所有字段
<method> 匹配類中所有的方法
<init> 匹配類中所有的構造函數
-keep class com.lily.test.** 本包和所包含子包下的類名都保持
-keep class com.lily.test.* 保持該包下的類名
-keep class com.lily.test.** {*;} 保持包和子包的類名和裏面的內容均不被混淆
如果要保留一個類中的內部類不被混淆則需要用$符號。
2.-assumenosideeffects指令:
assumeosideeffects是Optimization過程中的選項,所以為保證指令的有效,需要開啟optimization。這個指令的含義是Proguard會在optimization過程中刪除對這些方法的調用,需要註意:當你知道你在做什麽的時候才能使用它。
3.一個自定義文件
#代碼混淆壓縮比,在0~7之間 -optimizationpasses 5# #混淆時不適用大小寫混合,混合後的類名為小寫 -dontusemixedcaseclassnames #指定不去忽略非公共庫的類 -dontskipnonpubliclibraryclasses #不做預校驗,preverify是proguard的四個步驟之一,Android不需要precerify,去掉這一步能夠加快混淆速度。 -dontpreverify -verbose #google推薦算法 -optimizations !code/simplification/arithmetic,!code/simplication/cast,!field/*,!class/mergin/* #避免混淆Annotation、內部類、泛型、匿名類 -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.Application -keep public class * extends android.app.Service -keep public class * entends 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> } #保留枚舉類不被混淆 -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } #第三方jar包不被混淆 -keep class com.github.test.** {*;} #保留自定義的Test類和類成員不被混淆 -keep class class.lily.Test {*;} #保留自定義的xlog文件夾下面的類、類成員和方法不被混淆 -keep class com.text.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 * -keepclassmember class * { @android.support.annotation.Keep *; }
五、常用到的不混淆
1.jni方法不混淆
jni方法不混淆,因為方法需要和native方法保持一致。
-keepclasseswithmembernames class * { # 保持native方法不被混淆 native <methods>; }
2.反射不混淆
反射用到的類不混淆(否則混淆可能出現問題)。
-keepatrributes EnclosingMethod
3.AndroidMainfest中的類不混淆
AndroidMainfest中的類不混淆,所以四大組件和Application的子類和Framework層下所有的類默認不會進行混淆。自定義的View默認也不會被混下,所以排除自定義View,或者四大組件被混淆的規則在ndroid Studio中無需加入的,下面是兼容性比較高的規則:
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content。BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * entends android.app.backup.BackupAgentHelper
-keep public class * entends android.preference.Preference
4.JSON對象類不混淆
與服務器交互時,使用GSON、fastjson等框架解析服務端數據時,所寫的JSON對象類不混淆,否則無法將JSON解析成對應的對象。
5.第三方開源或SDK包
使用第三方開源庫或者引用其他第三方的SDK包時,如果有特別要求,也需要在混淆文件中加入對應的混淆規則。
6.WebView的JS調用的接口方法不混淆
有用到WebView的JS調用也需要保證寫的接口方法不混淆,原則和第一條一樣。
-keepclassmembers classs fqcn.of.javascript.interface.for.webview {
public *;
}
7.Parcelable的子類和Creator靜態成員變量不混淆
Parcelable的子類和Creator靜態成員變量不混淆,否則會產生Android.os.BadParcelableException異常;
-keep class * implements Android.os.Parcelable {
# 保持Parcelable不被混淆
public static final Android.os.Parcelable$Creator *;
}
8.enum類型
使用enum類型時需要註意避免以下兩個方法混淆,因為enum類的特殊性,以下兩個方法會被反射調用。
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
9.註解不混淆
-keepatrributes *Annotation*
-keep class * extends java.lang.annotation.Annotation {*;}
10.泛型不混淆
-keepattributes Signature
11.內部類不混淆
-keepattributes InnerClasses
六、第三方混淆參考規則
1.Gson
-dontwarn com.google.**
-keep class com.google.gson.** {*;}
2.otto
-keepattributes *Annotation*
-keepclassmembers class ** {
@com.squareup.Subscribe public *;
@com.squareup.otto.Produce public *;
}
3.universal-image-loader
-dontwarn com.nostra13.universalimageloader.**
-keep class com.nostra13.universalimageloader.** {*;}
4.友盟統計
-keepclassmembers class * { public <init> (org.json.JSONObject); } #友盟統計5.0.0以上SDK需要 -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } #友盟統計R.java刪除問題 -keep public class com.gdhbgh.activity.R$*{ public static final int *; }
5.OkHttp
-dontwarn com.squareup.okhttp.**
-keep class com.squareup.okhttp.** {*;}
-keep interface com.squareup.okhttp.** {*;}
-dontwarn okio.**
6.nineoldandroids
-dontwarn com.nineoldandroids.*;
-keep class com.nineoldandroids.** {*;}
7.支付寶
-keep class com.alipay.android.app.IAlixPay{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;}
-keep class com.alipay.sdk.app.PayTask{
public *;
}
-keep class com.alipay.sdk.app.AuthTask{
public *;
}
8.Socket.io
-keep class socket.io-client.
-keepclasswithmembers,allowshrinking class socket.io-client.* {*;}
-keep class io.socket.
-keepclasseswithmembers,allowshrinking class io.socket.* {*;}
9.JPUSH
-dontwarn cn.jpush.**
-keep class cn.jpush.** {*;}
# protobuf(jpush依賴)
-dontwarn com.google.**
-keep class com.google.protobuf.** {*;}
10.友盟分享
-dontwarn com.umeng.**
-dontwarn com.tencent.weibo.sdk.**
-keep public interface com.tencent.**
-keep public interface com.umeng.socialize.**
-keep public interface com.umeng.socialize.sensor.**
-keep public interface com.umeng.scrshot.**
-keep public class com.umeng.socialize.* {*;}
-keep class com.umeng.scrshot.**
-keep public class com.tencent.** {*;}
-keep class com.umeng.socialize.sensor.**
-keep class com.umeng.socialize.handler.**
-keep class com.umeng.socialize.handler.*
-keep class com.tencent.mm.sdk.modelmsg.WXMediaMessage {*;}
-keep class com.tencent.mm.sdk.modelmsg.** implements com.tencent.mm.sdk.modelmsg.WXMediaMessage$IMediaObject {*;}
-keep class im.yixin.sdk.api.YXMessage {*;}
-keep class im.yixin.sdk.api.** implements im.yixin.sdk.api.YXMessage$YXMessageData{*;}
-keep class com.tencent.** {*;}
-dontwarn com.tencent.**
-keep public class com.umeng.soexample.R$*{
public static final int *;
}
-keep class com.tencent.open.TDialog$*
-keep class com.tencent.open.TDialog$* {*;}
-keep class com.tencent.open.PKDialog
-keep class com.tencent.open.PKDialog {*;}
-keep class com.tencent.open.PKDialog$*
-keep class com.tencent.open.PKDialog$* {*;}
-keep class com.sina.** {*;}
-dontwarn com.sina.**
-keep class com.alipay.share.sdk.** {*;}
七、常見的一些問題
1.網絡層混淆
一般網絡層都不進行混淆,可以經過劃分包後直接不混淆網絡層的包:
-keep class com.xxx.xxx.http.** {*;}
2.數據模型混淆
-keep class * implements java.io.Serializable {*;}
-keepclassmembers class * implements java.io.Serializable {*;}
有時候上面的這種方式可能會導致應用卡住,沒有任何錯誤提示,所以建議采用分包模式,吧所有bean放在同一個包中,直接對該包加白名單。
-keep class com.xxx.xxx.domain.xx {*;}
3.XML映射混淆
如果遇到一些空間無法Inflate,報NullPointException,比如ListView,NavigationView等等
-keep class *.** {*;}
4.混淆規則編寫方法
如果混淆後報錯,通過retrace後找到錯誤的問題後可以直接編寫規則來去掉混淆,但是如果報的錯誤莫名其妙,而且報錯的類沒有混淆,那麽可以采用極端的方法,加入下面的規則:
-keep class *.** {*;}
這條規則表示不混淆所有類及其中所有代碼,加了這條規則之後,還不能運行表示是其他問題,例如註解,內部類等等,可以運行後,可以通過反編譯,尋找所有包名,記錄下來,吧上述規則改為:
-keep class android.** {*;}
-keep class com.** {*;}
-keep class org.** {*;}
一個個去掉檢查是否有報錯,例如查到
-keep class com.** {*;}
加了就沒有錯誤,則可以繼續一級級往下檢查。
但要註意,有時候可能是幾個包混合問題。
參考文章:
http://mp.weixin.qq.com/s/WmJyiA3fDNriw5qXuoA9MA
http://www.jianshu.com/p/7436a1a32891#
http://www.2cto.com/kf/201607/530170.html
Android混淆