Java 混淆那些事(五):ProGuard 其他的選項

QuincySx發表於2019-03-24

本文已授權微信公眾號「玉剛說」獨家釋出。

這篇是我們「Java 混淆那些事」第五講,其實通過前四篇大家已經能夠寫出正常的混淆規則了,這一篇是簡單的介紹一下不怎麼常用的一些命令,個人覺得重要的會單獨拿出來寫個例子。大家可以簡單看一遍用到的時候再來查或者直接去參考官方文件。

輸入輸出選項

命令 解釋
-include filename 指定其他配置檔案,可以指定多個。例如:-include proguard1.pro
@filename -include filename 的縮寫。例如:@proguard1.pro、@test/proguard2.pro
-basedirectory directoryname 配置後續出現相對路徑的基目錄。他預設的基目錄是配置檔案的目錄,如果不存在配置檔案基目錄就是工作目錄。例如:-basedirectory 'C:\test' ,可以指定多次。注:「後續」這個詞很重要
-injars class_path 指定要處理的檔案( jar 或 aars,war,ear,zips,apks 或目錄)。預設情況下,非類檔案不會進行更改。請注意如果目錄中有任何臨時類檔案(例如由 IDE 建立)不想被修改,可以過濾類路徑中的條目。可以使用多個 -injars 選項指定類路徑條目。
-outjars class_path 指定輸出檔案(jar 或 aars,wars,ear,zips,apks 或目錄)的名稱。將 -injars 選項的檔案處理完成過後輸入到指定的檔案。切記輸出檔案不能覆蓋任何輸入檔案,否則容易出錯。為了更好的可讀性,可以使用多個 -outjars 選項指定類路徑條目。如果不寫 -outjars 選項,則不會有輸出。
-libraryjars class_path 指定混淆時需要依賴的類庫檔案( jar 或 aars,wars,ear,zips,apk s或目錄)。這些 jar 裡面的檔案不會處理和輸出,可以使用多個 -libraryjars 選項指定類路徑條目。
-skipnonpubliclibraryclasses 不處理 library 裡面非 public 修飾的類。以加快處理速度並減少 ProGuard 的記憶體使用量。
-dontskipnonpubliclibraryclasses 處理 library 中非 public 類。從 4.5 版本開始這是預設配置。
-dontskipnonpubliclibraryclassmembers 處理 library 中public 的類成員。預設不處理。
-keepdirectories [directory_filter] 輸出檔案中保留指定目錄。預設情況下,目錄會被移除。這會減少輸出檔案的大小。
-target version 將類檔案 java 版本處理為指定版本。預設情況下,類檔案的版本號保持不變。例如,我們給予 Java 5 寫的專案想使用在 Java 6 的環境下,就可以通過指定 -target 更改版本號並將其預先驗證,將類檔案升級。但是降級需謹慎可能會有一些問題。
-forceprocessing 強制輸出,即使保證輸出檔案每次都是最新狀態。

壓縮選項

命令 解釋
-dontshrink 關閉壓縮功能。預設情況下,會開啟壓縮;
-printusage [filename] 把被壓縮的類和方法輸出到檔案。主要用來驗證自己的混淆規則正確不正確。
-whyareyoukeeping class_specification 列印出壓縮過程中保留了這些類檔案和類成員的具體原因。例如:-whyareyoukeeping class **Manager { *; }

混淆選項

