基于JVMTI的Java字节码保护技术的研究和实现v0.22

更新时间:2023-04-23 22:04:01 阅读量: 实用文档 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

摘要:本文分析了Java字节码保护技术的现状,在此基础上提出了一种基于JVMTI的Java字节码保护技术,使得Java字节码的安全级别相当于传统的二进制代码。本文还给出了该技术在Windows平台和Linux平台下的实现方案。

基于JVMTI的Java字节码保护技术的

研究和实现

吴芳

(陕西省纺织科学研究所,西安 710038)

摘要:本文分析了Java字节码保护技术的现状,在此基础上提出了一种基于JVMTI的Java字节码保护技术,使得Java字节码的安全级别相当于传统的二进制代码。本文还给出了该技术在Windows平台和Linux平台下的实现方案。

关键词:Java;JVMTI;字节码保护;安全级别

引言

C/C ++等编程语言开发的程序被编译成本机可执行的二进制代码。编译器用内存地址代替了程序中的变量名称、方法名称等信息。因此对这些本地二进制代码进行反编译从而得到源代码是非常困难,甚至是不可能的。

Java是一种跨平台的、解释型语言。Java编译工具将Java 源代码被编译成为class文件(即Java 字节码),由Java 虚拟机(Java Virtual Machine,JVM)负责对class文件进行解释执行。与本地目标代码不同,class文件中仍然保留了方法名称、变量名称,并且通过这些名称来访问变量和方法,这些符号往往带有许多语义信息。因此,对class文件进行反编译就显得比较容易。目前,市场上有许多Java的反编译工具,有免费的,也有商用的,还有的是开放源代码的。这些工具都能够从class文件生成高质量源代码。所以,如何保护Java程序就变成了一个非常重要的挑战。

1 Java字节码保护技术的现状

目前主要有有如下几种Java字节码保护技术:隔离Java程序、字节码混淆、转换本地代码、数字水印、自定义类加载器等。

隔离Java程序是指将关键的class文件放在服务器端,客户端通过访问服务器的相关接口来获得服务,而不是直接访问class文件。这样黑客就没有办法反编译class文件[1]。HTTP、Web Service、RPC等标准和协议都能够支持通过接口提供服务。但是有很多应用都不适合这种保护方式,例如对于单机运行的程序就无法隔离Java程序。

字节码混淆主要是通过将定义的类、变量、方法和包的名字改为无意义的字符串、使用非法的字符代替变量符号和在软件中添加一些无关的指令或永远执行不到的指令[2]等手段来增加反编译和对反编译后源代码阅读的难度。但这种方法并不能真正阻止反编译,而且不

摘要:本文分析了Java字节码保护技术的现状,在此基础上提出了一种基于JVMTI的Java字节码保护技术,使得Java字节码的安全级别相当于传统的二进制代码。本文还给出了该技术在Windows平台和Linux平台下的实现方案。

论是开源的或是商用的混淆工具都有一定的规律可循,如果掌握这些规律,在反编译时仍然可以得到一定质量的源代码。

转换本地代码即将Java程序像C/C++程序编译成本机可执行的二进制代码,目前TowerJ、jexegen、JET、JOVE、JToEXE等工具都能做到这一点。但是这样做使得Java程序失去其跨平台的特性,而且这种技术目前并不十分成熟,因此不适用于大型应用程序。

数字水印技术是在class 文件中嵌入以数字水印形式存在的开发者的签名[3]。数字水印技术并不能阻止反编译,但是能在确认某些程序是否属于剽窃时提供有效的证据。

自定义类加载器是指首先将class文件进行加密处理,然后自己编写一个Java类装载器(即继承ng.ClassLoader并重载其loadClass方法)在class文件装载时再进行解密处理。这种方法的缺点在于虽然经过加密的class文件无法被反编译,但自定义的类加载器本身却不能防止被反编译。因此,加密过的class文件仍然是不安全的。

另外,一些商用的专业数据加密软件也能提供对Java字节码的保护,如HASP、

CodeMeter等,其原理是对Java程序和java.exe都进行加壳处理,在Java程序运行时需要连接加密狗进行解密。这种方法虽然安全性较高但是对于不使用java.exe来运行的Java程序(如Eclipse平台使用的是javaw.exe)则无能为力。因此通用性较差。

2 利用JVMTI和JNI保护Java字节码

通过第一节对Java字节码保护现状的分析可以得出结论:要找到一种更好Java字节码保护方法则必须满足以下3个条件:

能够有效防止反编译,使得Java程序的安全级别相当于本地应用程序。

保持Java程序的跨平台特性。

不修改JVM。

将class文件作为数据文件进行加密是比较容易的(关于数据加密的算法很多,在此不再赘述),实现了这一步即可阻止对class文件的直接反编译。但问题的关键在于何时以何种方式将加密过的class文件安全地解密?首先来分析一下JVM的类加载机制。

2.1 JVM的类加载机制

