博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java 混淆那些事(三):了解 ProGuard Keep 规则
阅读量:6912 次
发布时间:2019-06-27

本文共 8426 字,大约阅读时间需要 28 分钟。

本文已授权微信公众号「玉刚说」独家发布。

这篇文章是「Java 混淆那些事」的第三篇,我们来真枪真刀的干一下子,用实际行动验证了解一下 ProGuard 的 Keep 语法,这篇代码偏多,希望大家好好理解。

阅读提示:上半部分纯属个人总结,不明白请看下半部分的例子,读完了根据自己的理解实践一下。

简介 Keep 语法

那么 keep 语法有什么用呢?如果我们对外提供了一套 Library ,如果不指定代码入口点恐怕是所有代码都要被删掉了,所以我们要指定「代码入口点」,并且告诉 ProGuard 那些类名绝对不能变动,那些方法名不能变动等等。

Keep 有以下几种用法
  • -keep [,modifier,…] class_specification 匹配类名以及指定的方法或字段,为代码入口点。可以单独匹配类或者类和类成员。匹配到的类不会被混淆和删除,匹配到的类成员不会被混淆和删除,方法被当作代码入口点。
  • -keepclassmembers [,modifier,…] class_specification 匹配类名以及规则指定的方法或字段,为代码入口点。但是有个前提:就是必须在压缩阶段被保留的类才可以。
  • -keepclasseswithmembers [,modifier,…] class_specification 它和 -keep 的作用基本一致,但是规则必须完全匹配类名以及类成员才能匹配成功,写错类成员名称或写不存在类成员名称都会导致整条规则失效。

其中 modifier 为可选配置,可以指定一个或多个。 class_specification 是类和成员的模板

modifier 共有一下几个可选值,当然匹配范围和限制还是要服从 keep 规则的。
  • includecode 保证所指定的字段名称不被混淆,而类型将被混淆。只能用于字段,否则报错。
  • includedescriptorclasses 指定有返回值的方法或者字段,他们返回值的类型以及字段的类型不会被混淆,相关的类名以及包名也不会被混淆。
  • allowshrinking 缩减 -keep 的匹配范围,如果这个类和方法不是必须的那么有可能会在压缩阶段被删除。
  • allowoptimization -keep 选项中指定的代码入口点可以在优化步骤中被优化改变,但是它们可能会在优化阶段被删除。
  • allowobfuscation -keep 选项中指定的代码入口点可以会被混淆改名,但是它们不会被删除。
还有三种和上面的用法是相对应的用法
  • **-keepnames class_specification ** 就是 -keep,allowshrinking class_specification 的简写
  • **-keepclassmembernames class_specification ** 就是 -keepclassmembers,allowshrinking class_specification 的简写
  • **-keepclasseswithmembernames class_specification ** 就是 -keepclasseswithmembers,allowshrinking class_specification 的简写

实践语法

这部分是实践大家也可以跳过,根据自己的理解亲自动手操作即可,如果我有什么重要的或错误结论欢迎指正。 在 ProGuard GUI 把混淆的配置文件保存,然后使用文件编辑器直接在配置文件下面添加即可。然后在 ProGuard 读取并执行。

/*  * 测试代码结构 * src *    -> DownloadClient.java *    -> DownloadManager.java *    -> http *        -> HttpDownload.java *        -> HttpRequest.java*/// 各个文件的具体代码// DownloadClient.javapublic int status = 0;public String url;private HttpDownload httpDownload;public DownloadClient(String url) {    this.url = url;    httpDownload = new HttpDownload();}public void start() {    status = 1;    httpDownload.start();}public void stop() {    status = 2;    httpDownload.stop();}// DownloadManager.javaHttpRequest httpRequest = new HttpRequest();public HttpRequest getDownloadUrl() {      System.out.println(httpRequest.get());    return httpRequest;}    // HttpDownload.javaprivate int i=0;public void start(){    System.out.println("开始下载");    i++;}public void stop(){    System.out.println("停止下载");    i--;}        // HttpRequest.javapublic String get() {    return "请求成功";}    复制代码

实践 keep 规则

这个例子我们不使用 main 方法,只把这个小例子当做一个 SDK。

-keep 命令
//混淆脚本-keep class DownloadClient {    public java.lang.String url;    public 
(java.lang.String); public void start();}复制代码

处理效果

/*  * 代码结构 * a *    -> a.java * defpackage *    -> DownloadClient.java*/// 各个文件的具体代码// a.javaprivate int a = 0;public final void a() {    System.out.println("开始下载");    this.a++;}// DownloadClient.javaprivate int a = 0;private a b;public String url;public DownloadClient(String str) {    this.url = str;    this.b = new a();}public void start() {    this.a = 1;    this.b.a();}复制代码

