- 本文已授權(quán)微信公眾號(hào):鴻洋【hongyangAndroid】獨(dú)家發(fā)布
2018年第一篇强衡,新年快樂端盆!
一闪幽、混淆的目的
一款發(fā)布到市場(chǎng)的軟件原則上都應(yīng)該做代碼混淆料皇,可能有人會(huì)說(shuō)誰(shuí)有功夫破解你的爛代碼谓松,這個(gè)嘛,開心就好......
通過(guò)代碼混淆可以將項(xiàng)目中的類践剂、方法鬼譬、變量等信息進(jìn)行重命名,變成一些無(wú)意義的簡(jiǎn)短名字逊脯,同時(shí)也可以移除未被使用的類优质、方法、變量等军洼。所以直觀的看巩螃,通過(guò)混淆可以提高程序的安全性,增加逆向工程的難度匕争,同時(shí)也有效縮減了apk
的體積避乏。一起來(lái)get這個(gè)技能吧!
二甘桑、開啟混淆
在基于Android Studio
項(xiàng)目的app module
的build.gradle
中有如下默認(rèn)代碼片段:
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
代表要發(fā)布的release
包的混淆配置拍皮,默認(rèn)不開啟混淆,要開啟混淆首先做如下修改:
minifyEnabled true
開啟混淆后還可以添加shrinkResources true
配置跑杭,代表開啟資源文件壓縮铆帽。
這樣就在release
模式下開啟了混淆。一般在debug
模式下不開啟混淆艘蹋,因?yàn)榛煜龝?huì)導(dǎo)致編譯時(shí)間變長(zhǎng)锄贼、無(wú)法debug
問(wèn)題,畢竟也是內(nèi)部測(cè)試嘛女阀,沒必要宅荤!
開啟混淆后,接下來(lái)就是用混淆配置文件來(lái)設(shè)置混淆規(guī)則:
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
-
proguard-android.txt
代表系統(tǒng)默認(rèn)的混淆規(guī)則配置文件浸策,該文件在<Android SDK目錄>/tools/proguard
下冯键,一般不要更改該配置文件,因?yàn)橐矔?huì)作用于其它項(xiàng)目庸汗,除非你能確保所做的更改不影響其它項(xiàng)目的混淆惫确。 -
proguard-rules.pro
代碼表當(dāng)前project
的混淆配置文件,在app module
下,可以通過(guò)修改該文件來(lái)添加適用當(dāng)前項(xiàng)目的混淆規(guī)則改化。
三掩蛤、編寫混淆配置文件
為了更好的編寫proguard-rules.pro
,先學(xué)習(xí)下系統(tǒng)的proguard-android.txt
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
# 混淆時(shí)不使用大小寫混合類名
-dontusemixedcaseclassnames
# 不跳過(guò)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è)控件通過(guò)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.
# 不對(duì)android.support包下的代碼警告(如果我們打包的版本低于support包下某些類的使用版本腥泥,會(huì)出現(xiàn)警告的問(wè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)鍵字冒萄,*
、<>
等通配符的語(yǔ)法橙数,先get這些語(yǔ)法吧尊流!
- 首先看
keep
類關(guān)鍵字:
關(guān)鍵字 | 含義 |
---|---|
keep | 保留類和類成員,防止被混淆或移除 |
keepnames | 保留類和類成員灯帮,防止被混淆崖技,但沒有被引用的類成員會(huì)被移除 |
keepclassmembers | 只保留類成員,防止被混淆或移除 |
keepclassmembernames | 只保留類成員钟哥,防止被混淆迎献,但沒有被引用的成員會(huì)被移除 |
keepclasseswithmembers | 保留類和類成員,防止被混淆或移除腻贰,如果指定的類成員不存在還是會(huì)被混淆 |
keepclasseswithmembernames | 保留類和類成員吁恍,防止被混淆,如果指定的類成員不存在還是會(huì)被混淆播演,沒有被引用的類成員會(huì)被移除 |
- 相關(guān)通配符:
通配符 | 含義 |
---|---|
* | 匹配任意長(zhǎng)度字符冀瓦,但不含包名分隔符. 。例如一個(gè)類的全包名路徑是com.othershe.test.Person 写烤,使用com.othershe.test.* 翼闽、com.othershe.test.* 都是可以匹配的,但com.othershe.* 就不能匹配 |
** | 匹配任意長(zhǎng)度字符洲炊,并包含包名分隔符. 感局。例如要匹配com.othershe.test.** 包下的所有內(nèi)容 |
*** | 匹配任意參數(shù)類型尼啡。例如*** getName(***) 可匹配String getName(String)
|
... | 匹配任意長(zhǎng)度的任意類型參數(shù)。例如void setName(...) 可匹配void setName(String firstName, String secondName)
|
<fileds> | 匹配類询微、接口中所有字段 |
<methods> | 匹配類崖瞭、接口中所有方法 |
<init> | 匹配類中所有構(gòu)造函數(shù) |
到這里對(duì)混淆已經(jīng)有了基本的了解,系統(tǒng)的proguard-android.txt
已經(jīng)為我們完成了大部分基礎(chǔ)的混淆配置工作撑毛,至于編寫當(dāng)前app module
下的proguard-rules.pro
读恃,只需要針對(duì)當(dāng)前項(xiàng)目添加一些特有的配置,避免某些重要的東西被混淆掉導(dǎo)致錯(cuò)誤代态,我們主要考慮以下幾點(diǎn):
- 在
AndroidManifest.xml
中注冊(cè)的繼承四大組件的子類的類名以及重寫的方法名都不會(huì)被混淆。
如果希望項(xiàng)目中android.support.v4.app.Fragment
子類的類名和重寫父類的方法名不被混淆可以添加如下配置:
# 不混淆Fragment的子類類名以及onCreate()疹吃、onCreateView()方法名
-keep public class * extends android.support.v4.app.Fragment {
public void onCreate(android.os.Bundle);
public android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
}
- 不混淆某個(gè)特定的類和類中所有成員
-keep class com.othershe.test.utils.CommonUtil { *; }
- 不混淆某個(gè)目錄下的文件蹦疑,例如使用
Gson
時(shí),數(shù)據(jù)bean不能被混淆萨驶,需要如下配置:
# com.othershe.test.model代表數(shù)據(jù)bean所在的全包名目錄
-keep class com.othershe.test.model.** { *; }
- 上一條的具體原因是因?yàn)?code>Gson用到了反射歉摧。如果我們自己使用了反射,例如
Field field = service.getField("BASE_URL");
BASE_URL
是service所屬類的一個(gè)字段名腔呜,則該字段不能被混淆叁温。
- 保留泛型
-keepattributes Signature
- 保留用于調(diào)試堆棧跟蹤的行號(hào)信息(為了后期調(diào)試方便,建議配置)
-keepattributes SourceFile,LineNumberTable
如果使用了上一行配置核畴,還需要添加如下配置將源文件重命名為SourceFile膝但,以便通過(guò)鼠標(biāo)點(diǎn)擊直達(dá)源文件:
-renamesourcefileattribute SourceFile
-
WebView
中使用了JS
調(diào)用,需要添加如下配置:
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
- 項(xiàng)目中使用的第三方
library
混淆規(guī)則谤草,列舉了幾個(gè)常用的:
# okhttp
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Retrofit
-dontwarn okio.**
-dontwarn javax.annotation.**
-dontnote retrofit2.Platform
-dontwarn retrofit2.Platform$Java8
-keepattributes Signature
-keepattributes Exceptions
# RxJava RxAndroid
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
# Gson
-keep class com.google.gson.stream.** { *; }
-keepattributes EnclosingMethod
# xxx代表model類的全包名路徑
-keep class xxx.** { *; }
# butterknie
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}
# eventbus
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
以上這些可以按需添加到proguard-rules.pro
跟束!
四、查看混淆結(jié)果
混淆后打包丑孩,會(huì)在app module/build/outputs/mapping/release
目錄下生成如下文件(動(dòng)不動(dòng)就幾萬(wàn)行冀宴,是在沒法看):
-
dump.txt
:描述apk
文件中所有類的內(nèi)部結(jié)構(gòu) -
mapping.txt
:混淆前后的類、類成員温学、方法的對(duì)照關(guān)系(重要略贮,追溯Crash堆棧信息要用到) -
resources.txt
:資源文件的壓縮信息 -
seeds.txt
:未被混淆的類和成員 -
usage.txt
:被移除的代碼
混淆后的apk
包,需要系統(tǒng)的測(cè)試仗岖,防止混淆導(dǎo)致的潛在bug
逃延。
我們還是有必要看一下混淆后的代碼結(jié)構(gòu),驗(yàn)證混淆是否成功轧拄。一個(gè)簡(jiǎn)單的辦法真友,Android Studio
的Build
菜單下有一個(gè)Analyze APK
選項(xiàng),只需要先選擇要分析的apk
包紧帕,在之后的界面點(diǎn)擊classes.dex
即可看到混淆后的代碼結(jié)構(gòu):
但是這樣只能看到一個(gè)類的成員變量和方法的結(jié)構(gòu)盔然,如果要看一個(gè)類的具體內(nèi)容桅打,就需要反編譯
apk
包了,具體可參考Android apk反編譯及重新打包流程愈案,希望一切順利挺尾!
五、 追溯Crash堆棧信息
代碼混淆后站绪,也會(huì)導(dǎo)致Crash堆棧信息被混淆遭铺,難以閱讀,增加定位問(wèn)題位置的難度恢准,一個(gè)混淆后的Crash堆棧信息類似這樣魂挂,核心的信息都沒了:
為了解決這個(gè)問(wèn)題,可以使用<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堆棧信息:
代碼混淆常用的內(nèi)容就這些了果正,重點(diǎn)還是要理解混淆的相關(guān)語(yǔ)法,靈活運(yùn)用盟迟!混淆一定程度增加逆向工程的難度秋泳,但還是能被破解的,如果你的代碼有價(jià)值攒菠,也難免被有心的人利用迫皱!除了混淆之外,一些廠商也提供了apk
加固的服務(wù)來(lái)保證軟件的安全性辖众,但也是可以被脫殼的舍杜!有興趣的可自行了解!下次再見......