Android代碼混淆 探索

聲明

這篇文章更多的是做一個整理,內(nèi)容來自于ProGuard官方文檔以及各種博客等,相關(guān)文章的鏈接在參考目錄里,感興趣的可以去看看。

本人關(guān)于學(xué)習(xí)代碼混淆的建議

了解基本的混淆概念和目的概念 -> 做一下簡單的代碼混淆實踐 -> 詳細了解混淆規(guī)則

目錄:

  • 一:代碼混淆是什么?
  • 二:為什么要進行代碼混淆?
  • 三:代碼混淆能保證代碼的絕對安全嗎?
  • 四:怎么進行代碼混淆?
  • 五:什么是 ProGuard
  • 六:Proguard 的作用?
  • 七:Android Studio 中怎么使用 ProGuard 進行代碼混淆
  • 八:為什么要了解混淆規(guī)則
  • 九:ProGuard 常用語法(包括 保留、壓縮、優(yōu)化、混淆)
  • 十:混淆注意事項
  • 十一:ProGuard 的輸出文件說明
  • 十二:參考

分這么多的目錄是為了 能夠全面而且循序漸進得 將 ProGuard 講清楚,同時適應(yīng)于不同水平的開發(fā)者,因為很多剛?cè)腴T的小白對一些比較基本的概念也是不清楚的,這些也是當初我剛接觸 Proguard 時困惑我的點,所以寫得比較多,你可以有選擇性得看。

一、代碼混淆是什么?

刪除無用代碼,將代碼中的各種元素,如包名、類名、函數(shù)名、變量名等改成無意義的符號,使得反編譯你apk的人無法根據(jù)名字猜測代碼的用途,這是一種加密手段。

二、為什么要進行代碼混淆?

如果代碼沒經(jīng)過混淆,發(fā)布出去后,別人只需要反編譯即可查看你的源碼,這是一種知識產(chǎn)權(quán)的保護手段。

三、代碼混淆能保證代碼的絕對安全嗎?

混淆的目的是為了加大反編譯的成本,但是并不能徹底防止反編譯

四、怎么進行代碼混淆?

使用Android Studio創(chuàng)建項目時會在項目根目錄下生成一個proguard-rules.pro文件,該文件便是指定項目混淆規(guī)則的文件,使用的時候只需要在里面加入相應(yīng)的混淆規(guī)則即可。也就是說,你在這個文件里面指定 哪些代碼需要混淆,哪些不需要

至于什么是混淆規(guī)則、混淆規(guī)則的語法、哪些需要或不需要混淆,在下面會詳細講到。

五、什么是ProGuard

ProGuard是一個開源的Java 代碼混淆器。它能且只能混淆Android項目里面的java代碼,對于Native代碼,資源文件Drawable、Xml等無法進行混淆。

  • 官方對Proguard的解釋是:

Proguard是一個集合了 文件壓縮、優(yōu)化、混淆和校驗 等功能的工具。
它通過將類名、變量名、方法名重命名為無意義的名稱實現(xiàn)混淆效果;
它檢測并刪除無用的類,變量,方法和屬性;
它優(yōu)化字節(jié)碼并刪除無用的指令;
最后它還校驗處理后的代碼。

進入app下的build.gradle,可以看到:

buildTypes {
    release {
        //buildConfigField "boolean", "LEO_DEBUG", "true" 在編譯時定義新的屬性及屬性值到BuildConfig.java中
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

其中proguardFiles getDefaultProguardFile就是制定 混淆規(guī)則 的文件,分兩部分:

(1)前一部分proguard-android.txt代表系統(tǒng)默認的android程序的混淆文件,該文件已經(jīng)包含了基本的混淆聲明(例如對枚舉、注解、Activity等的過濾),幫我們省去了很多事,這個文件在SDK/tools/proguard/proguard-android.txt下;

(2)后一部分是我們項目里的自定義的混淆文件,目錄在 app/proguard-rules.pro下,在這個文件里我們可以聲明一些我們所需要的定制的混淆規(guī)則,主要在這里處理的有 對第三方庫的混淆聲明、對實體類的混淆聲明等。

六、Proguard的作用?

壓縮(Shrink):檢測并移除代碼中無用的類、字段、方法和特性(Attribute)。
優(yōu)化(Optimize):對字節(jié)碼進行優(yōu)化,移除無用的指令。
混淆(Obfuscate):使用a,b,c,d這樣簡短而無意義的名稱,對類、字段和方法進行重命名。
預(yù)檢(Preveirfy):在Java平臺上對處理后的代碼進行預(yù)檢,確保加載的class文件是可執(zhí)行的。

七、Android Studio中怎么使用ProGuard進行代碼混淆

很簡單,只需要 修改build.grade 配置文件,將 buildTypes代碼塊中的 minifyEnabled false 改為 minifyEnabled true 即可。

注:接下來在以release模式下打包apk時便會自動運行ProGuard,此時即使你不添加自己的混淆規(guī)則,也會進行代碼混淆。

可以看看google默認的混淆文件,在 \sdk\tools\proguard\proguard-android.txt

接下來介紹混淆規(guī)則

八、為什么要了解混淆規(guī)則

其實google已經(jīng)給我們提供了很好的打包規(guī)則, 即如果我們將minifyEnabled set為true后,即使我們在proguard-rules.pro 里啥也不寫, 我們打出來的release包也是混淆好的

但是!如果我們不添加混淆規(guī)則,一般程序在build的過程中會因為出現(xiàn)問題而中斷。

如果加入一些自己的混淆規(guī)則 只需要在 proguard-rules.pro 中文件加入自己的混淆規(guī)則即可,

九、ProGuard常用語法(包括 保留、壓縮、優(yōu)化、混淆)

詳見官方文檔:https://www.guardsquare.com/en/proguard/manual/usage#classspecification

保留

libraryjars

使用該語句把你項目所有用到的jar包都聲明進來

1.libraryjars class_path 應(yīng)用的依賴包,如android-support-v4  
例如:  -libraryjars libs/universal-image-loader-1.9.0.jar

keep相關(guān)語法

表明要保留哪些 Java元素不進行混淆 (前3條指定 類或成員,后3條指定 類或成員 的名稱)

1.keep [,modifier,...] class_specification 不混淆指定的類文件和類的成員 
例如:
(1).保留某個包下面的類以及子包 -keep public class com.droidyue.com.widget.**
(2).-keep class com.czy.**//不混淆所有com.czy包下的類,** 換成具體的類名則表示不混淆某個具體的類
(3).-keep class com.clock.**{*;}//不混淆所有com.clock包下的類和類中的所有成員變量,**可以換成具體類名,*可以換成具體的字段,可參照Serialzable的混淆 

2.keepclassmembers [,modifier,...] class_specification 不混淆指定類的成員,如果此類受到保護他們會被保護得更好
例如:
(1).保留所有類中使用otto的public方法:
-keepclassmembers class ** {
@com.squareup.otto.Subscribe public *;
@com.squareup.otto.Produce public *;
}
(2).保留Contants類的BOOK_NAME屬性:
-keepclassmembers class com.example.admin.proguardsample.Constants {
 public static java.lang.String BOOK_NAME;
}

3.keepclasseswithmembers [,modifier,...] class_specification 不混淆指定的類和類的成員,但條件是所有指定的類和類成員是要存在。

4.keepnames class_specification 不混淆指定的類和類的成員的 名稱(如果他們不會壓縮步驟中刪除)

5.keepclassmembernames class_specification 不混淆指定的類的 成員的名稱 (如果他們不會壓縮步驟中刪除)

6.keepclasseswithmembernames class_specification 不混淆指定的類和類的成員 的名稱,如果所有指定的類成員出席(在壓縮步驟之后)

7.printseeds {filename} 列出類和類的成員-keep選項的清單,標準輸出到給定的文件

dontwarn

dontwarn是一個和keep可以說是形影不離,尤其是處理引入的library時.
引入的library可能存在一些無法找到的引用和其他問題,在build時可能會發(fā)出警告,如果我們不進行處理,通常會導(dǎo)致build中止.因此為了保證build繼續(xù),我們需要使用dontwarn處理這些我們無法解決的library的警告.

8.dontwarn [class_filter] 不提示warnning  
例如:關(guān)閉Twitter sdk的警告  -dontwarn com.twitter.sdk.**

壓縮

9.dontshrink 不壓縮輸入的類文件
10.printusage {filename}
11.whyareyoukeeping {class_specification}

優(yōu)化

12.dontoptimize 不優(yōu)化輸入的類文件
13.assumenosideeffects {class_specification} 優(yōu)化時假設(shè)指定的方法,沒有任何副作用,即假設(shè)調(diào)用不產(chǎn)生任何影響,在proguard代碼優(yōu)化時會將該調(diào)用remove掉。如system.out.println和Log.v等等  
14.allowaccessmodification 優(yōu)化時允許訪問并修改有修飾符的類和類的成員,

混淆

15.dontobfuscate 不混淆輸入的類文件
16.obfuscationdictionary {filename} 使用給定文件中的關(guān)鍵字作為要混淆方法的名稱
17.overloadaggressively 混淆時應(yīng)用侵入式重載
18.useuniqueclassmembernames 確定統(tǒng)一的混淆類的成員名稱來增加混淆
19.flattenpackagehierarchy {package_name} 重新包裝所有重命名的包并放在給定的單一包中
20.repackageclass {package_name} 重新包裝所有重命名的類文件中放在給定的單一包中
21.dontusemixedcaseclassnames 混淆時不會產(chǎn)生形形色色的類名
22.keepattributes {attribute_name,…} 保護給定的可選屬性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and InnerClasses.
23.renamesourcefileattribute {string} 設(shè)置源文件中給定的字符串常量

通配符匹配規(guī)則

通配符 規(guī)則
? 匹配單個字符
* 匹配類名中的任何部分,但不包含額外的包名
** 匹配類名中的任何部分,并且可以包含額外的包名
% 匹配任何基礎(chǔ)類型的類型名

詳細通配符見官方文檔
https://www.guardsquare.com/en/proguard/manual/usage#classspecification

十、混淆注意事項

1.哪些不能混淆

1.反射中使用的元素,如一些ORM框架的使用,需要保證類名、方法不變, 不然混淆后, 就反射不了

如果用到了反射需要加入 : 
-keepattributes Signature  
-keepattributes EnclosingMethod  

2.如果想讓一些bean 對象不混淆, 例如com.czy.bean 包下面的全是 Json框架生成的bean對象, 那么只需加入:

-keep class czy.**{*;}//不混淆所有的com.czy.bean包下的類和這些類的所有成員變量 

3.枚舉也不要混淆

4.四大組件不建議混淆,因為四大組件聲明必須在manifest中注冊,而混淆后類名會發(fā)生更改,此時混淆后的類名沒有在manifest注冊,這是不符合Android組件注冊機制的。(AndroidMainfest中的類不混淆,四大組件和Application的子類和Framework層下所有的類默認不會進行混淆)

5.注解不能混淆
注解在Android平臺中使用的越來越多,常用的有ButterKnife和Otto.很多場景下注解被用作在運行時反射確定一些元素的特征.

為了保證注解正常工作,我們不應(yīng)該對注解進行混淆.Android工程默認的混淆配置已經(jīng)包含了下面保留注解的配置

-keepattributes *Annotation*

6.JNI調(diào)用的java方法

7.Java的Native方法

8.JS調(diào)用Java的方法

9.WebView中JavaScript調(diào)用的方法方法不混淆

有用到WEBView的JS調(diào)用接口,需加入如下規(guī)則: 
-keepclassmembers class fqcn.of.javascript.interface.for.webview {  
   public *;  
}  
-keep class com.xxx.xxx.** { *; }//保持WEB接口不被混淆 此處xxx.xxx是自己接口的包名  

10.第三方庫不建議混淆,使用第三方開源庫或者引用其他第三方的SDK包時,需要在混淆文件中加入對應(yīng)的混淆規(guī)則(關(guān)于第三方的庫的, 一般都是看他們的官方文檔)

這里推薦兩個開源項目,里面收集了一些第三方庫的混淆規(guī)則
android-proguard-snippets
android-proguard-cn

不難理解,混淆之后,類名會變成a,b,c這種,通過包名+類名自然就會找不到該類了,自然就會出現(xiàn)ClassNotFoundException異常。這里推薦一篇文章:http://www.itnose.net/detail/6043297.html

11.Parcelable的子類和Creator靜態(tài)成員變量不混淆,否則會產(chǎn)生android.os.BadParcelableException異常

12.GSON的序列化與反序列化

使用GSON、fastjson等框架時,所寫的JSON對象類不混淆,否則無法將JSON解析成對應(yīng)的對象

13.繼承了Serializable接口的類,在反序列化的時候, 需要正確的類名等, 在Android 中大多是實現(xiàn) Parcelable來序列化的

繼承了Serializable接口的類,需要加上:

//不混淆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();  
}  

14.Layout文件引用到的自定義View

2.Log處理

我們都知道,使用Log的時候,需要用到TAG,然而TAG我們一般都會寫成:

private static final String TAG = MainActivity.class.getSimpleName()

這時候MainActivity如果被混淆的話,log輸出信息就會變成V/a:xxxxxxx,所以為了讓log輸出信息維持原狀,可以將TAG處理成固定的字符串:

private static final String TAG = "MainActivity"

正好Android Studio里面的 Live Templates 能讓你輕輕松松的聲明TAG !
關(guān)于Log處理,推薦一篇文章:https://www.zybuluo.com/shark0017/note/163330

移除一些log代碼:
移除Log類打印各個等級日志的代碼,打正式包的時候可以做為禁log使用,這里可以作為禁止log打印的功能使用,另外的一種實現(xiàn)方案是通過BuildConfig.DEBUG的變量來控制

-assumenosideeffects class android.util.Log {  
public static *** v(...);  
public static *** i(...);  
public static *** d(...);  
public static *** w(...);  
public static *** e(...);  
}  

3.Crash信息處理

代碼混淆的時候記得加上在混淆文件里面記得加上這句:
# keep住源文件以及行號
-keepattributes SourceFile,LineNumberTable

否則你將看到崩潰信息
這里推薦bugly的一篇文章: http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=26&extra=page%3D1

十一、ProGuard的輸出文件說明

混淆后,會在/build/proguard/目錄下輸出下面的文件

dump.txt 描述apk文件中所有類文件間的內(nèi)部結(jié)構(gòu)。
mapping.txt 列出了原始的類,方法,和字段名與混淆后代碼之間的映  射。
seeds.txt 列出了未被混淆的類和成員
usage.txt 列出了從apk中刪除的代碼 當我們需要處理crash log的時候,就可以通過mapping.txt的映射關(guān)系找到對應(yīng)的類,方法,字段等。方法如下:

sdk\tools\proguard\bin 目錄下有個retrace工具可以將混淆后的報錯堆棧解碼成正常的類名window下為retrace.bat,linux和mac為retrace.sh,

使用方法如下:

1.將crash log保存為yourfilename.txt
2.拿到版本發(fā)布時生成的mapping.txt
3.執(zhí)行命令retrace.bat -verbose mapping.txt yourfilename.txt

所以我們每次打包版本都需要保存最新的mapping.txt文件。如果要使用到第三方的crash統(tǒng)計平臺,比如bugly,還需要我們上傳APP版本對應(yīng)的mapping.txt.每次都要保存最新的mapping文件,那不就很麻煩?放心,gradle會幫到你,只需要在bulid.gradle加入下面的一句。每次我們編譯的時候,都會自動幫你保存mapping文件到本地的。

android {
applicationVariants.all { variant ->
    variant.outputs.each { output ->
        if (variant.getBuildType().isMinifyEnabled()) {
            variant.assemble.doLast{
                    copy {
                        from variant.mappingFile
                        into "${projectDir}/mappings"
                        rename { String fileName ->
                            "mapping-${variant.name}.txt"
                        }
                    }
            }
        }
    }
    ......
}
}

實踐記錄

混淆實體類

實體類不能混淆,需要保留setget方法。對于boolean類型的get方法為isXXX,不能遺漏。在開發(fā)的時候我們可以將所有的實體類放在一個包內(nèi),這樣我們寫一次混淆就行了。

-keep public class com.ljd.example.entity.** {
public void set*(***);
public *** get*();
public *** is*();
}

-keep class com.demo.login.bean.** { *; }
-keep class com.demo.main.bean.** { *; }

反編譯三步走:

1.下載以下三個工具:

  • apktool
    • 作用:資源文件獲取,可以提取出圖片文件和布局文件進行使用查看
  • dex2jar
    • 作用:將apk反編譯成Java源碼(classes.dex轉(zhuǎn)化成jar文件)
  • jd-gui
    • 作用:查看APK中classes.dex轉(zhuǎn)化成出的jar文件,即源碼文件

2.下載上述工具中的apktool,解壓得到3個文件:aapt.exe,apktool.batapktool.jar

將需要反編譯的APK文件放到該目錄下,打開命令行界面(運行-CMD) ,定位到apktool文件夾,輸入以下命令:

apktool.bat d -f  test.apk  test    

apktool.bat   d  -f    [apk文件 ]   [輸出文件夾])