命令 解釋
-dontobfuscate 關閉混淆功能。預設情況下,開啟混淆。
-printmapping [filename] 把重新命名的類和類成員的新、舊名稱的對映檔案輸入到指定檔案。
-applymapping filename 指定要重用的對映檔案,對映檔案中沒有的類和類成員會被混淆為新的名稱。如果程式碼結構發生根本變化,ProGuard 可能會列印出應用對映檔案導致衝突的警告。您可以通過 -useuniqueclassmembernames 在兩個混淆執行中指定選項來降低此風險。只允許一個對映檔案。僅在混淆時適用。
-obfuscationdictionary filename 指定類、方法及欄位混淆後時用的混淆字典。預設使用 ‘a’,’b’ 等短名稱作為混淆後的名稱。
-classobfuscationdictionary filename 指定類名的混淆字典。
-packageobfuscationdictionary filename 指定包名的混淆字典。
-overloadaggressively 開啟侵入性過載混淆。多個欄位及方法允許同名,只要它們的引數及返回值型別不同。該選項可使處理後的程式碼更小(及更難閱讀)。只有開啟混淆時可用。注:Dalvik 不能處理過載的靜態欄位。
-useuniqueclassmembernames 類和成員混淆的時候,使用唯一的名字。
-dontusemixedcaseclassnames 不使用大小寫混合類名,注意,windows使用者必須為 ProGuard 指定該選項,因為 windows 非大小寫敏感,輸出檔案可能將會相互覆蓋。
-keeppackagenames [package_filter] 不混淆指定的包名。有多個包名可以用逗號隔開。包名可以包含 ?、*、** 萬用字元,還可以在包名前加上 ! 否定符。只有開啟混淆時可用。如果你使用了 mypackage.MyCalss.class.getResource(""); 這些程式碼獲取類目錄的程式碼,就會出現問題。需要使用 -keeppackagenames 保留包名。
-flattenpackagehierarchy [package_name] 將所有重新命名的包重新打包到給定的單一包中。如果沒引數或字串為空,包移動到根包下。
-repackageclasses [package_name] 把所有重新命名的類重新打包到給定的單一包中。如果沒引數或字串為空,類的包會被完全移除。此選項會覆蓋該 -flattenpackagehierarchy 選項。低版本的引數名是 -defaultpackage 。
-keepattributes [attribute_filter] 保留任何可選屬性。過濾器是由逗號分隔的 JVM 及 ProGuard 支援的屬性列表。屬性名可以包含 ?、*、** 萬用字元,並且可以在屬性名前加上 ! 否定符。例如:處理庫檔案時應該加上 Exceptions,InnerClasses,Signature 屬性。同時保留 SourceFile 及 LineNumberTable 屬性使混淆後仍能獲取準確的堆疊資訊。同時如果你的程式碼有使用註解你可能會保留 annotations 屬性。只有開啟混淆時可用。
-keepparameternames 保留方法引數名稱和保留的方法型別。
-renamesourcefileattribute [string] 指定一個常量字串作為 SourceFile 屬性的值。需要被 -keepattributes 選項指定保留。只有開啟混淆時可用。
-adaptclassstrings [class_filter] 混淆與完整類名一致的字串。沒指定過濾器時,所有符合現有類的完整類名的字串常量均會混淆。只有開啟混淆時可用。
-adaptresourcefilenames [file_filter] 以混淆後的類檔案作為樣本重新命名指定的原始檔。沒指定過濾器時,所有原始檔都會重新命名。
-adaptresourcefilecontents [file_filter] 以混淆後的類檔案作為樣本混淆指定的原始檔中與完整類名一致的內容。沒指定過濾器時,所有原始檔中與完整類名一致的內容均會混淆。

優化選項

