ProGuard 混淆解析

最近被keep keepnames keepclassmembers等几个混淆规则搞得晕头转向,看起来虽然简单,但用起来却经常跟自己预想的一样。所以决定放弃看他人总结的博客,直接看ProGuard官方文档,目前为止,总算是有一定了解了。

1 ProGuard简介

通常我们都认为ProGuard是一个代码混淆工具,实际上其作用还不至于此,而是包括了四部分内容:

  • shrink(删减):删减无用代码,包括无用的类、无用的变量、方法等
  • optimize(优化):优化方法字节码
  • obfuscate(混淆):混淆现有代码
  • preverify(预校验):给类添加预校验信息,这是J2ME和Java 6及以上要求的

Proguard的整个工作过程如下图所示:

这里写图片描述

了解了Proguard的四大步骤,我们才能更好地理解Proguard混淆规则。

1.1 Entry Points

entry points就是程序入口点。ProGuard以entry points作为代码扫描入口,遍历所有代码,并最终决定哪些代码需要被丢弃或混淆。比较典型的 entry points包括:

  • main方法
  • applets
  • midlets
  • activities

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

  • shrinking: 在shrinking阶段以entry points作为起点递归遍历所有代码,不可达的代码会被丢弃
  • optimization:在optimization阶段会对代码进行进一步优化:
    • 非入口代码可能会被改为private、static、final
    • 未被使用参数会被删除
    • 部分方法可能会被优化为内联方法
  • obfuscation:obsfucation阶段会将非入口代码进行混淆。被标识为入口的代码则会免于被混淆

2 Keep 选项

keep选项是为了在代码混淆的过程中保留部分类及其字段不被混淆以满足程序运行需求。keep选项一共有如下6种规则:

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

2.1 keep

keep规则用于标识程序入口,被keep规则修饰的类及其成员会被指定为程序入口,从而免于被混淆。

2.2 keepnames

keepnames修饰的类及其成员不会被混淆,但前提是对应的成员在shrinking类没有被删减掉。比如保留所有实现Serializable接口的类名:

-keepnames class * implements java.io.Serializable 

2.3 keepclassmembers

keepclassmembers仅保留指定的类成员不被混淆,但类名会被混淆。接着上面的例子,如果我们不仅向保留所有实现Seriablizable接口的类名,同时还要保留其所有的接口方法:

-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保留指定类成员不被混淆,前提是相关的类成员没有在shrinking阶段被删减。

2.5 keepclasseswithmembers

keepclasseswithmembers会保留类和类成员不被混淆,前提是对应的类包含所有指定的类成员。keepclasseswithmembers适用于指定一批拥有功能类成员的方法,而不用一一列举。比如保留所有又main方法的类:

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

2.6 keepclasswithmembernames

keepclasseswithmembernames保留类和类成员不被混淆,前提是对应的类包含所有指定的类成员,同时对应的类成员在shrinking阶段没有被删减。比如保留所有native方法:

-keepclasseswithmembernames class * { 
    native <methods>; 
} 

2.7 关系梳理

看完上述几个规则一定有点晕,没有关系,记住下面这个表就是:

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规则都应该跟一个类说明(specification of classes and class members)。如下就是一个类说明的例子:

class * { 
    native <methods>; 
} 

类说明的规则将在下一节详细介绍。

如果你不清楚到底该用哪个keep规则,建议直接使用keep,被keep标明的类及其类成员不会被删减或重命名。需要注意的是,如果仅仅指明要keep的类,而不指明其类成员:

keep class yourpackage.demo

那ProGuard仅会保留其类和无参数构造方法不被删减或重命名。

3 类说明(Class Specification)

类说明(class specification)是一个用于描述要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 ... ] *;
    ...
}]

看起来好像很复杂,这是因为其功能强大,提供的选项很多,实际我们在实际使用过程中都是使用的简化模式。

不过这里我们还是来看看完整的格式,类说明模板有很多符号,理解这些符号的作用很有必要:

  • []表示可选项
  • 表示非
  • | 表示或,如public|private表示要修饰的对象是publicprivate

理解了基本的符号含义,再来看这个模板就简单些了,整个表达式分为两部分:

  • 类描述
  • 类成员描述

3.1 类描述

类描述用于限定类本身,其对应的是上面完整表达式的:

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

部分。

  • [@annotationtype]用于描述类注解(可选)
  • [[!]public|final|abstract|@ ...] 用于描述类的访问权限
  • [!]interface|class|enum用于描述要类的类型:
    • class:可以表示任何类或接口
    • interface:仅表示接口
    • enum:枚举
  • classname 类名
  • [extends|implements [@annotationtype] classname]用于描述继承、实现关系,通常用于描述一组类,如上文中提到过的所有实现Serializable接口的类:`class * implements java.io.Serializable

这个类描述中所涉及到的两个classname都支持通配符,用以指定一组类,其通配符使用说明如下:

通配符 描述
匹配所有的单个字符。比如mypackage.test?可以指代mypackage.test1mypackage.test2,但不能指代mypackage.test12
* 匹配任意长度的类名,但不包括分隔符.。比如mypackage.*Test*可以描述mypackage.Testmypackage.YourTestApplication。但无法描述mypackage.mysubpackage.MyTest。通常的用法是,用mypackage.*来描述mypacakge包下的所有类,但不包括其子包中的类
** 匹配任意长度的类名,包括分隔符.mypackage.**用于描述mypackage包下的所有类,也包括其子包中的类。

3.2 类成员描述

类成员描述对应于上文中的:

[{
    [@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 ... ] *;
    ...
}]

部分。

首先,整个类成员描述部分都是可选的,这部分不写也是可以的,比如keep class mypackage.MyTest,这种情况下keep或保留类名和类的无参数构造方法不被移除或混淆。

类成员描述的形式大致有三种,也就是类成员描述模板中用;分割开来的三个表达式,接下来分别讲下。

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

用于描述类的成员变量,[@annotationtype][[!]public|private|protected|static|volatile|transient ...]用于限定变量的注解类型和访问权限,均为可选。而变量名的描述也有两种方式:

  • <fields>:指代所有的变量
  • (fieldtype fieldname):指定具体的某个变量。注意,fieldtype和fieldname必须成对出现
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|...]用于限定方法的注解类型和访问权限。而方法名的描述有四种方式:

  • <methods>:指代所有方法
  • <init>(argumenttype,...):指代构造方法。<init>指代所有的构造方法,(argumenttype,...)描述方法的参数列表
  • classname(argumenttype,...):另一种指代构造方法的方式,因为只有构造方法才没有返回类型
  • (returntype methodname(argumenttype,...)):指代特定成员方法
3.2.3 类成员描述通配符

类成员的描述也支持通配符:

通配符 描述
% 描述任何的原型类型,如:boolean、int、但不包括void
描述任意的单个字符
* 匹配任意长度的类名,但不包括分隔符.
** 匹配任意长度的类名,包括分隔符.
*** 匹配所有的数据类型,(原型类型或非原型类型,数组或非数组)
... 匹配任意长度的任意类型参数列表
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容