Java 语言是一种动态性的解释型语言。Java编译器将每个类和接口都编译成一个单独的class 文件,这些文件对于JVM来说就是一个个可以动态加载的单元,这些文件只有在需要使用时才会被加载。当指定程序运行的时候,JVM就将class 文件按照需求和一定的规则加载到JVM的内存,并组织成为一个完整的Java 应用程序。

JVM初始化类加载器的过程如图1所示:

摘要:本文分析了Java字节码保护技术的现状,在此基础上提出了一种基于JVMTI的Java字节码保护技术,使得Java字节码的安全级别相当于传统的二进制代码。本文还给出了该技术在Windows平台和Linux平台下的实现方案。

图1:JVM类加载器的初始化

JVM被激活后,会产生第一个类加载器BootstrapClassLoader,BootstrapClassLoader是采用C++语言编写的本地代码,BootstrapClassLoader首先加载Launcher.java之中的ExtClassLoader,并设定其Parent为null,代表其父加载器为BootstrapClassLoader。然后BootstrapClassLoader再要求加载Launcher.java 之中的AppClassLoader,并设定其Parent为之前产生的ExtClassLoader实例。在这三个类加载器中BootstrapClassLoader和

ExtClassLoader用来加载JVM的系统类,AppClassLoader用来加载用户自己的类。

AppClassLoader首先通过其findclass方法查找需要加载的class文件,然后调用defineclass方法将class文件的内容转换成Class对象。AppClassLoader的defineclass方法最终调用的是其超类ClassLoader中的本地方法defineclass1。defineclass1方法的第二个参数就是class文件的内容。

通过上述分析,如果有一种Hook机制能够将JVM调用本地方法defineclass1的事件截获,并在Hook函数中利用本地方法实现对class文件内容的解密操作,然后将解密后的字节码传递给JVM。这样就能使解密过程仅在内存中进行,从而实现对加密后的class文件的安全解密。

摘要:本文分析了Java字节码保护技术的现状,在此基础上提出了一种基于JVMTI的Java字节码保护技术,使得Java字节码的安全级别相当于传统的二进制代码。本文还给出了该技术在Windows平台和Linux平台下的实现方案。

2.2 JNI和JVMTI

JNI的全称是Java Native Interface[4](Java本地接口)。JNI 是JDK 的一部分,用于为Java 提供本地代码接口。JNI 使得运行在JVM 虚拟机上的Java 代码能够操作使用其它语言编写的应用程序和库,如C/C++以及汇编语言等。JNI 在Java 程序和C/C++程序间的调用原理如图2所示:

图2:JNI在Java程序和C/C++程序间的调用原理

通过JNI中提供的接口,本地代码可以与JVM进行交互,其中有一个名为RegisterNatives的接口,这个接口可以注册与某个类的方法关联的本地代码。

JVMTI的全称是Java Virtual Machine Tool Interface[5](Java虚拟机工具接口),它提供了一种监视JVM状态及控制JVM执行的方法:JVMTI客户端(又称为代理或Agent)。JVMTI客户端能够从JVM监听到感兴趣的事件。用户可以通过JVM的-agentlib参数向JVM注册JVMTI客户端。下面对本文所要涉及的JVMTI函数和事件做简要说明。

Agent_OnLoad、Agent_OnUnload函数:JVM与代理交互的入口和出口函数。 SetEventNotificationMode函数:该函数可以向JVM注册要监听的事件。

SetEventCallbacks函数:该函数可以为用SetEventNotificationMode注册过的监听

事件指定Hook函数。

VMInit事件:JVM的初始化事件,这个事件的发生表示JVM初始化的完成。在该

事件完成后,JVMTI客户端就可以得到JNI和JVMTI环境。

通过对JNI和JVMTI的分析可知只要采用JVMTI技术编写合适的JVMTI客户端就能够监听到class文件的加载事件;然后向该事件挂接一个采用JNI技术编写事件的回调函数,在回调函数中对字节码进行解密处理,这样就可以在JVM加载字节码的时候利用本地代码将其解密。由于JVMTI客户端和JNI回调函数都是本地代码,并且解密的动作在JVM的内存中完成,不在本地保留解密后的字节码,因此解密动作的安全级别与本地代码相当。

由于JNI和JVMTI都是JVM提供的机制,与平台无关,即JVMTI客户端和JNI回调

摘要:本文分析了Java字节码保护技术的现状,在此基础上提出了一种基于JVMTI的Java字节码保护技术,使得Java字节码的安全级别相当于传统的二进制代码。本文还给出了该技术在Windows平台和Linux平台下的实现方案。

函数在支持JVM的平台上都可以实现,所以这种方法能够保持Java程序的跨平台特性。 3 解密实现

当JVM加载一个JVMTI客户端时会自动调用Agent_OnLoad函数,如果在这个函数中向JVM注册VMInit事件及回调函数,那么JVM初始化后JVMTI客户端就会监听到VMInit事件并进入该事件的回调函数。在回调函数中利用JNI提供的RegisterNatives函数将ClassLoader.defineclass1方法注册为自定义的一个代理函数,这样就可以拦截JVM对ClassLoader.defineclass1方法的调用。当JVM在为了生成某个类的Class对象而调用