命令 解釋
-dontoptimize 關閉優化功能。預設情況下啟用優化。
-optimizations optimization_filter 指定優化的粒度規則,後面的引數是一個粒度過濾器,一般不做更改預設即可。
-optimizationpasses n 表示對你的程式碼進行迭代優化的次數。引數是整數。一般來說設定為 10 以下即可,因為優化次數多了也不會有什麼實質性的優化了,如果你有什麼特殊業務需求,按自己需求調整就好了。
-assumenosideeffects class_specification 優化階段刪除指定程式碼,比如: 刪除所有日誌 -assumenosideeffects class com.Log { \*; }
-assumenoexternalsideeffects class_specification 優化階段刪除指定程式碼,力度比 -assumenosideeffects 強,因為它可以優化引數或堆。例如,刪除日誌記錄程式碼時,如果日誌包含 String 拼接的位元組碼就可以徹底刪除了。 -assumenosideeffects 是無法在位元組碼層面刪除的。
-assumenoescapingparameters class_specification 指定不允許其引用引數轉義到堆的方法。 這些方法可以使用,修改或返回引數,但不能直接或間接地將它們儲存在任何欄位中。 例如,System.arrayCopy 方法不允許其引用引數轉義,但方法 System.setSecurityManager 不會。
-assumenoexternalreturnvalues class_specification 指定在呼叫時不返回已在堆上的引用值的方法。例如,ProcessBuilder#start 返回一個 Process 引用值,但它是一個在堆上並沒有使用的新例項。
-allowaccessmodification 優化時允許優化並修改類和類的成員的訪問修飾符。對外提供的 SDK 包需要注意此選項。
-mergeinterfacesaggressively 指定介面可以合併,即使它們的實現類未實現合併後介面的所有方法。該選項可以通過減少類的總數減少輸出檔案的大小。只有開啟優化時可用。

保持選項

其他選項之前的文章已經介紹過了,這裡只介紹之前沒說過的。

命令 解釋
-if class_specification 如果 if 指定類和類成員存在,隨後的keep選項(-keep,-keepclassmembers,...)才會進行匹配。
-printseeds [filename] 將詳盡列出由各種 -keep 選項匹配的類和類成員列表輸出到指定檔案。該列表可用於驗證是否確實找到了預期的類成員,尤其是在使用萬用字元時。例如,您可能希望列出您保留的所有應用程式或所有小程式。

預校驗選項

命令 解釋
-dontpreverify 關閉預校驗功能。預設情況下,如果類檔案針對 Java ME 或 Java 6 或更高版本,則會對其進行預驗證。對於 Java ME ,需要預驗證。對於 Java 6,預驗證是可選的,但從 Java 7開始,它是必需的。如果類檔案針對 Android 時,沒有必要,因此您可以將其關閉以減少處理時間。
-microedition 指定已處理的類檔案以 Java ME 為目標。然後,預驗證程式將新增適當的StackMap屬性,這些屬性與 Java SE 的預設 StackMapTable 屬性不同。例如,如果要處理 midlet,則需要此選項。
-android 指定已處理的類檔案以Android平臺為目標。然後ProGuard確保某些功能與 Android 相容。

常規選項

命令 解釋
-verbose 在處理期間列印更多資訊。如果程式以異常終止,則此選項將列印出整個堆疊跟蹤,而不僅僅是異常訊息。
-dontnote [class_filter] 指定不列印有關配置中潛在錯誤或遺漏的資訊,例如配置中的類名拼寫錯誤或可能缺失有用的選項。會有提示。
-dontwarn [class_filter] 指定找不到引用或其他重要問題時不列印警告資訊。例如,在某個類的引用中找不到相關類,會有警告提示。使用 dontwarn 就可以忽略提示。
-ignorewarnings 列印找不到引用或其他重要問題的警告資訊。
-printconfiguration [filename] 將已解析的整個配置輸出到指定檔案。
-dump [filename] 指定將類檔案的內部結構輸出到指定檔案。
-addconfigurationdebugging 指定用除錯語句對處理過的程式碼進行測試,這些除錯語句會列印出缺少 ProGuard 配置的建議。如果處理過的程式碼由於仍然缺少一些反射配置而崩潰,他會提示一些簡易的配置。例如,程式碼可能正在使用 GSON 庫序列化類,您可能需要對其進行一些配置。通常,您可以將控制檯中的建議 複製/貼上 到配置檔案中。注:不要在發行版本中使用此選項,因為它會將混淆資訊新增到處理過的程式碼中。

選項引數格式

上面的命令後面有很多引數格式,我們來說一說各個引數怎麼寫。 首先說一下帶 [] 的引數是可選的不是必填,而不帶 [] 的引數是必填。

