基于Java编译器的Maven混淆插件的设计与实现

2018-06-02 08:50傅晓巍
电脑知识与技术 2018年10期

傅晓巍

摘要:Java编程语言被大量的工程项目使用,对Java源代码进行保护,防止其被窃取和篡改是非常有必要的。该文以OpenJDK和Oracle JDK的编译器Javac为基础,以编译器插件的形式设计和实现了一个作用于Java源代码的混淆器。该混淆器通过修改由编译器提供的抽象语法树和实现访问者模式达到混淆源代码的目的。该文介绍了名称变换、包修改和数组下标同态计算三种混淆方法。同时,该混淆器被包装成Maven插件,以方便实际项目的使用。

关键词:Java编译器;Maven插件;混淆

中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2018)10-0065-03

Abstract: The Java programming language is used by many engineering projects and it is necessary to protect the Java source code against theft and tampering. Based on compiler Javac of OpenJDK and Oracle JDK, this paper designs and implements an obfuscator that acts on Java source code in the form of compiler plug-in. The obfuscator achieves the purpose of obfuscating the source code by modifying the abstract syntax tree provided by the compiler and implementing the visitor pattern. This article introduces the three obfuscation methods of name conversion, package modification, and array index homomorphic calculation. Meanwhile, the obfuscator is packaged as a Maven plugin to facilitate the use of actual projects.

Key words: java compiler; maven plugin; obfuscate

在所有的软件中,以Java语言编写的程序正在日益成为盗版问题的重灾区。Java语言是当前软件编写最常用的编程语言,它有着优于其他大多数编程语言的可移植性与平台无关性,从而使Java语言越来越被大多数企业接受来研发产品。Java语言在以下两个行业里起着核心作用:1)Android軟件行业。Android手机操作系统使用由Google公司使用Java语言开发的手机端OS,所有Android手机APP必须使用Java语言开发;2)互联网行业。Java语言有着极其优秀的可移植性,同时易于学习,成为许多互联网大公司开发的首选。这些公司使用Java语言后,常常会开发可以提供给其他人使用的工具,反过来更进一步地促进了Java语言本身的发展。现在大部分互联网框架都是使用Java实现的。为了实现可移植性,Java语言使用中间代码发布。这种特性,导致了Java语言所编写的软件比起其他软件更容易被破解,从而严重威胁开发者的知识产权及使用者的软件安全。

Maven是当前十分常用的Java项目管理与发布框架,有必要开发一个能够对源代码进行混淆[1-2]的Maven插件。

1 混淆插件的设计

1.1 Maven插件开发

Java语言在执行代码时,会先由编译器将源代码编译成字节码,然后由虚拟机解释执行字节码。把一个项目下所有源代码、资源、配置等文件编译、打包成字节码的过程称作项目构建。如果项目比较简单,我们可以借助IDE甚至直接使用编译器来构建项目。在实践中,Java项目一般会比较复杂,比如多个项目之间有依赖关系、需要导入第三方的库、有特殊的发布需求等。

对于复杂项目的构建,一般需要使用项目构建工具:

1)Ant是最早出现的项目构建工具,使用脚本来实现配置,其中脚本文件使用XML格式编写。但是XML文件是层次型的结构,不是很适合过程型的脚步,当项目比较大的时候,Ant文件就会非常复杂。

2)Maven具有非常强大的功能,是当下Java项目比较流行使用的。Maven也使用XML文件作为配置文件,该配置文件名称是pom.xml。和Ant不同的是,Maven配置的结构是层次化的。

3)Gradle兼具了Ant和Maven的特点。Gradle不使用XML,而是使用基于Groovy语言的DSL。Gradle可以使用Maven插件。

考虑到Maven插件可以同时被Maven和Gradle两种项目构建工具使用,我们把混淆插件开发成Maven插件的形式。

Maven使用配置Maven仓库以及依赖的方式解决依赖管理。先把包发布到Maven仓库中,仓库包括Apache Maven官方仓库、第三方仓库和自己的仓库。然后在需要使用该依赖包的项目中配置上这个包的仓库地址和名称。这样,通过配置文件和模块的方式配置项目依赖,极大地减少了项目开发过程中依赖管理的开销。

