ProGuard代碼混淆詳細攻略


轉載請標明出處:http://blog.csdn.net/shensky711/article/details/52770993 本文出自: 【HansChen的博客】

ProGuard簡介和工作流程

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

這里寫圖片描述

上圖就是ProGuard的工作流程,分別會經過四個階段:

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

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

PrgGuard環境配置和使用

運行PrgGuard需要以下依賴:

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

這里寫圖片描述

如何運行ProGuard

ProGuard可以通過命令行調用,如:

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

這里寫圖片描述

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

這里寫圖片描述

可以發現,類已經被混淆處理了。

PrgGuard配置文件使用

Entry points的概念

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

會用到的指令參數說明

Modifier

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

Class Specifications

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

這里寫圖片描述

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

基本指令

-basedirectory directoryname:

在配置文件中出現的相對路徑均是相對於該路徑,如圖: 這里寫圖片描述

-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包是不需要被處理以及寫入到輸出文件的。比如: 這里寫圖片描述

-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混淆。默認只會保持類名不被混淆,如果需要保持類中的方法和成員不被混淆,需要在keep選項中做指定,如:

# 指定Keep類名不被混淆,類中的方法和成員仍然會被混淆 -keep class site.hanschen.proguard.Keep
  • 1
  • 2
  • 1
  • 2
# 指定Keep類名不被混淆,且Keep的sayHello方法和成員helloStr不被混淆 -keep class site.hanschen.proguard.Keep { public void sayHello(); private static final java.lang.String helloStr; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

-keepclassmembers [,modifier,…] class_specification

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

# 指定Keepclassmembers的sayHello方法不被混淆,注意Keepclassmembers的類名仍然會被混淆 -keepclassmembers class site.hanschen.proguard.Keepclassmembers { public void sayHello(); }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

keep選項的區別是,keepclassmembers只保持屬性不被混淆,會混淆類名

-keepclasseswithmembers [,modifier,…] class_specification

通過成員來指定哪些類不被混淆處理。保持匹配到的類的類名和指定的方法不被混淆,其他未指定的方法仍然會被混淆。可以用來保留包含main方法的類:

# 通過成員來指定哪些類的類名和成員不被混淆 -keepclasseswithmembers public class site.hanschen.proguard.keepclasseswithmembers { public static void main(java.lang.String[]); }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
# 如果指定了多條規則,如下,那么必須同時包含 sayHello 和 main 兩個方法的類才會被保留 -keepclasseswithmembers public class site.hanschen.proguard.keepclasseswithmembers { public static void main(java.lang.String[]); public void sayHello(); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

-keepnames class_specification

-keep,allowshrinking class_specification的別名,允許該類被壓縮,未被使用的元素將會在壓縮階段被移除,此選項會保持對應的類名和指定的成員不被混淆(未指定的成員依然會被混淆)。比如指定了 sayHello 這個方法

# 指定 keepnames 類名以及 sayHello 方法不被混淆,但需要注意的是,若 sayHello 方法未被使用,會在壓縮階段被移除掉 -keepnames public class site.hanschen.proguard.keepnames { public void sayHello(); }
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

-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輸出都移除,可以配置:

 # 在優化階段移除相關方法的調用 -assumenosideeffects class android.util.Log { public static boolean isLoggable(java.lang.String, int); public static int v(...); public static int i(...); public static int w(...); public static int d(...); public static int e(...); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

那么所有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$* {*;}

#-----------以下處理反射類---------------


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM