混淆概念
Android代碼混淆,又稱Android混淆蔬蕊,是伴隨著Android系統(tǒng)的流行而產(chǎn)生的一種APP保護技術(shù)拌滋,用于保護APP不被破解和逆向分析。
在Android的具體表現(xiàn)就是打包時雌芽,將項目里的包名授艰、類名、變量名根據(jù)混淆規(guī)則進行更改膘怕,使反編譯工具反編譯出來的代碼人難以閱讀想诅,從而達到防止被逆向破解的目的。
在Android里面,由于AndroidStudio集成了ProGuard来破,因此我們最常用篮灼,最簡單的混淆是ProGuard混淆。
ProGuard混淆主要包括有四個功能:
壓縮(Shrink):用于檢測和刪除沒有使用的類徘禁、字段诅诱、方法和屬性。
優(yōu)化(Optimize):對于字節(jié)碼進行優(yōu)化送朱,并且移除無用指令娘荡。
混淆(Obfuscate):使用a,b,c等名稱對類,字段和方法進行重命名驶沼。
預(yù)檢(Preverify):主要是在Java平臺上對處理后的代碼進行預(yù)檢炮沐。
綜上我們可以總結(jié)出混淆的兩大作用:
安全,提高了反編譯&逆向破解的難度回怜。
壓縮代碼大年,刪除無用的代碼,簡單的代碼重命名玉雾,都可以減少Apk體積翔试。
其實混淆還有另一個妙用,也是今天我們重點要說的:
不同的混淆規(guī)則可以編譯完全不同的代碼复旬,降低代碼重復(fù)率垦缅,用于制作馬甲包。
開啟混淆
在主模塊的build.gradle中添加如下代碼驹碍,即可開啟混淆壁涎。
android{
//...
buildTypes {
release {
debuggable false //是否debug
jniDebuggable false // 是否打開jniDebuggable開關(guān)
minifyEnabled true //代碼壓縮,混淆
shrinkResources true //資源壓縮
zipAlignEnabled true //壓縮優(yōu)化
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release //簽名
}
debug {
signingConfig signingConfigs.release //簽名
}
}
//...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
配置混淆開關(guān)的是minifyEnabled志秃。
proguardFiles用于指定混淆規(guī)則粹庞,自動使用默認(rèn)的混淆規(guī)則,而我們可以在proguard-rules.pro中自定義自己的混淆規(guī)則洽损。
混淆規(guī)則
常見的混淆規(guī)則應(yīng)該主要包含如下四個部分:
基本配置,設(shè)定混淆的規(guī)則等革半,基本配置是每個混淆文件必須存在的碑定。
基本的keep項,多數(shù)Android工程都需要非混淆的內(nèi)容又官,包括有四大組件延刘,AndroidX等內(nèi)容。
第三方庫sdk的混淆白名單六敬,根據(jù)對應(yīng)文檔添加即可(新版本構(gòu)建工具可以在sdk內(nèi)部添加碘赖,因此大部分sdk新版本都無須再添加)。
其他需要不混淆的內(nèi)容,包括:實體類普泡,json解析類播掷,WebView及js的調(diào)用模塊,與反射相關(guān)的類和方法撼班。
混淆的詳細規(guī)則可以參考:ProGuard的官方文檔
混淆指令的詳細解釋可以參考:混淆必知必會
混淆白名單
指定一些包名歧匈、類名、變量等不可以被混淆砰嘁。假設(shè)沒指定白名單就進行混淆打包件炉,而某某類的類名被混淆了(假設(shè)變成了a),那么可能其他引用或使用該類的類就找不到該類矮湘,說不定應(yīng)用就會因此崩潰或是導(dǎo)致相應(yīng)的功能無法使用斟冕。
比如我們通過反射調(diào)用了一個類,如果該類打包時被混淆缅阳,那么運行時必定會找不到該類i磕蛇,進而導(dǎo)致App異常。
打包效果
proguard-rules.pro中配置完常用混淆后券时,我們可以打一個release包孤里。(每次混淆修改后,打包前注意clean)
打包成功后在AndroidStudio中雙擊打開apk橘洞,點擊classes.dex就可以看到混淆后的代碼捌袜,包名,類名炸枣,方法都變成了近似于“亂碼”虏等。
打印混淆信息
混淆后雖然增強了App的安全性,上線后同時也會導(dǎo)致一些“副作用”:
日志打印信息不準(zhǔn)確
報錯信息無法定位具體位置
我們可以在proguard-rules.pro中添加如下代碼适肠,混淆后霍衫,就會在指定文件中輸入混淆信息。
這對于我們排查線上問題有很大幫助侯养,諸如bugly等異常上報平臺敦跌,都可以上傳mapping.txt幫助在錯誤信息中定位代碼。
-printseeds proguardbuild/print_seeds.txt #未混淆的類和成員
-printusage proguardbuild/print_unused.txt #列出從 apk 中刪除的代碼
-printmapping proguardbuild/print_mapping.txt #混淆前后的映射逛揩,生成映射文件
1
2
3
混淆出不同的代碼
實際上基本混淆配置完只要不去修改柠傍,相同代碼每次打包得到的代碼都是一樣的。
對于單包常規(guī)開發(fā)來說辩稽,這是沒有問題的惧笛。
但是實際上絕大多數(shù)App都需要馬甲包去增加搜索關(guān)鍵字,進而達到引流效果逞泄。
然而馬甲包上架有一個始終無法回避問題:代碼相似度患整,在審核嚴(yán)格的平臺拜效,代碼相似度過高甚至?xí)?dǎo)致主包下架,造成無法挽回的損失各谚。
降低代碼相似度紧憾,常規(guī)來說有兩個方案:
成本高&低效率的一點點改代碼。
暴力的生成垃圾代碼嘲碧,增大分母稻励,降低百分比。
如何通過混淆來解決這個問題呢愈涩?核心混淆規(guī)則如下:
指定一個文本文件望抽,其中所有有效字詞都用作混淆字段和方法名稱。
默認(rèn)情況下履婉,諸如“a”煤篙,“b”等短名稱用作混淆名稱。
使用模糊字典毁腿,您可以指定保留關(guān)鍵字的列表辑奈,或具有外來字符的標(biāo)識符,
例如: 忽略空格已烤,標(biāo)點符號鸠窗,重復(fù)字和#符號后的注釋。
注意胯究,模糊字典幾乎不改善混淆稍计。 有些編譯器可以自動替換它們,并且通過使用更簡單的名稱再次混淆裕循,可以很簡單地撤消該效果臣嚣。
最有用的是指定類文件中通常已經(jīng)存在的字符串(例如'Code'),從而減少類文件的大小剥哑。 僅適用于混淆處理硅则。
-obfuscationdictionary proguardbuild/pro_package.txt
指定一個文本文件,其中所有有效詞都用作混淆類名株婴。 與-obfuscationdictionary類似怎虫。 僅適用于混淆處理。
-classobfuscationdictionary proguardbuild/pro_class.txt
指定一個文本文件困介,其中所有有效詞都用作混淆包名稱揪垄。與-obfuscationdictionary類似。 僅適用于混淆處理逻翁。
-packageobfuscationdictionary proguardbuild/pro_func.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
簡單來說我們可以指定一個txt文件作為混淆字典,混淆過程中修改的包名捡鱼,類八回,方法都取自于該字典。只要我們每個馬甲包的字典不一致,我們自然能得到代碼相似度低的馬甲包缠诅。
你可以簡單粗暴的Copy中的別人的混淆字典:喪心病狂的Android混淆文件生成器
打包出來的效果如下:
Tips:相同的混淆字典打包出來的代碼仍是一致的溶浴,因此你每個馬甲包都應(yīng)該使用不同的混淆字典。
腳本生成
如果你有幾百個馬甲包管引,Copy別人的遲早“山窮水盡”士败。
作為程序員,這些都不應(yīng)該難到我們褥伴。Python腳本隨機生成就是啦~
import random
import string
import os
'''
腳本生成混淆字典
'''
//生產(chǎn)8000個不重復(fù)的字符串
totalNum = 8000
def main():
dir = "../app/proguardbuild"
createProRules(dir+'/pro_package.txt')
createProRules(dir+'/pro_class.txt')
createProRules(dir+'/pro_func.txt')
def createProRules(fileName):
dirName = os.path.dirname(fileName)
if not os.path.exists(dirName):
os.mkdir(dirName)
'''
生成totalNum個隨機不重復(fù)的字符串
:return:
'''
new_list = []
while 1:
value = ''.join(random.sample(string.ascii_letters +
string.digits, random.randint(1, 8))) //生成1~8位數(shù)隨機字符串
if value not in new_list:
new_list.append(value)
if len(new_list) == totalNum:
break
else:
continue
result = '\n'.join(new_list)
file = open(fileName, 'w', encoding='UTF-8')
file.write(result)
file.close()
if name == 'main':
main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
使用Python腳本生成混淆字典后谅将,打包效果如下:
至此,你再也不用效率低下的去改代碼重慢,也不用生成一堆垃圾代碼讓“強迫癥”難受饥臂。
混淆模板
我的混淆模板,僅供參考~
------------------------------基本指令區(qū)---------------------------------
-optimizationpasses 5 #指定壓縮級別
-optimizations !code/simplification/arithmetic,!field/,!class/merging/ #混淆時采用的算法
-verbose #打印混淆的詳細信息
-dontoptimize #關(guān)閉優(yōu)化
-keepattributes Annotation #保留注解中的參數(shù)
-keepattributes Annotation,InnerClasses # 保持注解
-keepattributes Signature # 避免混淆泛型, 這在JSON實體映射時非常重要
-ignorewarnings # 屏蔽警告
-keepattributes SourceFile,LineNumberTable # 拋出異常時保留代碼行號
混淆時不使用大小寫混合似踱,混淆后的類名為小寫(大小寫混淆容易導(dǎo)致class文件相互覆蓋)
-dontusemixedcaseclassnames
未混淆的類和成員
-printseeds proguardbuild/print_seeds.txt
列出從 apk 中刪除的代碼
-printusage proguardbuild/print_unused.txt
混淆前后的映射隅熙,生成映射文件
-printmapping proguardbuild/print_mapping.txt
指定一個文本文件,其中所有有效字詞都用作混淆字段和方法名稱核芽。
默認(rèn)情況下囚戚,諸如“a”,“b”等短名稱用作混淆名稱轧简。
使用模糊字典驰坊,您可以指定保留關(guān)鍵字的列表,或具有外來字符的標(biāo)識符吉懊,
例如: 忽略空格庐橙,標(biāo)點符號,重復(fù)字和#符號后的注釋借嗽。
注意态鳖,模糊字典幾乎不改善混淆。 有些編譯器可以自動替換它們恶导,并且通過使用更簡單的名稱再次混淆浆竭,可以很簡單地撤消該效果。
最有用的是指定類文件中通常已經(jīng)存在的字符串(例如'Code')惨寿,從而減少類文件的大小邦泄。 僅適用于混淆處理。
-obfuscationdictionary proguardbuild/pro_package.txt
指定一個文本文件裂垦,其中所有有效詞都用作混淆類名顺囊。 與-obfuscationdictionary類似。 僅適用于混淆處理蕉拢。
-classobfuscationdictionary proguardbuild/pro_class.txt
指定一個文本文件特碳,其中所有有效詞都用作混淆包名稱诚亚。與-obfuscationdictionary類似。 僅適用于混淆處理午乓。
-packageobfuscationdictionary proguardbuild/pro_func.txt
-------------------------------基本指令區(qū)--------------------------------
---------------------------------默認(rèn)保留區(qū)---------------------------------
繼承activity,application,service,broadcastReceiver,contentprovider....不進行混淆
-keep public class * extends android.app.Activity
-keep public class * extends androidx.fragment.app.Fragment
-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 class android.support.** {*;}
androidx 混淆
-keep class com.google.android.material.** {;}
-keep class androidx.* {;}
-keep public class * extends androidx.*
-keep interface androidx.** {;}
-keep class * implements androidx.* {
;
}
-dontwarn com.google.android.material.*
-dontnote com.google.android.material.**
-dontwarn androidx.**
-printconfiguration
-keep,allowobfuscation interface androidx.annotation.Keep
-keep @androidx.annotation.Keep class *
-keepclassmembers class * {
@androidx.annotation.Keep *;
}
不混淆View中的set() 和 get()方法 以保證屬性動畫正常工作 某個類中的某個方法不混淆
自定義View的set get方法 和 構(gòu)造方法不混淆
-keep public class * extends android.view.View{
*** get();
void set(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
這個主要是在layout 中寫的onclick方法android:onclick="onClick"站宗,不進行混淆
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
保持 Serializable 不被混淆
-keepnames class * implements java.io.Serializable
實現(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();
}
保留R文件中所有靜態(tài)字段,以保證正確找到每個資源的ID
-keepclassmembers class *.R$ {
public static <fields>;
}
-keepclassmembers class * {
void (Event);
}
保留枚舉類中的values和valueOf方法
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
保留Parcelable實現(xiàn)類中的Creator字段益愈,以保證Parcelable機制正常工作
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
不混淆包含native方法的類的類名以及native方法名
-keepclasseswithmembernames class * {
native<methods>;
}
避免log打印輸出
-assumenosideeffects class android.util.Log {
public static *** v(...);
public static *** d(...);
public static *** i(...);
public static *** w(...);
}
對含有反射類的處理
--------------------------------默認(rèn)保留區(qū)--------------------------------------------
----------------------------- WebView(項目中沒有可以忽略) -----------------------------
webView需要進行特殊處理
WebView
-dontwarn android.webkit.WebView
-dontwarn android.net.http.SslError
-dontwarn android.webkit.WebViewClient
-keep public class android.webkit.WebView
-keep public class android.net.http.SslError
-keep public class android.webkit.WebViewClient
在app中與HTML5的JavaScript的交互進行特殊處理
我們需要確保這些js要調(diào)用的原生方法不能夠被混淆梢灭,于是我們需要做如下處理:
-keepclassmembers class com.deepocean.tplh5.helper.JsInterfaceHelper {
<methods>;
}
----------------------------- WebView(項目中沒有可以忽略) -----------------------------
----------------------------- 實體類不可混淆 ------------------------------------------
添加實體類混淆規(guī)則
Application classes that will be serialized/deserialized over Gson
-keep class .entity. { *; }
-keep class .bean. { *; }
----------------------------- 實體類不可混淆 ------------------------------------------
----------------------------- 第三方類庫 ------------------------------------------
添加第三方類庫的混淆規(guī)則
Adjust sdk
-keep class com.adjust.sdk.*{ ; }
-keep class com.google.android.gms.common.ConnectionResult {
int SUCCESS;
}
-keep class com.google.android.gms.ads.identifier.AdvertisingIdClient {
com.google.android.gms.ads.identifier.AdvertisingIdClientInfo {
java.lang.String getId();
boolean isLimitAdTrackingEnabled();
}
-keep public class com.android.installreferrer.{ *; }
OkHttp3 去掉缺失類警告
-dontwarn org.bouncycastle.**
-dontwarn org.conscrypt.**
-dontwarn org.openjsse.javax.net.ssl.**
-dontwarn org.openjsse.net.ssl.**
----------------------------- 第三方類庫 ------------------------------------------
————————————————
版權(quán)聲明:本文為CSDN博主「DeMonnnnnn」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議蒸其,轉(zhuǎn)載請附上原文出處鏈接及本聲明敏释。
原文鏈接:https://blog.csdn.net/DeMonliuhui/article/details/128295336
混淆語法主要用于定義不需要混淆的代碼。
1.
-keep class com.example.MyClass {
public void myMethod1();
public void myMethod2();
}
上述規(guī)則會保留 com.example.MyClass 類的類名和 myMethod1() 和 myMethod2() 方法不被混淆枣接。
2.
-keep class com.thc.test.*
一顆星表示只是保持該包下的類名颂暇,而子包下的類名還是會被混淆;
3.
-keep class com.thc.test.**
兩顆星表示把本包和所含子包下的類名都保持但惶;
4.
-keep class com.thc.test.*{*;}
既可以保持該包下的類名耳鸯,又可以保持類里面的內(nèi)容不被混淆;
5.
-keep class com.thc.test.**{*;}
既可以保持該包及子包下的類名,又可以保持類里面的內(nèi)容不被混淆;
6.
-keep class com.thc.gradlestudy.MyProguardBean{
<init>; #匹配所有構(gòu)造器
<fields>;#匹配所有域
<methods>;#匹配所有方法
}
保持類中特定內(nèi)容膀曾,而不是所有的內(nèi)容
7.
-keep class com.xlpay.sqlite.cache.ProguardTest$MyClass{*;}
要保留一個類中的內(nèi)部類不被混淆需要用 $ 符號
8.
-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.view.View
使用Java的基本規(guī)則來保護特定類不被混淆县爬,比如用extends,implement等這些Java規(guī)則添谊,如下:保持Android底層組件和類不要混淆
9.
#保持ProguardTest類下test(String)方法不被混淆
-keepclassmembernames class com.xlpay.sqlite.cache.ProguardTest{
public void test(java.lang.String);
}
如果不需要保持類名财喳,只需要保持該類下的特定方法保持不被混淆,需要使用keepclassmembers斩狱,而不是keep耳高,因為keep方法會保持類名。
10.
#保持native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
jni方法不可混淆所踊,因為native方法是要完整的包名類名方法名來定義的泌枪,不能修改,否則找不到秕岛;
11.
-keep class * implements Android.os.Parcelable {
# 保持Parcelable不被混淆
public static final Android.os.Parcelable$Creator *;
}
Parcelable的子類和Creator靜態(tài)成員變量不混淆碌燕,否則會產(chǎn)生Android.os.BadParcelableException異常
12.
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
使用enum類型時需要注意避免以下兩個方法混淆,因為enum類的特殊性继薛,以下兩個方法會被反射調(diào)用修壕,見第二條規(guī)則
http://www.reibang.com/p/546733072d8d
其他常用混淆設(shè)置
通用設(shè)置
# 指定代碼的壓縮級別 0 - 7(指定代碼進行迭代優(yōu)化的次數(shù),在Android里面默認(rèn)是5遏考,這條指令也只有在可以優(yōu)化時起作用慈鸠。)
-optimizationpasses 5
# 混淆時不會產(chǎn)生形形色色的類名(混淆時不使用大小寫混合類名)
-dontusemixedcaseclassnames
# 指定不去忽略非公共的庫類(不跳過library中的非public的類)
-dontskipnonpubliclibraryclasses
# 指定不去忽略包可見的庫類的成員
-dontskipnonpubliclibraryclassmembers
#不進行優(yōu)化,建議使用此選項灌具,
-dontoptimize
# 不進行預(yù)校驗,Android不需要,可加快混淆速度青团。
-dontpreverify
# 拋出異常時保留代碼行號
-keepattributes SourceFile,LineNumberTable
#打印混淆的詳細信息
-verbose