ClassLoader.defineclass1方法时,它会调用自定义的代理函数,在代理函数中实现对字节码的解密。

JVMTI客户端和JNI回调函数在支持JVM的平台上都可以实现,这里仅以Windows平台和Linux平台为例说明。

3.1 基于Windows平台的实现

在Windows平台上JVMTI客户端以动态链接库的形式出现。支持创建动态链接库的开发工具很多,这里以Microsoft Visual C++为例说明。

首先创建一个动态链接库项目,并将JDK中JVMTI和JNI相关的头文件(jvmti.h、jni.h、jni_md.h)引入到项目中以建立支持JVMTI和JNI的编程环境;然后创建Agent_OnLoad函数,在该函数中通过GetEnv获取JVMTI环境并向JVM预定VMInit事件的通知并设置VMInit事件的回调函数,在回调函数中利用RegisterNatives将自定义的本地代理方法注册到ClassLoader.defineclass1上;最后完成自定义的本地代理方法,在该方法中实现对字节码的解密。解密后的字节码存储在JVM的内存中,此时再调用java.dll中的

_Java_java_lang_ClassLoader_defineClass1@32方法将解密后的字节码转换为Class对象并将这个对象作为Agent_OnLoad的返回值返回给JVM。

在运行加密过的class文件时,必须在JVM的运行参数中通过-agentlib参数指定JVMTI客户端。

3.2 基于Linux平台的实现

在windows平台上的源代码基础上做如下修改即可实现JVMTI客户端向Linux平台的移植:

由于在Windows平台从动态链接库中调用函数需要LoadLibrary、GetProcAddress

和FreeLibrary三个库函数,而g++编译器不支持这三个库函数,所以需要将

windows.h头文件改为dlfcn.h头文件,然后分别使用dlopen、dlsym和dlclose这三

摘要:本文分析了Java字节码保护技术的现状,在此基础上提出了一种基于JVMTI的Java字节码保护技术,使得Java字节码的安全级别相当于传统的二进制代码。本文还给出了该技术在Windows平台和Linux平台下的实现方案。

个库函数,具体实现和Windows平台类似。

利用g++编译代码时,需要使用shared参数,这样将代码编译成动态链接库,利用

-o libXXX.so输出代理库,其中输出名称的后缀为lib和.so,XXX为自定义名称。 在程序运行前,必须使用set命令设置L-D_LIBRARY_PATH变量为代理库的路径。 和Windows平台一样,在运行加密过的class文件时,必须在JVM的运行参数中通过-agentlib参数指定JVMTI客户端。

4 结束语

本文提出的基于JVMTI的Java字节码保护技术及其实现方案在保持Java程序跨平台特性和不修改JVM的前提下达到了有效防止反编译的目标。当然这种技术并非无懈可击,采用极端的方式仍然可以对JVMTI客户端程序进行破解,破解的难度取决于JVMTI客户端采用的加密算法的强度。但正如Alex Kalinovsky所说[6]:成功的保护机制的关键是让它复杂到95%的典型用户很难破解,并迫使其余5%有经验的破解者花费大量的时间来破译。换句话来说,目标是让购买许可比花费在破解保护上的时间显得更加划算。

本文在JVMTI客户端程序对JVM类加载器执行效率的影响方面没有给出做详尽的分析或数据,但可以肯定的是这种影响是存在的,其影响程度主要取决于解密算法的时间复杂度。这个问题值得相关工程技术人员进一步研究和探讨。

参考文献:

[1] 欧阳辰.如何保护Java程序[EB/OL].

/article/websec/java/index,2004-12-02.

[2]陈刚.基于封装包的Java源代码安全保护[J].电子信息与对抗技术,2006,21(3):45-48.

[3]陈晗,赵轶群,缪亚波.Java字节码的水印嵌入[J].微型电脑应用,2004,20(20):56-58.

[4]Sun Microsystems. Java Native Interface

Specification[EB/OL]./javase/6/docs/technotes/guides/jni/spec/niTOC.2007-06-20.

[5]Sun Microsystems.JVM(TM)Tool Interface

1.1.102[EB/OL]./javase/6/docs/platform/jvmti/jvmti.2007-06-20.

[6]Alex Kalinovsky. 透视JAVA[M].刘凌,周哲海 译.北京:清华大学出版社,2005

摘要:本文分析了Java字节码保护技术的现状,在此基础上提出了一种基于JVMTI的Java字节码保护技术,使得Java字节码的安全级别相当于传统的二进制代码。本文还给出了该技术在Windows平台和Linux平台下的实现方案。

Research and Realization of Java Byte Code Protection Technical Based on

JVMTI

Abstract: The status of existing Java byte code protection technical is analyzed. Based on the result of this analyze, put forward a Java byte code protection technical based on JVMTI that can make the security level of Java byte code equal to the traditional binary code. Moreover, the implementation scheme works on the platform of Windows and Linux were offered. Keywords: Java; JVMTI; byte code protection; security level

本文来源:https://www.bwwdw.com/article/f45q.html

Top