玩转代码混淆工具:ProGuard

维基百科对ProGuard的介绍是:ProGuard是一个压缩(shrink),优化(optimize)与混淆(Obfuscate)Java代码的开源命令行工具。也就是说混淆只是ProGuard的其中一个功能,本文也只介绍它的混淆功能。
About ProGuard
ProGuard能通过重命名类名,字段名,方法名为一些没有意义的名字来混淆Java和Android程序,从而使逆向工程更难。并且ProGuard已经是Android SDK的一部分了,当然本文只讲如何混淆Java代码。

用法

本文介绍两种方式:使用UI工具 和 Maven集成。
  • UI工具
这个比较简单,先从https://sourceforge.net/projects/proguard/下载最新版的ProGuard,然后解压,到bin目录下找到proguardgui.bat双击即可,程序运行后的界面如下:
ProGuard
Input/Output中,Add input选择要混淆的JAR包(ProGuard不支持混淆单个class文件,但是我们可以通过命令把一个或多个class打成jar包:jar cvf Test.jar com/afei/test/*.class),Add output指定输出文件名。Obfuscation中有很多混淆自定义规则,根据需求自行调整,ShrinkingOptimization视情况而定是否需要勾选(如果只是混淆代码,则建议关闭)。最后选择Process,点击右下角的Process!即可。如下图所示,User.java混淆前后对比,我们发现类名,属性名都被重命名为一些毫无意义的命名了:
image.png
  • Maven集成
通过maven集成方式从而在maven打包阶段就达到了混淆的目。集成方式非常简单,只需修改POM,添加如下一些代码即可:
— 添加依赖
<dependency>
    <groupId>net.sf.proguard</groupId>
    <artifactId>proguard-base</artifactId>
    <version>5.3.3</version>
</dependency>

— 添加插件
<plugin>
    <groupId>com.github.wvengen</groupId>
    <artifactId>proguard-maven-plugin</artifactId>
    <version>2.0.15-SNAPSHOT</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals><goal>proguard</goal></goals>
        </execution>
    </executions>
    <configuration>
        <proguardVersion>5.3.3</proguardVersion>
        <injar>${project.build.finalName}.jar</injar>
        <outjar>${project.build.finalName}.jar</outjar>
        <obfuscate>true</obfuscate>
        <libs>
            <lib>${java.home}/lib/rt.jar</lib>
            <lib>${java.home}/lib/jce.jar</lib>
        </libs>
        <options>
            <!–不需要压缩–>
            <option>-dontshrink</option>
            <!–不需要优化–>
            <option>-dontoptimize</option>
            <option>-keepnames interface **</option>          
            <option>-keepparameternames</option>
            <option>-keeppackagenames</option>                    
        </options>
    </configuration>
</plugin>
集成后执行maven package打包,就会看到target目录下会多出一些命名中带有proguard的文件,例如afei-demo-1.0.0-SNAPSHOT_proguard_base.jarproguard_map.txtproguard_seed.txt等,并且每个目录下的类名都被重命名为a, b, c等毫无意义的类名:
confusion target

  • 配置说明
maven集成ProGuard后,可自定义的地方主要集中在options标签中,一些option说明如下:
-dontshrink
shrink是ProGuard几大核心功能之一,配置了dontshrink表示不需要压缩,即关闭shrink功能

-dontoptimize
optimize也是ProGuard几大核心功能之一,配置了dontoptimize表示不需要优化,即关闭soptimize功能

-keeppackagenames
是否不混淆包名,如果配置了该项,那么包名会完整保留。如果没有配置,那么除了根目录(com.afei.test),子目录(例如bean, controller, impl等)都会被混淆。

-keepattributes SourceFile,LineNumberTable
抛出异常时保留代码行号,在异常分析中可以方便定位

-dontusemixedcaseclassnames
这个是给Windows系统用户的,因为ProGuard假定使用的操作系统是能区分大小写名,但是Windows不是这样的操作系统,所以必须为ProGuard指定-dontusemixedcaseclassnames选项

-keep class com.afei.test.Application
Application这个类名不混淆,但是方法名,属性名,以及方法中的内容还是会混淆;

-keep class com.afei.test.Application { *; }
Application这个类的类名,方法名,属性名完全保留不做任何混淆,但是方法体内的内容还是会混淆。

-keepnames interface **
所有接口类命名不做任何混淆,但是接口里的属性和方法会还是会被混淆;

-keepnames enum **
枚举类名不做任何混淆,但是枚举的值会混淆;

-keep public class * implements java.io.Serializable{
    public *;
    protected *;
    private *;
}
保留实现了Serializable接口类中的公有的,友好的,私有的成员(属性和方法),这个配置主要是对应实体类的配置。

-keep class com.afei.test.dao.** { *; }
dao层的类名和方法都不混淆;

-keepnames
用法如下,保留Application的类名和main方法不混淆:
<option>-keepnames public class com.afei.test.Application {
        public static void main(java.lang.String[]);
    }
</option>

用法如下,保留FtpConfigSaveJob的类名和execute方法不混淆:
<option>-keepclasseswithmembers public class com.afei.test.job.FtpConfigSaveJob {
        com.xxl.job.core.biz.model.ReturnT execute(java.lang.String);
    }

</option>

配置中符号的含义:
!:感叹号表示除这个以外,例如-keep class !com.afei.test.Application表示出了Application.java,其他的类名都不混淆

遇到的问题

通过使用ProGuard混淆springboot项目的过程,笔者遇到了下面这些问题:
  1. 工程目录要清晰,比如VO, DTO, PO存放目录要统一,自定义Exception子类存放目录等。因为这些类名,甚至属性名都不能混淆,VO属性名与接口定义有关,PO与Mapper.xml有关;
  2. 与序列化反序列相关的对象也不会混淆,与第三方约定的接口相关的模型也不能混淆等;
  3. xxl-job相关类不能混淆,方法申明必须是execute(String),笔者相信还会有其他的框架也会有类似的约定和限制;
  4. springboot的Application.java也不能混淆,因为main方法不能被重命名,Application.java类名最好也不要被重命名,因为maven中有配;置com.afei.test.Application
  5. 很多注解不能混淆
  6. … …
如下图所示,是默认配置混淆后的Application.java,很明显,这样的springboot工程是无法启动的,因为main主方法被混淆为a了:
混淆后的Application.java
另外,由于ProGuard默认会将每个子目录下的类都重命名为a, b, c。例如com.afei.test.a下的类会被重命名为a.class,b.class,c.class,而com.afei.test.b下的类也会被重命名为a.class,b.class,c.class。这样就会导致spring初始化加载bean时抛出下面的异常:
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name ‘b’ for bean class [com.afei.test.config.bconflicts with existingnon-compatible bean definition of same name and class [com.afei.test.common.b]
        at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:345)
        at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:283)
        at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:135)
        at 

ProGuard总结

通过上面的分析可知,ProGuard或者类似原理的代码混淆工具的限制太多太多,对工程目录有严格的要求,而且很多地方需要明确配置不允许混淆,以后有新增的代码,还需要评估是否需要申明不需要混淆。这样分析下来后发现,如果想要使用ProGuard混淆服务端的代码,最后几乎只能混淆方法体内的内容。

XJar

针对ProGuard或者同原理方案的缺陷,笔者找到了另一种方案:XJar。
官方介绍XJar是Spring Boot JAR 安全加密运行工具,同时支持的原生JAR。基于对JAR包内资源的加密以及拓展ClassLoader来构建的一套程序加密启动,动态解密运行的方案,避免源码泄露或反编译。功能特性如下:
  • 无需侵入代码,只需要把编译好的JAR包通过工具加密即可。
  • 完全内存解密,杜绝源码以及字节码泄露或反编译。
  • 支持所有JDK内置加解密算法。
  • 可选择需要加解密的字节码或其他资源文件,避免计算资源浪费。
详细的介绍和用法请参考XJar GitHub主页: https://github.com/core-lib/xjar,介绍的非常详细。
本站所有文章均由网友分享,仅用于参考学习用,请勿直接转载,如有侵权,请联系网站客服删除相关文章。若由于商用引起版权纠纷,一切责任均由使用者承担
极客文库 » 玩转代码混淆工具:ProGuard

Leave a Reply

欢迎加入「极客文库」,成为原创作者从这里开始!

立即加入 了解更多