ProGuard代碼混淆詳細攻略

ProGuard簡介和工做流程

ProGuard可以經過壓縮、優化、混淆、預檢等操做,檢測並刪除未使用的類,字段,方法和屬性,分析和優化字節碼,使用簡短無心義的名稱來重命名類,字段和方法。從而使代碼更小、更高效、更難進行逆向工程。 html

2019-9-2-10-59-30.png

上圖就是ProGuard的工做流程,分別會通過四個階段:java

  1. 壓縮(Shrink):在壓縮處理這一步中,用於檢測和刪除沒有使用的類,字段,方法和屬性
  2. 優化(Optimize):在優化處理這一步中,對字節碼進行優化,而且移除無用指令
  3. 混淆(Obfuscate):在混淆處理這一步中,使用a,b,c等無心義的名稱,對類,字段和方法進行重命名
  4. 預檢(Preveirfy):在預檢這一步中,主要是在Java平臺上對處理後的代碼進行預檢

以上四個步驟都是可選的,咱們能夠經過配置腳原本決定其中的哪幾個步驟。好比咱們能夠配置只壓縮和混淆,不進行優化,不進行預檢。 ProGuard的官網有使用指導:proguard.sourceforge.net/android

PrgGuard環境配置和使用

運行PrgGuard須要如下依賴:

  • proguard.jar或者proguardgui.jar。proguardgui提供了一個簡單的配置界面(以下圖),能夠在上面進行配置,而progua.jar則是使用配置文件進行處理
  • Java運行環境

2019-9-2-11-0-18.png

如何運行ProGuard

ProGuard能夠經過命令行調用,如:web

  • java -jar proguardgui.jar:啓動圖形化配置界面
  • java -jar proguard.jar @config.file –options ...:經過配置文件進行ProGuard處理

2019-9-2-11-1-37.png

執行成功後,用jd-gui打開處理後的jar文件:算法

2019-9-2-11-1-52.png

能夠發現,類已經被混淆處理了。windows

PrgGuard配置文件使用

Entry points的概念

這裏,咱們引入Entry points的概念。Entry points是在ProGuard過程當中不會處理的類或者方法。 在Shrink的步驟中,ProGuard會遞歸遍歷,搜索使用了哪些類和成員,對於沒有使用的類和類成員,就會在壓縮階段丟棄。 接下來在Optimize階段,那些非Entry points的類、方法都會被設置爲private、static或者final,沒有使用的參數會被移除,此外,有些方法會被標記爲內聯。 在Obfuscate的步驟中,ProGuard會對非Entry points的類和方法進行重命名。bash

會用到的指令參數說明

Modifier

  • Includedescriptorclasses:通常用於保證native方法名,確保方法的參數類型不會重命名,確保方法簽名不會被改變,這樣才能跟native libraries相匹配。
  • Allowshrinking:容許壓縮
  • Allowoptimization:容許優化
  • Allowobfuscation:容許混淆名稱

Class Specifications

Class Specifications是用來描述類和方法的模板,下面是這個模板的格式:app

2019-9-2-11-2-15.png

其中,[]中的內容是可選,名稱可使用通配符,匹配構造函數、匹配成員、匹配方法,詳細請參考:proguard.sourceforge.net/manual/usag…ide

基本指令

-basedirectory directoryname:

在配置文件中出現的相對路徑均是相對於該路徑,如圖: 函數

2019-9-2-11-2-50.png

-injars class_path

指定處理的jar包(或者aars, wars, ears, zips, apks, directories)等,這個jar包裏面的類將會被ProGuard處理並寫入到輸出的jar包裏去。通常非class文件會不作任何處理直接直接複製到輸出文件中,injars能夠屢次使用,引入不一樣的須要處理的文件。 注意,該選項能夠指定一個目錄,那麼該目錄下全部文件都會被看成input file處理。

-outjars class_path

設置處理完成後的輸出文件路徑

-libraryjars class_path

指定要處理應用程序的jar(或者aars, wars, ears, zips, apks, directories),這些文件不會包含到輸出文件中。通常是指被處理文件所依賴的一些jar包,而那些jar包是不須要被處理以及寫入到輸出文件的。好比:

2019-9-2-11-6-36.png

-skipnonpubliclibraryclasses

忽略library裏面非public修飾的類。從而加快ProGuard的處理速度和下降ProGuard的使用內存。通常而言,library裏的非公開類是不能被程序使用的,忽略掉這些類能夠加快混淆速度。可是請注意,有一種特殊狀況:有些人編寫的代碼與類庫中的類在同一個包下,並且對該包的非public類進行了使用,在這種狀況下,就不能使用該選項了

–dontskipnonpubliclibraryclasses

不忽略library裏面非public修飾的類