此時test文件夾下即包含了所有資源文件

3.下載上述工具中的dex2jarjd-gui ,解壓

將要反編譯的APK后綴名改為.rar或則 .zip,并解壓,得到其中的classes.dex文件(它就是java文件編譯再通過dx工具打包而成的),將獲取到的classes.dex放到之前解壓出來的工具dex2jar-0.0.9.15 文件夾內(nèi),
在命令行下定位到dex2jar.bat所在目錄,輸入dex2jar.bat classes.dex,

在改目錄下會生成一個classes_dex2jar.jar的文件,然后打開工具jd-gui文件夾里的jd-gui.exe,之后用該工具打開之前生成的classes_dex2jar.jar文件,便可以看到源碼了

4.下面圖文解釋,對應(yīng)上面流程

進入工具`apktool`目錄下,執(zhí)行`apktool.bat d -f app-release.apk`,app-release為你的`apk`名稱
此時會生成反編譯`apk`后得到的資源文件`app-release`,你也可以在上面的命令中指定輸出文件的名稱,如`apktool.bat d -f app-release.apk test`,則會生成一個文件夾`test`來存放反編譯得到的資源文件
按照上面步驟將解壓后得到的`classes.dex`文件放到工具`dex2jar`文件夾后,`cmd`進入`dex2jar`并執(zhí)行`dex2jar.bat classes.dex`
此時`dex2jar`工具下出現(xiàn)`classes_dex2jar.jar`
進入工具`jd-gui-0.3.5.windows`下運行`jd-gui.exe`并用`jd-gui.exe`打開上面生成的`classes_dex2jar.jar`文件即可看到反編譯的項目