Maven完成项目的编译、测试、发布等任务时,同样也通过配置文件,组合使用Maven丰富的功能插件,完成特定的任务。Maven自己只是框架,运行时以插件构成,比如测试的插件、编译的插件等等。Maven官方本身提供了非常丰富的插件,网上也有很多可以使用的第三方插件,同时我们也可以开发自己的Maven插件。如图1。

要开发自己的Maven插件,只需要继承org.apache.maven.plugin.AbstractMojo类,然后选择把该项目以Maven plugin的格式打包发布。之后就可以在需要使用的项目的pom.xml配置插件并使用了。

1.2 Java插件开发

Java编译器是Java编程语言的编译器,用于编译Java源代码。Java编译器输出的是包含平台无关的Java字节码的Java类文件。Java类文件可以运行在Java虚拟机JVM上。

Javac是OpenJDK和Oracle JDK的编译器。Javac本身是用Java编写的,也可以被编程调用。

开发Javac插件,是通过实现Plugin接口的方式來完成的。开发好插件之后,在META-INF目录下配置com.sun.source.util.Plugin文件,Javac会使用Java的ServiceLoader机制来获得插件的配置。之后就可以在Javac的命令中使用自己开发的插件了。通过-XPlugin:[插件的名字] <参数>的方式来调用。

当我们在启动Javac的时候,Javac会调用Plugin.init方法进行初始化。我们需要在JavacTask中添加我们自己的监听器。之后在Javac的运行过程中调用,会在每个步骤调用我们的TaskListener。如图2。

在执行的过程中,在每个阶段都会调用。其中编译的内容会以抽象语法树的形式呈现。我们可以使用source包下的工具分析抽象语法树。如果需要获取更进一步的信息,甚至要修改抽象语法树,需要使用tools下面的工具。

1.3 插件的设计

我们的插件使用了Javac,虽然可以作为编译器使用,但是我们在实际使用时还是只作为混淆功能使用。我们可以只对代码进行修改,然后再把源代码产生。之后让Maven使用标准的Java编译器来把我们产生的代码编译成Class文件。因此我们的插件是在预处理阶段使用的,不是编译阶段。如图3。

2 混淆器实现

2.1 抽象语法树

Javac是Oracle JDK和OpenJDK中的Java编译器。Javac在编译时会把源代码解析成抽象语法树(Abstract Syntax Tree,AST),然后在抽象语法树上执行编译的任务(如图4):

[public class Main {

public static void main(String[] args) {

System.out.println

("Hello World!");

}

}]

以上是一段简单的Java程序的语法树结构。

在Parse阶段,Javac会把源代码解析成语法树。之后在Enter阶段,Javac会把语法树中对应的符号表建立。在之后的Analyze阶段,把生产的符号和使用的地方对应。我们的修改工作在Analyze阶段之后,Generate阶段之前。Analyze阶段之后,语法树中的信息是最多的,同时语法树的结构还保留着语法糖,当我们把代码写回新文件时,结构最好。在Analyze阶段之后,会开始去语法糖和生成字节码的任务,语法树结构会开始被修改。

抽象语法树中的节点都是JCTree类的子类。这些子类使用了设计模式中的访问者模式。通过使用Visitor,可以遍历抽象语法树。

我们对源代码的修改,主要是通过许多个Visitor对语法树的遍历来实现的。如图5。

2.2 名称变换

名称变化主要是指变量名、函数名和内部类名的变换。包名和外部类名的修改因为涉及了物理文件,我们在2.3节中介绍。

对名称的修改由两个阶段组成:

2.2.1 修改符号

为了能够找到每个名字所对应的变量、函数、类,Javac会为此建立一个符号表,当某个名字被使用时,可以通过符号表查找来确定时那个符号。在之后产生字节码的时候,为这些使用同一个符号的指令分配同一个内存地址。

1)VarSymbol

