ProGuard代码混淆详细攻略

摘要:
ProGuard的官方网站有使用说明:http://proguard.sourceforge.net/PrgGuardPrgGuard的环境配置和操作需要以下依赖项:proguard.jar或proguardgui.jar。入口点是不会在proguard过程中处理的类或方法。在收缩步骤中,ProGuard将递归遍历并搜索使用的类和成员。对于未使用的类和类成员,它们将在压缩阶段被丢弃。在Obfuscate步骤中,ProGuard将重命名非Entrypoints类和方法。

转载请标明出处: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
ProGuard代码混淆详细攻略第8张
  • 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
ProGuard代码混淆详细攻略第9张
  • 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
ProGuard代码混淆详细攻略第9张
  • 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
ProGuard代码混淆详细攻略第9张
  • 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
ProGuard代码混淆详细攻略第9张
  • 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
ProGuard代码混淆详细攻略第9张
  • 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
ProGuard代码混淆详细攻略第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$* {*;}

#-----------以下处理反射类---------------

免责声明:文章转载自《ProGuard代码混淆详细攻略》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇seq2seq聊天模型(二)——Scheduled SamplingAI Accord.NET入门下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

value optimized out的问题

看redis源码,查看某个变量的值的时候出现:value optimized out 变量被编译优化掉了,看不到了。 解决方法: 在编译redis的时候,make添加参数。0表示编译的时候不对代码进行优化。 make CFLAGS='-g -O0'...

MindSpore模型精度调优实战:常用的定位精度调试调优思路

摘要:在模型的开发过程中,精度达不到预期常常让人头疼。为了帮助用户解决模型调试调优的问题,我们为MindSpore量身定做了可视化调试调优组件:MindInsight。 本文分享自华为云社区《技术干货 | 模型优化精度、速度我全都要!MindSpore模型精度调优实战(二)》,原文作者:HWCloudAI 。 引言: 在模型的开发过程中,精度达不到预期常...

synchronized实现原理及锁优化

1.引言 并发编程中synchronized是重量级锁,但随着JVM1.6对synchronized进行优化后,有些情况下它并不那么重,本文介绍了Java SE1.6中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。 2.术语定义 CAS(Compare and Swap):比较并交换。用于在硬件层面上提供原子性操...

使用CompletableFuture优化你的代码执行效率

  这篇文章详细讲解java8中CompletableFuture的特性,方法以及实例.   在java8以前,我们使用java的多线程编程,一般是通过Runnable中的run方法来完成,这种方式,有个很明显的缺点,就是,没有返回值,这时候,大家可能会去尝试使用Callable中的call方法,然后用Future返回结果,如下: public stati...

【Java】嵌套For循环性能优化案例

参考资料:http://cgs1999.iteye.com/blog/1596671 1 案例描述 某日,在JavaEye上看到一道面试题,题目是这样的:请对以下的代码进行优化  Java代码   for (int i = 0; i < 1000; i++)       for (int j = 0; j < 100; j++)    ...

C#异步编程之(三):深入 Async 和 Await 的实现及其成本

From: http://msdn.microsoft.com/zh-cn/magazine/hh456402.aspx异步性能:了解 Async 和 Await 的成本Stephen Toub 异步编程长时间以来一直都是那些技能高超、喜欢挑战自我的开发人员涉足的领域 — 这些人愿意花费时间,充满热情并拥有心理承受能力,能够在非线性的控制流程中不断地...