引數名 作用
filename 表示具體檔名,可以是相對路徑,也可以是絕對路徑。比如 test/test111.txttest111.txt
string 表示隨便一段字串,比如 "xxx"
class_filter 表示類過濾器,具體用法參考上一章類名過濾器
class_path 表示類檔案路徑,可以是 jars, aars, wars, ears, zips, apks 或目錄,可以是相對路徑,也可以是絕對路徑。
file_filter 表示檔案過濾器,具體用法參考上一篇檔案相關的過濾器
class_specification 表示類規範是類和類成員的模板,上一篇有提到
directory_filter 表示目錄過濾器,比如 com/httpcom/* 具體用法參考上一篇檔案相關的過濾器
package_name 表示包名 com.http
package_filter 表示包名過濾器,比如 com.*
version 表示 Java 版本號可以是 1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7 (7)、1.8(8)、1.9(9)或 10 。

還有兩個指令不是一兩句話可以說明,我們單獨拿出來講。

optimization_filter

如果有多個規則,可以使用萬用字元或 ,(逗號) 隔開寫多個規則。在調整優化粒度規則的時候,希望你能夠研究明白再使用,以免出現什麼問題。 支援三個萬用字元 ? 、 * 、! ,它們代表什麼我就不多說了。

我列舉一下固定的,其他的不穩定的我就不列舉了,有需要希望大家去看看幫助文件。

規則 作用
class/marking/final 儘可能將類標記為 fianl 類。
class/unboxing/enum 儘可能將列舉型別簡化為整數常量。
class/merging/vertical 垂直合併類 ( 上下層級的類 進行合併 )。
class/merging/horizontal 水平合併類 ( 同一層級的類 進行合併 )。
field/removal/writeonly 移除只讀欄位。
field/marking/private 儘可能將欄位標記為私有。
field/propagation/value 在方法中傳遞屬性值。
method/marking/private 儘可能將方法標記為私有。
method/marking/static 儘可能將方法標記為靜態。
method/marking/final 儘可能將方法標記為 final 方法。
method/removal/parameter 刪除沒有用到的方法。
method/propagation/parameter 將方法引數的值從方法呼叫傳到呼叫的方法。
method/propagation/returnvalue 將方法返回的值從方法傳到它們的呼叫處。
method/inlining/short 合併比較短的方法。
method/inlining/unique 合併只呼叫一次的方法。
method/inlining/tailrecursion 簡化尾部遞迴呼叫。(尾遞迴轉迴圈)
code/merging 合併相同的程式碼塊。
code/simplification/variable 變數載入和儲存時,使用窺孔優化(peephole optimization)技術,一種優化技術。
code/simplification/arithmetic 對算術指令進行窺孔優化。
code/simplification/cast 對型別轉換進行窺孔優化。
code/simplification/field 對屬性載入和儲存使用窺孔優化。
code/simplification/branch 對分支指令使用窺孔優化。
code/simplification/string 對常量字串使用窺孔優化,最好使用 code/removal/variable 刪除。
code/simplification/advanced 基於控制流分析和資料流分析簡化程式碼。
code/removal/advanced 基於控制流分析和資料流分析刪除死程式碼。
code/removal/simple 基於簡單控制流分析簡化程式碼。
code/removal/variable 從本地變數中,刪除未使用的變數。
code/removal/exception 當 try-catch 中,try塊內為空時,刪除exceptions。
code/allocation/variable 在本地變數中優化變數分配。

attribute_filter

類檔案實質上定義了類,它們的欄位和方法。許多基本和非必要資料作為屬性附加到這些類,欄位和方法。例如,屬性可以包含位元組碼,原始檔名,行號表等。ProGuard 的混淆步驟刪除了執行程式碼通常不需要的屬性。如果我們需要保留就需要使用 -keepattributes 來進行保留。同樣多個使用逗號,支援三個萬用字元 ? 、 * 、!

可選的屬性
可選項 解釋
*Annotation* 註解。
SourceFile 從中編譯類檔案的原始檔的名稱。
SourceDir 從中編譯類檔案的源目錄的名稱。
InnerClasses 類及其內部類和外部類之間的關係。
EnclosingMethod 定義類的方法。
Deprecated 表示不推薦使用類,欄位或方法。
Synthetic 表示編譯器生成了類,欄位或方法。
Signature 類、欄位或方法的通用簽名,程式碼可以通過反射訪問此簽名。
MethodParameters 方法引數的名稱和訪問標誌。
Exceptions 指定方法可能丟擲的異常。
LineNumberTable 方法的行號。
LocalVariableTable 方法的區域性變數的名稱和型別。
LocalVariableTypeTable 方法的區域性變數的名稱和泛型型別。
RuntimeVisibleAnnotations 在執行時,類,欄位和方法可見的註釋。
RuntimeInvisibleAnnotations 在編譯時對類,欄位和方法可見的註釋。
RuntimeVisibleParameterAnnotations 方法引數在執行時可見的註釋。
RuntimeInvisibleParameterAnnotations 方法引數在編譯時可見的註釋。
RuntimeVisibleTypeAnnotations 在執行時可見的註釋,用於泛型型別,指令等。
RuntimeInvisibleTypeAnnotations 在編譯時可見的註釋,用於泛型型別,指令等。
AnnotationDefault 註釋的預設值。

例子

flattenpackagehierarchy

// 混淆規則
-keep class **Manager {
    <fields>;
    <methods>;
}
-flattenpackagehierarchy "xxx"
複製程式碼

混淆前的結構

Java 混淆那些事(五):ProGuard 其他的選項

混淆後的結構

Java 混淆那些事(五):ProGuard 其他的選項

  1. 所有改變包名的都放到了 xxx 包下

repackageclasses

// 混淆規則
-keep class **Manager {
    <fields>;
    <methods>;
}
-repackageclasses "xxx"
複製程式碼

混淆前的結構

Java 混淆那些事(五):ProGuard 其他的選項

混淆後的結構

Java 混淆那些事(五):ProGuard 其他的選項

  1. 所有改變類名的類都放到了 xxx 包下

adaptclassstrings

// 混淆規則
-keep class **Manager {
    <fields>;
    <methods>;
}
-keepclassmembers,allowobfuscation class **{
	public java.lang.String get();
}
-adaptclassstrings

// 混淆前的程式碼
public String get() {    
    String ss1 = "com.test.http.HttpRequest";
    return "請求成功 "+ss1;
}

// 混淆後的程式碼
public String a() {
    return "請求成功 " + "com.test.a.a";
}
複製程式碼
  1. 混淆與類名相同的字串,用途大家可以想一想,比如反射的時候可以用。Class.forName("com.test.http.HttpRequest")

adaptresourcefilenames

// 混淆規則
-keep class **Manager {
    <fields>;
    <methods>;
}
-adaptresourcefilenames
複製程式碼

混淆前的資原始檔

Java 混淆那些事(五):ProGuard 其他的選項

混淆後的資原始檔

Java 混淆那些事(五):ProGuard 其他的選項

  1. 混淆與類名相同的資原始檔

renamesourcefileattribute

// 混淆指令碼
-keepattributes SourceFile
-renamesourcefileattribute "renamesourcefileattribute Test"

// 程式碼
/* compiled from: renamesourcefileattribute Test */
public final class a {
}
複製程式碼

發現保留了 SourceFile 屬性,並且指定了一個字串。

adaptresourcefilecontents

// 混淆指令碼
-adaptresourcefilecontents

//混淆前的資原始檔
test:com.test.http.HttpRequest

//混淆後的資原始檔
test:com.test.a.a
複製程式碼
  1. 大家可以想想用途,比如 Android 中想混淆 Activity 就需要修改清單檔案類路徑,這個屬性就可以自動修改了。

小結

這一篇內容比較多大家過一遍就好,如果你自己覺得還有其他能用到的選項就需要自己動手去試一試效果了。

相關文章