ITPub博客

首页 > Linux操作系统 > Linux操作系统 > [转载]Java反编译的研究

[转载]Java反编译的研究

原创 Linux操作系统 作者:dinner1007 时间:2019-04-22 21:48:06 0 删除 编辑
Java反编译的研究
java诞生于1995年,是一门较年轻的语言。它以平台无关性,安全性,面向对象,分布式,键壮性等特点赢得了众多程序员的青睐。特别是它简洁的 面向对象的语言风格,更让许多人对它爱不释手。但人们在使用java的过程中,会发现它有几个致命的弱点:运行速度慢,用户使用不便,源代码保护机制不够 安全。特别是在保护源代码方面,java是基于解释一种叫java字节码的中间代码来运行其程序的,而且jvm比计算机的微处理器要简单的多,文档也很齐 全,结果造成其目标程序很容易被反编译,而且所得代码和其原始代码十分相似,甚至可以一模一样,可读性相当好。这就给java的代码保护带来了不利。但要 实现java程序的保护,也不是不可能的,经研究和总结,至少有三种实现方式:1.混淆器;2.网络加载重要类;3加密重要类。

一、 混淆器

  目前,开发人员使用的比较多的保护代码的方法是用混淆器。混淆器是采用一些方 法将类,变量,方法,包的名字改为无意义的字符串;使用非法的字符代替符号;贴加一些代码使反编译软件崩溃;贴加一些无关的指令或永远执行不到的指令等使 反编译无法成功或所得的代码可读性很差。这样就实现了反反编译的目的。我们来做个演示。原始代码如下:


import java.io.*;

import java.security.*;

public class sKey_kb{

public static void main(String args[]) throws Exception{

FileInputStream f=new FileInputStream("key1.dat");

ObjectInputStream b=new ObjectInputStream(f);

Key k=(Key)b.readObject();

byte[] kb=k.getEncoded();

FileOutputStream f2=new FileOutputStream("keykb1.dat");

f2.write(kb);

for(int i=0;i

System.out.print(kb[i]+",");

} } }



  使用混淆器后,再用jad反编译得代码如下:

import java.io.*;

import java.security.Key;