这个效果很明显:

  1. keep 指定的类和类成员都没有被移除,并且没有被混淆(构造方法和 start() 方法))。
  2. 指定的字段也没有被混淆。
  3. keep 指定的方法被作为了代码入口点,调用到的相关类和方法也没有被移除,但是被混淆了。

还有几种情况,大家自己试一下。

  1. 如果规则不写任何类成员,就只会留下一个空的类文件
  2. 如果规则写了不存在的类成员,也不会有什么效果。
  3. 可以让字段名不被混淆。但是字段类型被混淆了。
  4. 可以让方法名不被混淆。但是方法返回值类型被混淆了。
-keepclasseswithmembers

这个效果和 keep 完全一样,但是稍微有点不同,大家也可以自己试一试。

  1. 如果不写任何类成员,混淆后就只会留下一个空的类文件,但是 ProGuard 会给出提示将规则改变为 -keep。
  2. 如果写了不存在的类成员,那么当前这条 -keepclasseswithmembers 规则没有任何效果。它就没有 -keep 那么佛系了。
-keepclassmembers
// 混淆脚本-keep class DownloadClient-keepclassmembers class DownloadClient {    private http.HttpDownload httpDownload;    public 
(java.lang.String); public void start();}复制代码

处理效果

/*  * 代码结构 * a *    -> a.java * defpackage *    -> DownloadManager.java*/// 各个文件的具体代码// a.javanull// DownloadManager.javaa httpRequest = new a();public a getDownloadUrl() {    System.out.println("请求成功");    return this.httpRequest;}复制代码

大家看到我这次写了一条 -keep 混淆规则,为什么呢?

因为在压缩阶段能留下来的类上 -keepclassmembers 才能有效果,否则没有效果。所以要先把相关类留下来。

我们总结一下 -keepclassmembers 的效果

  1. 作用的类必须在压缩阶段被保留 -keepclassmembers 才可以生效。
  2. 可以让类的成员不被混淆。但是字段类型被混淆了。
  3. 可以让方法名不被混淆。但是方法返回值类型被混淆了。
  4. -keepclassmembers 同样可以起到指定代码入口点的工作,虽然 a.java 是空的,但这因为是代码优化的作用。

还是几种情况,大家自己试一下。

  1. 如果写的 -keepclassmembers 规则没有写类成员,ProGuard 会给出提示改变为 -keep。
  2. 如果写的某个类成员没有匹配到就不会生效,但是其余规则的匹配到的还是会生效的,并不会像 -keepclasseswithmembers 那么霸道。

实践 modifier 规则

includecode
//混淆脚本-keep class DownloadClient{	public 
(java.lang.String); public void start();}-keep,includecode class DownloadClient { private http.HttpDownload httpDownload;}复制代码

混淆效果

/*  * 代码结构 * a *    -> a.java * defpackage *    -> DownloadClient.java*/// 各个文件的具体代码// a.javaprivate int a = 0;public final void a() {    System.out.println("开始下载");    this.a++;}// DownloadClient.javaprivate int a = 0;private String b;private a httpDownload;public DownloadClient(String str) {    this.b = str;    this.httpDownload = new a();}public void start() {    this.a = 1;    this.httpDownload.a();}复制代码

实际效果

  1. -keep 所指定的字段名称不被混淆,但是类型还是被混淆的。

动手试一试

  1. 只能做用于字段,否则报错。
includedescriptorclasses
//混淆脚本-keep class DownloadClient {    public 
(java.lang.String); public void start();}-keep,includedescriptorclasses class DownloadManager { public http.HttpRequest getDownloadUrl();}-keep,includedescriptorclasses class DownloadClient { private http.HttpDownload httpDownload;}复制代码

混淆效果

/*  * 代码结构 * http *    -> HttpRequest.java *    -> HttpDownload.java * defpackage *    -> DownloadClient.java *    -> DownloadManager.java*/// 各个文件的具体代码// HttpRequest.javapublic static String a() {    return "请求成功";}// HttpDownload.javaprivate int a = 0;public final void a() {    System.out.println("开始下载");    this.a++;}// DownloadClient.javaprivate int a = 0;private String b;private HttpDownload httpDownload;public DownloadClient(String str) {    this.b = str;    this.httpDownload = new HttpDownload();}public void start() {    this.a = 1;    this.httpDownload.a();}// DownloadManager.javaprivate HttpRequest a = new HttpRequest();public HttpRequest getDownloadUrl() {    System.out.println(HttpRequest.a());    return this.a;}复制代码

实际效果

  1. keep 所指定的字段名称不被混淆,而且字段类型也没有混淆了。
  2. keep 一个带返回类型的方法,返回值的类型也不会被混淆
allowshrinking
//混淆脚本-keep class DownloadClient {    public 
(java.lang.String); public void start();}-keep,allowshrinking class DownloadManager { public http.HttpRequest getDownloadUrl();}复制代码

混淆效果

