ProGuard 混淆解析

最近被keep keepnames keepclassmembers等幾個(gè)混淆規(guī)則搞得暈頭轉(zhuǎn)向,看起來雖然簡單,但用起來卻經(jīng)常跟自己預(yù)想的一樣。所以決定放棄看他人總結(jié)的博客,直接看ProGuard官方文檔,目前為止,總算是有一定了解了。

1 ProGuard簡介

通常我們都認(rèn)為ProGuard是一個(gè)代碼混淆工具,實(shí)際上其作用還不至于此,而是包括了四部分內(nèi)容:

  • shrink(刪減):刪減無用代碼,包括無用的類、無用的變量、方法等
  • optimize(優(yōu)化):優(yōu)化方法字節(jié)碼
  • obfuscate(混淆):混淆現(xiàn)有代碼
  • preverify(預(yù)校驗(yàn)):給類添加預(yù)校驗(yàn)信息,這是J2ME和Java 6及以上要求的

Proguard的整個(gè)工作過程如下圖所示:

這里寫圖片描述

了解了Proguard的四大步驟,我們才能更好地理解Proguard混淆規(guī)則。

1.1 Entry Points

entry points就是程序入口點(diǎn)。ProGuard以entry points作為代碼掃描入口,遍歷所有代碼,并最終決定哪些代碼需要被丟棄或混淆。比較典型的 entry points包括:

  • main方法
  • applets
  • midlets
  • activities

在ProGuard的不同階段中,entry points起到不同的作用:

  • shrinking: 在shrinking階段以entry points作為起點(diǎn)遞歸遍歷所有代碼,不可達(dá)的代碼會被丟棄
  • optimization:在optimization階段會對代碼進(jìn)行進(jìn)一步優(yōu)化:
    • 非入口代碼可能會被改為private、static、final
    • 未被使用參數(shù)會被刪除
    • 部分方法可能會被優(yōu)化為內(nèi)聯(lián)方法
  • obfuscation:obsfucation階段會將非入口代碼進(jìn)行混淆。被標(biāo)識為入口的代碼則會免于被混淆

2 Keep 選項(xiàng)

keep選項(xiàng)是為了在代碼混淆的過程中保留部分類及其字段不被混淆以滿足程序運(yùn)行需求。keep選項(xiàng)一共有如下6種規(guī)則:

  • keep
  • keepnames
  • keepclassmember
  • keepclassmembernames
  • keepclasseswithmembers
  • keepclasseswithmembernames

2.1 keep

keep規(guī)則用于標(biāo)識程序入口,被keep規(guī)則修飾的類及其成員會被指定為程序入口,從而免于被混淆。

2.2 keepnames

keepnames修飾的類及其成員不會被混淆,但前提是對應(yīng)的成員在shrinking類沒有被刪減掉。比如保留所有實(shí)現(xiàn)Serializable接口的類名:

-keepnames class * implements java.io.Serializable 

2.3 keepclassmembers

keepclassmembers僅保留指定的類成員不被混淆,但類名會被混淆。接著上面的例子,如果我們不僅向保留所有實(shí)現(xiàn)Seriablizable接口的類名,同時(shí)還要保留其所有的接口方法:

-keepnames class * implements java.io.Serializable 
-keepclassmembers class * implements java.io.Serializable { 
    static final long serialVersionUID; 
    private static final java.io.ObjectStreamField[] serialPersistentFields; 
    !static !transient <fields>; 
    private void writeObject(java.io.ObjectOutputStream); 
    private void readObject(java.io.ObjectInputStream); 
    java.lang.Object writeReplace(); 
    java.lang.Object readResolve(); 
} 

2.4 keepclassmembernames

keepclassmembernames保留指定類成員不被混淆,前提是相關(guān)的類成員沒有在shrinking階段被刪減。

2.5 keepclasseswithmembers

keepclasseswithmembers會保留類和類成員不被混淆,前提是對應(yīng)的類包含所有指定的類成員。keepclasseswithmembers適用于指定一批擁有功能類成員的方法,而不用一一列舉。比如保留所有又main方法的類:

-keepclasseswithmembers public class * { 
    public static void main(java.lang.String[]); 
} 

2.6 keepclasswithmembernames

keepclasseswithmembernames保留類和類成員不被混淆,前提是對應(yīng)的類包含所有指定的類成員,同時(shí)對應(yīng)的類成員在shrinking階段沒有被刪減。比如保留所有native方法:

-keepclasseswithmembernames class * { 
    native <methods>; 
} 

2.7 關(guān)系梳理

看完上述幾個(gè)規(guī)則一定有點(diǎn)暈,沒有關(guān)系,記住下面這個(gè)表就是:

Keep From being removed or renamed From being renamed
Classes and class members -keep -keepnames
Class members only -keepclassmembers -keepclassmembernames
Classes and class members, if class members present -keepclasseswithmembers keepclasseswithmembernames

每一條keep規(guī)則都應(yīng)該跟一個(gè)類說明(specification of classes and class members)。如下就是一個(gè)類說明的例子:

class * { 
    native <methods>; 
} 

類說明的規(guī)則將在下一節(jié)詳細(xì)介紹。

如果你不清楚到底該用哪個(gè)keep規(guī)則,建議直接使用keep,被keep標(biāo)明的類及其類成員不會被刪減或重命名。需要注意的是,如果僅僅指明要keep的類,而不指明其類成員:

keep class yourpackage.demo

那ProGuard僅會保留其類和無參數(shù)構(gòu)造方法不被刪減或重命名。

3 類說明(Class Specification)

類說明(class specification)是一個(gè)用于描述要keep的類及其成員的描述模板,其完整的格式如下所示:

[@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname
    [extends|implements [@annotationtype] classname]
[{
    [@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> |
                                                                      (fieldtype fieldname);
    [@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> |
                                                                                           <init>(argumenttype,...) |
                                                                                           classname(argumenttype,...) |
                                                                                           (returntype methodname(argumenttype,...));
    [@annotationtype] [[!]public|private|protected|static ... ] *;
    ...
}]

看起來好像很復(fù)雜,這是因?yàn)槠涔δ軓?qiáng)大,提供的選項(xiàng)很多,實(shí)際我們在實(shí)際使用過程中都是使用的簡化模式。

不過這里我們還是來看看完整的格式,類說明模板有很多符號,理解這些符號的作用很有必要:

  • []表示可選項(xiàng)
  • !表示非
  • | 表示或,如public|private表示要修飾的對象是publicprivate

理解了基本的符號含義,再來看這個(gè)模板就簡單些了,整個(gè)表達(dá)式分為兩部分:

  • 類描述
  • 類成員描述

3.1 類描述

類描述用于限定類本身,其對應(yīng)的是上面完整表達(dá)式的:

[@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname
    [extends|implements [@annotationtype] classname]

部分。

  • [@annotationtype]用于描述類注解(可選)
  • [[!]public|final|abstract|@ ...] 用于描述類的訪問權(quán)限
  • [!]interface|class|enum用于描述要類的類型:
    • class:可以表示任何類或接口
    • interface:僅表示接口
    • enum:枚舉
  • classname 類名
  • [extends|implements [@annotationtype] classname]用于描述繼承、實(shí)現(xiàn)關(guān)系,通常用于描述一組類,如上文中提到過的所有實(shí)現(xiàn)Serializable接口的類:`class * implements java.io.Serializable

這個(gè)類描述中所涉及到的兩個(gè)classname都支持通配符,用以指定一組類,其通配符使用說明如下:

通配符 描述
? 匹配所有的單個(gè)字符。比如mypackage.test?可以指代mypackage.test1mypackage.test2,但不能指代mypackage.test12
* 匹配任意長度的類名,但不包括分隔符.。比如mypackage.*Test*可以描述mypackage.Testmypackage.YourTestApplication。但無法描述mypackage.mysubpackage.MyTest。通常的用法是,用mypackage.*來描述mypacakge包下的所有類,但不包括其子包中的類
** 匹配任意長度的類名,包括分隔符.mypackage.**用于描述mypackage包下的所有類,也包括其子包中的類。

3.2 類成員描述

類成員描述對應(yīng)于上文中的:

[{
    [@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> |
                                                                      (fieldtype fieldname);
    [@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> |
                                                                                           <init>(argumenttype,...) |
                                                                                           classname(argumenttype,...) |
                                                                                           (returntype methodname(argumenttype,...));
    [@annotationtype] [[!]public|private|protected|static ... ] *;
    ...
}]

部分。

首先,整個(gè)類成員描述部分都是可選的,這部分不寫也是可以的,比如keep class mypackage.MyTest,這種情況下keep或保留類名和類的無參數(shù)構(gòu)造方法不被移除或混淆。

類成員描述的形式大致有三種,也就是類成員描述模板中用;分割開來的三個(gè)表達(dá)式,接下來分別講下。

3.2.1 類成員變量描述
[@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> |
                                                                      (fieldtype fieldname);

用于描述類的成員變量,[@annotationtype][[!]public|private|protected|static|volatile|transient ...]用于限定變量的注解類型和訪問權(quán)限,均為可選。而變量名的描述也有兩種方式:

  • <fields>:指代所有的變量
  • (fieldtype fieldname):指定具體的某個(gè)變量。注意,fieldtype和fieldname必須成對出現(xiàn)
3.2.2 類成員方法描述
    [@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> |
                                                                                           <init>(argumenttype,...) |
                                                                                           classname(argumenttype,...) |
                                                                                           (returntype methodname(argumenttype,...));

前半部分和類成員變量的描述一致,[@annotationtype]、[[!]public|private|protected|static|...]用于限定方法的注解類型和訪問權(quán)限。而方法名的描述有四種方式:

  • <methods>:指代所有方法
  • <init>(argumenttype,...):指代構(gòu)造方法。<init>指代所有的構(gòu)造方法,(argumenttype,...)描述方法的參數(shù)列表
  • classname(argumenttype,...):另一種指代構(gòu)造方法的方式,因?yàn)橹挥袠?gòu)造方法才沒有返回類型
  • (returntype methodname(argumenttype,...)):指代特定成員方法
3.2.3 類成員描述通配符

類成員的描述也支持通配符:

通配符 描述
% 描述任何的原型類型,如:boolean、int、但不包括void
? 描述任意的單個(gè)字符
* 匹配任意長度的類名,但不包括分隔符.
** 匹配任意長度的類名,包括分隔符.
*** 匹配所有的數(shù)據(jù)類型,(原型類型或非原型類型,數(shù)組或非數(shù)組)
... 匹配任意長度的任意類型參數(shù)列表
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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