public class sKey_kb{

public skey() {}

public static void main(String args[]) {

FileInputStream fileinputstream=new FileInputStream(ma);

ObjectInputStream objectinputstream=new ObjectInputStream(fileinputstream);

Key key=(Key)b.readObject();

byte abyte0[]=key.getEncoded();

FileOutputStream fileoutputstream=new FileOutputStream(na);

fileoutputstream.write(abyte0);

for(int i=0;i

System.out.print(abyte0[i]+oa);

}

private static String a(String s){

int i=s.length();

char ac[]=new char[i];

for(int j=0;j
return new String(ac);

}

private static String ma="u5AA1u5AAFu5AF3u5AFBu5AE4u5AAEu5AABu5ABE";

private static String na="u5AA1u5AAFu5AB3u5AA1u5AA8u5AFBu5AE4u5AAEu5AABu5ABE";

private static String oa="u5AE6";

public static{

ma=a(ma);

na=a(ma)

oa=a(oa);

} }

  混淆后,再反编译所仍然能得到源代码,但显然,所得代码与原始代码比,变得难以读懂,代码中多了其他的方法,文件名等信息也被打乱了。并且,把以上代码写进sKey_kb.java中,无法通过编译。

  但是,如果在编写软件时,在软件中写入某些注册信息,或一些简单的算法,通过反编译,还是有可能得到这些信息的,从而未能达到保护软件的目的。反编译器与混淆器之间的斗争是永无止尽的。所以从其他角度去保护java的源代码是很有必要。


二、 网络加载重要类

   在java中提供了一个ClassLoader类,这个类可以让我们使用类加载器将所需要的java字节码文件加载到jvm中。我们通过重写这个类,可 以实现从网络通过url加载java字节码文件。这样,我们就可以把一些重要的,隐秘的class放在网络服务器上,通过口令去检验是否有权限下载该类。 从而实现java代码保护的目的。其次在java中正好提供了URLClassLoader这个类,通过此类,正好可以实现我们的目的。 URLClassLoader类的基本使用方法是通过一个URL类型的数组告诉URLClassLoader类的对象是从什么地方加载类,然后使用 loadclass()方法,从给定的URL中加载字节码文件,获得它的方法,然后再执行。

  具体步骤如下:

  1.创建URL

URL url[]={

new URL("file:///c:/classloader/web"),

new URL("http://www.asp.zjc.zjut.edu.cn/javaclass/")

};



  2.创建URLClassLoader对象

URLClassLoader cl=new URLClassLoader(url);



  3.使用URLClassLoader对象加载字节码文件

Class class=cl.loadClass("class1");



  4.执行静态方法

Class getarg[]={

(new String [1]).getClass() };

Method m=class.getMethod("main",getarg);

String[] myl={"arg1 passed","arg2 passed");

Object myarg[]={myl};

m.invole(null,myarg);




三、 加密重要类

  使用网络加载重要类的方法固然有一定的用处,但是,在遇到无网络的情况时,还是无法解决我们的问题。对于这种情况,我们只能把所有文件放在本地计算机上。那么,对此我们该怎么做才能保护好java代码呢?

   其实,要实现这一点,并不难,只需要对一些重要的类实行加密就可以了。当然,在装载时,加密的类是需要解密才能被ClassLoader识别的。所以, 我们必须自己创建ClassLoader类。在标准java api中ClassLoader有几个重要的方法。创建定制ClassLoader时,我们只需覆盖其中的一个,即loadClass,添加获取原始类文 件数据的代码。这个方法有两个参数:类的名字,以及一个表示JVM是否要求解析类名字的标记(即是否同时装入有依赖关系的类)。如果这个标记为true, 我们只需在返回JVM之前调用resolveClass。

  原代码如下:

public Class loadClass( String name, boolean resolve )

throws ClassNotFoundException {

try {

Class clasz = null;

//步骤1:如果类已经在系统缓冲之中,我们就不需要再次装入它

clasz = findLoadedClass( name );

if (clasz != null)

return clasz;

byte classData[] = /* 通过某种方法获取字节码数据 */;

if (classData != null) {

clasz = defineClass( name, classData, 0, classData.length );

}

//步骤2:如果上面没有成功,

if (clasz == null)

clasz = findSystemClass( name );

//步骤3:如有必要,则装入相关的类

if (resolve && clasz != null)

resolveClass( clasz );

return clasz;

} catch( IOException ie ) {

throw new ClassNotFoundException( ie.toString() );

} catch( GeneralSecurityException gse ) {

throw new ClassNotFoundException( gse.toString() );

} }



  代码中的大部分对所有ClassLoader对象来说都一样,但有一小部分是特有的。在处理过程中,ClassLoader对 象要用到其他几个辅助方法:findLoadedClass:用来进行检查,以便确认被请求的类当前是否存在,loadClass方法应该首先调用它。 defineClass:获得原始类文件字节码数据之后,调用defineClass把它转换成对象,任何loadClass实现都必须调用这个方法。 findSystemClass:提供默认ClassLoader的支持。如果用来寻找类的定制方法不能找到指定的类,则可以调用该方法尝试默认的装入方 式。resolveClass:当JVM想要装入的不仅包括指定的类,而且还包括该类引用的所有其他类时,它会把loadClass的resolve参数 设置成true。这时,我们必须在返回刚刚装入的Class对象给调用者之前调用resolveClass。

  接下来就是加密解密 部分。Java加密扩展即Java Cryptography Extension,简称JCE,是Sun的加密服务软件,包含了加密和密匙生成功能。我们可以用DES算法加密和解密字节码。用JCE加密和解密数据是 要遵循一些基本步骤的(可以参考<>,这里就不祥述了)。

  加密完成后,就是通过解密来获取原始类的java字节码。可以通过一个DecryptStart程序运行经过加密的应用。

  具体方法如下:

public class DecryptStart extends ClassLoader

{

private SecretKey key;

private Cipher cipher;

public DecryptStart( SecretKey key ) throws GeneralSecurityException,IOException {

this.key = key;

String algorithm = "DES";

SecureRandom sr = new SecureRandom();

System.err.println( "[DecryptStart: creating cipher]" );

cipher = Cipher.getInstance( algorithm );

cipher.init( Cipher.DECRYPT_MODE, key, sr );

}

// main过程:我们要在这里读入密匙,创建DecryptStart的

static public void main( String args[] ) throws Exception {

String keyFilename = args[0];

String appName = args[1];

String realArgs[] = new String[args.length-2];

System.arraycopy( args, 2, realArgs, 0, args.length-2 );

System.err.println( "[DecryptStart: reading key]" );

byte rawKey[] = Util.readFile( keyFilename );

DESKeySpec dks = new DESKeySpec( rawKey );

SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );

SecretKey key = keyFactory.generateSecret( dks );

DecryptStart dr = new DecryptStart( key );

System.err.println( "[DecryptStart: loading "+appName+"]" );

Class clasz = dr.loadClass( appName );

String proto[] = new String[1];

Class mainArgs[] = { (new String[1]).getClass() };

Method main = clasz.getMethod( "main", mainArgs );

Object argsArray[] = { realArgs };

System.err.println( "[DecryptStart: running "+appName+".main()]" );

main.invoke( null, argsArray );

}



  虽然应用本身经过了加密,但启动程序DecryptStart没有加密。攻击者可以反编译启动程序并修改它,把解密后的类文件 保存到磁盘。降低这种风险的办法之一是对启动程序进行高质量的模糊处理。或者,启动程序也可以采用直接编译成机器语言的代码,使得启动程序具有传统执行文 件格式的安全性.比如使用java的jini技术,来实现解密部分,就可以作到。当然,这是需要付出一定的代价的,就是丧失了java的最大特点--平台 无关性。不过,jni技术可以用c语言在多种平台实现,我们可以在不同的平台编写不同的启动程序。


四、 综合实例

   对于一些需要网络支持的软件来说,可以建立一个Web站点,在站点上存放该软件的关键类,并且建立用户管理机制,用户直接登陆网站进行确认,是许可用 户,则发放解密key文件,让其下载关键类,在本地解密运行。这样作的优点是建立的Web站点可以有效的管理密钥以及用户资料。从而起到加强保护软件源代 码的作用,并方便软件升级。用C/S结构是不错的选择。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-130969/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2018-08-23

  • 博文量
    992
  • 访问量
    713459