/*  * 代码结构 * a *    -> a.java * defpackage *    -> DownloadClient.java*/// 各个文件的具体代码// a.javaprivate int a = 0;public final void a() {    System.out.println("开始下载");    this.a++;}// DownloadClient.javaprivate int a = 0;private String b;private a c;public DownloadClient(String str) {    this.b = str;    this.c = new a();}public void start() {    this.a = 1;    this.c.a();}复制代码

实际效果

  1. -keep 指令是保留相关的类,但是 DownloadManager 并没有保留下来,就是因为 allowshrinking 的作用,如果这个类和方法不是必须的那么有可能会在压缩阶段被删除。

动手试一试

  1. 如果其他代码入口点调用了该方法,才会保留,效果跟去掉 allowshrinking modifier 的效果一致。
allowoptimization
//混淆脚本-keep,allowoptimization class DownloadClient {    public 
(java.lang.String); public void start();}复制代码

混淆效果

/*  * 代码结构 * defpackage *    -> DownloadClient.java*/// 各个文件的具体代码// DownloadClient.java//空的复制代码

实际效果

  1. 我们虽然指定了代码入口点,但是我们并没有用到,所以全都优化阶段删除了。

动手试一试

  1. 如果我们指定一个其他代码入口点,并且调用了 DownloadClient 的 start() 方法,那么他就会跟没有 allowoptimization modifier 的效果一致了。例如我们的例子,如果被调用就和如下脚本效果一致。
-keep class DownloadClient {    public 
(java.lang.String); public void start();}复制代码
allowobfuscation
//混淆脚本-keep,allowobfuscation class DownloadClient {    public 
(java.lang.String); public void start();}复制代码

混淆效果

/*  * 代码结构 * a *    -> a.java * defpackage *    -> a.java*/// 各个文件的具体代码// a/a.javaprivate int a = 0;public final void a() {    System.out.println("开始下载");    this.a++;}// defpackage/a.javaprivate int a = 0;private String b;private a.a c;public a(String str) {    this.b = str;    this.c = new a.a();}public void a() {    this.a = 1;    this.c.a();}复制代码

实际效果

  1. 虽然代码入口点的代码保留了,但是名称全部都混淆了。

自己动手

  1. 如果指定没有用到的代码,那么他也会保留并且同样是被混淆的。

简述 class_specification

官方描述的类规范模板,看着 Java 代码很像。

  1. [] 标识可选。
  2. | 表示 ‘或’ 的意思只能取一个。
  3. ...表示可以有多个,简单举个例子 [[!]public|private|protected|static ... ] 可以包含 public static 这两个,从 Java 的角度理解也不难。
  4. {} 大括号是实实在在的大括号。
  5. () 就是实实在在的括号,没有什么其他意思。
  6. ! 表示 ’否‘ ,例如:!class 规则匹配表示不能是这个 class
  7. * 、 、 、 都是通配符,我们下一篇再描述。

再放一张格式化过的比较好理解的图

我对官方的规则进行格式化了一下,这样看是不是就好理解多了,如果不考虑通配符,其实就是跟 Java 正常的写法是一致的。比如我们上面例子中混淆的写法,除了有个 之外,其他都是普通的 Java 语法。

再说几条上面没有体现的规则

  1. 写类名包名必须要写全,比如 String 要写 java.lang.String。
  2. 连接内部类,比如 com.XXX 中有内部类 Builder,这种构建器的写法很常见吧,混淆规则怎么写呢?简单举个例子: ```-keep class com.XXX Builder```,

小结

到此为止 ProGuard 的 Keep 规则我们也简单的聊了一下,希望大家自己多尝试,然后总结一下每条命令的用处,方便日后使用。

转载地址:http://irwcl.baihongyu.com/

你可能感兴趣的文章
Keepalived 高可用
查看>>
Excel动态图表应用
查看>>
.NET简谈自定义事务资源管理器
查看>>
Linux-HA开源软件Heartbeat(安装篇)
查看>>
cocos2d-x游戏例子01:是男人就坚持20秒(WIN32)
查看>>
svn 备份脚本(包含mysql数据库)
查看>>
进一步理解VC中的句柄
查看>>
日志聚合与关联分析技术实例视频演示
查看>>
类的const和非const成员函数的重载
查看>>
[RHEL5企业级Linux服务攻略]--第9季 Squid服务全攻略之常规配置
查看>>
javascript:求绝对值最小的数
查看>>
WCF分布式开发步步为赢(3)WCF服务元数据交换、配置及编程开发
查看>>
通过CLR同步SQL Server和Sharepoint List数据(三)
查看>>
SharePoint下用C#代码上传文档至文档库的子文件夹中
查看>>
统计文章各种分类总数
查看>>
CheckBoxList 拓展
查看>>
MySQL 5.1升级到Percona Server 5.6.17
查看>>
彩虹图的绘制
查看>>
Eclipse 使用小结
查看>>
【MVC 4】6.SportsSore:导航
查看>>