-dontskipnonpubliclibraryclassmembers

指定不忽略非public類裏面的成員和方法。ProGuard默認會忽略類庫裏非public類裏的成員和方法,可是因爲一些3.2.5裏面的一些緣由,應用程序裏可能會用到這些,這時候就須要這個選項來指定不忽略它們。

-keepdirectories [directory_filter]

指定要保留在輸出文件內的目錄。默認狀況下,目錄會被移除。這會減小輸出文件的大小,但若是你的代碼引用到它們時可能會致使程序崩潰(如mypackage.MyCalss.class.getResource(""))。這時就須要指定-keepdirectories mypackage。-keepdirectories mydirectory匹配 mydirectory 目錄;-keepdirectories mydirectory/*匹配 mydirectory 的直接子目錄;-keepdirectorie mydirectory/**匹配全部子目錄,若是沒有指定過濾器,全部目錄會被保留。

-target version

指定被處理class文件所使用的java版本,可選的有: 1.0, 1.1, 1.2, 1.3, 1.4, 1.5 (or just 5), 1.6 (or just 6), 1.7 (or just 7), or 1.8 (or just 8).

-forceprocessing

強制輸出,即便輸出文件已是最新狀態

-keep [,modifier,...] class_specification

指定該類以及類的成員和方法爲entry points,不被ProGuard混淆

-keepclassmembers [,modifier,...] class_specification

指定類的某些成員不被混淆,注意類名仍是會被混淆,如:

2019-9-2-11-7-54.png

-keepclasseswithmembers [,modifier,...] class_specification

經過成員來指定哪些類不被混淆處理。好比能夠用來保留包含main方法的類:

2019-9-2-11-8-32.png

若是指定了多條規則,以下,那麼必須同時包含sayHello和test兩個方法的類纔會被保留

2019-9-2-11-8-47.png

-keepnames class_specification

-keepclassmembers,allowshrinking class_specification的別名,保留名稱不被混淆,但能夠被壓縮

-keepclassmembernames class_specification

-keepclasseswithmembers,allowshrinking class_specification的別名,保留名稱不被混淆,但能夠被壓縮

-keepclasseswithmembernames class_specification

-keepclasseswithmembers,allowshrinking class_specification的別名

-printseeds [filename]

把keep匹配的類和方法輸出到文件中,能夠用來驗證本身設定的規則是否生效.

-dontshrink

指定不進行壓縮.

-printusage [filename]

把沒有使用的代碼輸出到文件中,方便查看哪些代碼被壓縮丟棄了。.

-dontoptimize

指定不對輸入代碼進行優化處理。優化選項是默認打開的。

-optimizations

指定混淆是採用的算法,後面的參數是一個過濾器,這個過濾器是谷歌推薦的算法,通常不作更改

-optimizationpasses n

指定優化的級別,在0-7之間,默認爲5.

-assumenosideeffects class_specification

能夠指定移除哪些方法沒有反作用,如在android開發中,如想在release版本能夠把全部log輸出都移除,能夠配置:

2019-9-2-11-9-17.png

那麼全部log代碼將會在優化階段被去除。

–dontobfuscate

指定不進行混淆

-printmapping [filename]

生成map文件,記錄混淆先後的名稱對應關係,注意,這個比較重要,由於混淆後運行的名稱會變得不可讀,只有依靠這個map文件來還原。

-applymapping filename

主要是用來維持兩次混淆公用一份mapping,確保相同的代碼先後兩次混淆後是同樣的命名

-obfuscationdictionary filename

指定外部模糊字典

-classobfuscationdictionary filename

指定class模糊字典.

-packageobfuscationdictionary filename

指定package模糊字典

–useuniqueclassmembernames

類和成員混淆的時候,使用惟一的名字

-dontusemixedcaseclassnames

不使用大小寫混合類名,注意,windows用戶必須爲ProGuard指定該選項,由於windows對文件的大小寫是不敏感的,也就是好比a.java和A.java會認爲是同一個文件。若是不這樣作而且你的項目中有超過26個類的話,那麼ProGuard就會默認混用大小寫文件名,致使class文件相互覆蓋。

-keeppackagenames [package_filter]

保持packagename 不混淆

-flattenpackagehierarchy [package_name]

指定從新打包,全部包重命名,這個選項會進一步模糊包名,將包裏的類混淆成n個再從新打包到一個個的package中

-repackageclasses [package_name]

將包裏的類混淆成n個再從新打包到一個統一的package中,會覆蓋flattenpackagehierarchy選項

-keepattributes [attribute_filter]

混淆時可能被移除下面這些東西,若是想保留,須要用該選項,對於通常註解處理如 -keepattributes Annotation

attribute_filter :

  • Exceptions,
  • Signature,
  • Deprecated,
  • SourceFile,
  • SourceDir,
  • LineNumberTable,
  • LocalVariableTable,
  • LocalVariableTypeTable,
  • Synthetic,
  • #EnclosingMethod,
  • RuntimeVisibleAnnotations,
  • RuntimeInvisibleAnnotations,
  • RuntimeVisibleParameterAnnotations,
  • RuntimeInvisibleParameterAnnotations,
  • AnnotationDefault.

-dontpreverify

指定不執行預檢

-verbose

把全部信息都輸出,而不只僅是輸出出錯信息

-dontnote [class_filter]

不輸出指定類的錯誤信息.

-dontwarn [class_filter]

不打印指定類的警告信息

-ignorewarnings

遇到警告的時候,忽略警告繼續執行ProGuard,不建議添加此項

-printconfiguration [filename]

輸出當前ProGuard所使用的配置

-dump [filename]

指定輸出所處理的類的結構

反射的處理

在代碼中,若是用到了反射,混淆會改變類和成員的名字,致使反射找不到相應的類或者方法,因此開發者在混淆的時候,必須把用到了反射的類保留,不進行混淆。通常而言,使用反射通常會有如下方式,能夠搜索代碼,找到相關的類,而後在混淆配置裏面進行保留:

  • Class.forName("SomeClass")
  • SomeClass.class
  • SomeClass.class.getField("someField")
  • SomeClass.class.getDeclaredField("someField")
  • SomeClass.class.getMethod("someMethod", new Class[] {})
  • SomeClass.class.getMethod("someMethod", new Class[] { A.class })
  • SomeClass.class.getMethod("someMethod", new Class[] { A.class, B.class })
  • SomeClass.class.getDeclaredMethod("someMethod", new Class[] {})
  • SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class })
  • SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class, B.class })
  • AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")
  • AtomicLongFieldUpdater.newUpdater(SomeClass.class, "someField")
  • AtomicReferenceFieldUpdater.newUpdater(SomeClass.class, SomeType.class, "someField")
# reflectClass類使用了反射,保留該類
-keep class package.reflectClass { *; }
複製代碼

PrgGuard的基本使用demo

#代碼混淆壓縮比,在0~7之間,默認爲5,通常不作修改
-optimizationpasses 5
#指定混淆是採用的算法,後面的參數是一個過濾器,這個過濾器是谷歌推薦的算法,通常不作更改
-optimizations !code/simplification/cast,!field/*,!class/merging/*

#混合時不使用大小寫混合,混合後的類名爲小寫,windows下必須使用該選項
-dontusemixedcaseclassnames

#指定不去忽略非公共庫的類和成員
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers

#輸出詳細信息
-verbose
#輸出類名->混淆後類名的映射關係
-printmapping map.txt

#不作預校驗,preverify是proguard的四個步驟之一,Android不須要preverify,去掉這一步可以加快混淆速度。
-dontpreverify

#保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses

#避免混淆泛型
-keepattributes Signature

#拋出異常時保留代碼行號
-keepattributes SourceFile,LineNumberTable

#保留本地native方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

#保留枚舉類不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

#保留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();
}
複製代碼

另外,因爲Android平臺在使用混淆的時候,還要特別注意要添加如下一些配置:

#保留咱們使用的四大組件,自定義的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 * 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 public class com.android.vending.licensing.ILicensingService

#保留support下的全部類及其內部類
-keep class android.support.** {*;}

#保留R下面的資源
-keep class **.R$* {*;}

#保留在Activity中的方法參數是view的方法,
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}

#保留咱們自定義控件(繼承自View)不被混淆
-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);
}

#保留Parcelable序列化類不被混淆
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

#對於帶有回調函數的onXXEvent的,不能被混淆
-keepclassmembers class * {
    void *(**On*Event);
}

#在咱們的app中使用了webView須要進行特殊處理
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.webView, jav.lang.String);
}

# 在app中與HTML5的JavaScript的交互進行特殊處理,如
# package com.ljd.example;
#
# public class JSInterface {
# @JavascriptInterface
# public void callAndroidMethod(){
# // do something
# }
# }
#咱們須要確保這些js要調用的原生方法不可以被混淆,因而咱們須要作以下處理
-keepclassmembers class com.ljd.example.JSInterface {
    <methods>;
}

#內嵌類常常被混淆,結果在調用的時候就崩潰了,若是須要保留內嵌類,則用如下方法來保留內嵌類,如暴力MyClass裏面的內嵌類,$就是用來分割內嵌類和母體的標誌
-keep class com.test.MyClass$* {*;}
#-----------如下處理反射類---------------

複製代碼
相關文章
相關標籤/搜索