VarSymbol是变量的符号,用来记录变量信息。一般情况下,一个类内部的变量名时不重复的。但是不同的类之间的变量名时互不干扰的。

2) MethodSymbol

MethodSymbol是函数的符号,记录函数的信息,比如名称、参数类型、返回值类型、泛型、异常等等。MethodSymbol中没有函数体的信息,函数体完全由抽象语法树表示。

3)ClassSymbol

ClassSymbol是类的符号,记录了类的信息,包括了类名和类内部所有成员的符号。修改内部类的名称只需要像变量和函数一样修改,但是修改外部类的名称还需要同时修改顶层语法树。

2.2.2 修改语法树

修改符号,只需要重设符号的name字段就能完成。但是语法树建立的时候,各个语法树节点的名字已经确定了。我们在修改后,这些变动不能应用在符号被使用的地方,我们需要自己使用Visitor去使名称的修改生效。

1)JCIdent

这是一个符号常会被使用的地方。例如语句i=0;中的就是一个JCIdent节点。

2)JCFieldAccess

这种节点对应语法是一个类中选择成员的表达式,比如person.name。

我们使用Visitor对语法树进行深度优先搜索,把所有使用了这个符号的语法树节点名称变成符号的名称。

2.3 包修改

包和外部类因为涉及了物理文件,因此修改步骤要比简单名称变换复杂很多。

2.3.1 修改符号和语法树

1)外部类

外部类的符号修改和内部类一致。类和子类的符号的关系是类包含了子类,因此子类的信息在父类的符号表中全部都有。对类和子类的访问是一种从上到下的方式进行的。

2)包

包的符号修改则和类的符号不同。包和类的符号的关系是由类来存储上层的包的符号,包和上一级的包也是这种关系。因此,对包的访问是一种从下往上的方式进行的。同时,包的语法树中并没有符号PackageSymbol。對包的语法树修改无法使用Visitor,我们需要自己手动修改。

2.3.2 写回

在我们修改完包和外部类的名称后,编译产生的外部类的物理地址也会改变。我们需要修改存储的物理节点的信息。但是由于包是没有直接对应的语法树节点的,我们必须要修改该包下所有的类的名称。

2.4 数组下标同态计算

数组下标是整形,同时数组的长度在产生后是固定的,可以使用同态计算[3][4][5]混淆数组下标:

1) 对一个长度为n的数组A,随机产生一个整数m,要求m>n,并且m和n互质。

2) 把所有对A进行下标访问的A[i]都修改成A[i×m mod n]。

以下是具体的实现的步骤:

a)使用Visitor找到所有对数组A的下标访问。对数组进行下标访问的语法树节点是JCArrayAccess。

b)在下标访问的表达式外侧添加同态计算的语法树。使用JCIdent和JCBinary。其中JCBinary需要使用Resolve工具来得到乘法和求模的MethodSymbol。

3 结论

本文以Javac编译器为基础,用插件的形式,实现了一个基于抽象语法的混淆器。并且实现了名称修改、包修改、数组下标同态计算等混淆方法。

同时,我们把混淆器包装成Maven插件,使混淆器可以在实践中被有效的使用。

参考文献:

[1] Cohen F B. Operating system protection through program evolution[J]. Computers & Security, 1993, 12(6): 565-584.

[2] Collberg C, Thomborson C, Low D. A taxonomy of obfuscating transformations[R]. Technical Report, 1997.

[3] Brakerski Z, Gentry C, Halevi S. Packed ciphertexts in LWE-based homomorphic encryption[M]//Public-Key Cryptography–PKC 2013. Springer Berlin Heidelberg, 2013: 1-13.

[4] Dalla Preda M, Giacobazzi R. Control code obfuscation by abstract interpretation[C]//Third IEEE International Conference on Software Engineering and Formal Methods (SEFM'05). IEEE, 2005: 301-310.

[5] Gentry C, Sahai A, Waters B. Homomorphic encryption from learning with errors: Conceptually-simpler, asymptotically-faster, attribute-based[M]//Advances in Cryptology–CRYPTO 2013. Springer Berlin Heidelberg, 2013: 75-92.