注:上述反編譯資料來自http://blog.csdn.net/vipzjyno1/article/details/21039349/

參考

ProGuard手冊
ProGuard手冊 Version4.7

ProGuard代碼混淆技術(shù)詳解
Android分享:代碼混淆那些事
http://blog.csdn.net/qq_35224673/article/details/52038093
http://blog.csdn.net/chen930724/article/details/49687067
http://blog.csdn.net/ljd2038/article/details/51308768 很詳細


http://www.tuicool.com/articles/vyEnu2A 含例子
http://www.itdecent.cn/p/be7ec1819d2f 含模板
http://www.itdecent.cn/p/7391f0c554be 含內(nèi)嵌類處理
常用第三方庫的混淆
常用第三方庫的混淆

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評論 25 709
  • 什么是代碼混淆 代碼混淆就是將代碼中的各種元素,如變量,方法,類和包的名字改寫成無意義的名字,增加項目反編譯后被讀...
    蝸牛家族史閱讀 5,306評論 1 4
  • 混淆(Proguard)用法 最近項目中遇到一些混淆相關(guān)的問題,由于之前對proguard了解不多,所以每次都是面...
    于曉飛93閱讀 57,151評論 38 230
  • 01 昨天跟家里打電話,我媽又習(xí)慣性地跟我嘮起了家常,什么誰家的孩子考上了一本,誰家的孩子來了錄取通知書,誰家的孩...
    余少閱讀 3,687評論 47 31
  • 姊齒秀次造假事件 一宗發(fā)生在日本的建筑舞弊案件。該案于2005年底被揭發(fā),起因于一級建筑師姊齒秀次基于個人利益而長...
    ucudrrad閱讀 212評論 0 0

友情鏈接更多精彩內(nèi)容