天问

  • Blog
  • 热修复与动态加载
  • python爬虫
  • 专栏
    • 热修复与动态加载
    • NDK开发
    • 设计模式与架构
    • React Native
  • 好文转载
    • 计算机技术
    • 投资理财
    • 摄影与拍照技巧
  • 投稿
  • 关于本站及隐私政策
  • Github
StevenQ
为今天工作,为明天投资,为未来孵化一些东西!
  1. 首页
  2. 计算机技术
  3. 安卓安全
  4. 正文

使用ClassLoader动态加载jar从而实现对jar包加密

2017年9月17日 2234点热度 6人点赞 0条评论

最近一直在看安卓dex动态加载相关的文章,如MultiDex,热修复等。而这些本质上是使用ClassLoader进行dex的动态加载。因此回过头来把java中的ClassLoader这块仔细的研究了下,虽然之前也了解过Class Loader相关知识,但这次有了更加深刻的理解。

使用java前为何需要配置环境变量

我们知道java虚拟机执行的是java字节码文件,即.class文件,当我们在Eclipse中创建一个java工程然后进行编译的时候会在工程的bin目录下生成我们在项目中创建的java文件对应的字节码文件,但是有没有想过一个问题就是我们项目中Import的类,如java系统类,这些类是如何被虚拟机加载执行的呢?虚拟机怎么知道在哪里去寻找我们Import的类的字节码的呢?这就涉及到java中的类的加载的问题了,不清楚的请自行百度,或者看这篇文章,个人觉得写的不错,http://blog.csdn.net/xyang81/article/details/7292380  不过这里简单介绍下,我们知道java中的类的加载采用的是双亲委派机制,当ClassLoader加载一个类时首先会判断该类是否已经加载过,如果已经加载过就直接返回该类的字节码,否则就调用该ClassLoader的父加载器(注意不是父类)进行加载,如果父加载器没找到再调用自己的findClass方法进行加载。而在java中主要包括三种类加载器:

  • BootStrapClassLoader
  • ExtClassLoader
  • AppClassLoader

而这三种加载器负责加载不同路径下的类,我们可以用如下代码进行测试

String bootstrapPath = System.getProperty("sun.boot.class.path");
System.out.println("BootStrapClassLoader Path : " + bootstrapPath);
String extPath = System.getProperty("java.ext.dirs");
System.out.println("ExtClassLoader Path : " + extPath);
String appPath = System.getProperty("java.class.path");
System.out.println("AppClassLoader Path : " + appPath);

运行之后输出如下:

可以看到BootStrapClassLoader负责加载java安装路径下的lib目录下的一些jar,如rt.jar,jsse.jar等,而这些jar中包含了java系统类的字节码文件,如rt.jar中包含了平时我们经常使用到的java.lang包,javax包等。如图所示:

这也是为何我们在使用java前需要配置java环境变量的原因,说白了就是设置路径,让java虚拟机知道在哪里去寻找你在项目中使用到的一些java系统类的字节码文件,从而将其加载到虚拟机执行。但是大家可能意识到了这三个类加载器的一个缺陷,即只能够加载指定目录下的字节码文件。那能否加载除了上述三个加载器制定的目录外的字节码文件呢?肯定是能够的,因为上述三个类加载器也是通过路径来加载字节码的,因此只要我们修改这个路径为自己的路径即可。这就需要我们自定义ClassLoader了。

自定义ClassLoader加载指定目录的字节码

自定义ClassLoader其实不难,主要包括三个步骤:

  1. 定义自己的类继承自ClassLoader类
  2. 重写ClassLoader的findClass方法,在该方法中获取字节码的内容将其存储到字节数组中
  3. 调用defineClass方法将字节数组中的内容转换为Class对象,返回该Class对象

为何是重写findClass方法,因为加载类起始于java.lang.ClassLoader的loadClass方法,而在loadClass中java语言开发者已经为我们处理好了加载类的双亲委派机制,即首先调用 findLoadedClass()方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的 loadClass()方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用本身的 findClass()方法来查找该类,因此我们只需要重写findClass()方法即可。

另外一个非常重要的函数就是defineClass函数,该函数用来将一个字节数组转换为类的实例(Converts an array of bytes into an instance of class ),该函数的定义如下:

protected final Class<?> defineClass(String name, byte[] b, int off, int len)

其返回值和各个参数及其意义如下:

  • 返回值Class<?>: 从确定的类数据中创建的类对象,也就是从参数b中创建的类对象
  • 参数name :要定义的类的名称
  • 参数b :构成类数据的字节数组,The bytes that make up the class data
  • 参数off  :类数据在参数b中的起始偏移(The start offset in b of the class data)一般设置为0
  • 参数len :类数据的长度

如下是一个简单的示例,用来加载自定义路径classpath下的class文件

public class CustomerClassLoader extends ClassLoader {

   private String mClassPath;

   public CustomerClassLoader(String classpath) {
      super();
      this.mClassPath=classpath;
      // TODO Auto-generated constructor stub
   }

   public CustomerClassLoader(ClassLoader parent,String classpath) {
      super(parent);
      this.mClassPath=classpath;
      // TODO Auto-generated constructor stub
   }

   //获取自定义路径中的类的字节码的内容,然后通过defineClass将字节码转为Class对象
   @Override
   protected Class<?> findClass(String className) throws ClassNotFoundException {
      // TODO Auto-generated method stub
      System.out.println(className);
      File classFileName=new File(mClassPath,getClassName(className));
      //System.out.println(classFileName);

      //获取类的字节码内容
      try {
         FileInputStream fileInputStream=new FileInputStream(classFileName);
         ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
         int len=0;
         while((len=fileInputStream.read())!=-1){
            byteArrayOutputStream.write(len);
         }

         byte[] classByteCode = byteArrayOutputStream.toByteArray();
         fileInputStream.close();
         byteArrayOutputStream.close();

         //将字节码内容转为Class对象
         return defineClass(className, classByteCode, 0, classByteCode.length);


      } catch (IOException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }

      return super.findClass(className);
   }

   //根据传入的类名获取去除包名后带.class后缀的类名,如com.example.classA.class->classA.class
   String getClassName(String name){
      int index = name.lastIndexOf('.');
      if(index == -1){
         return name+".class";
      }else{
         return name.substring(index+1)+".class";
      }

   }

}

自定义ClassLoader动态加载jar能够做什么

既然能够做到使用时动态的去加载一个jar了,那么我们可以将该jar进行加密操作,然后在动态加载使用前进行解密操作,然后定义加载,这样能够保护jar包的代码不被他人反编译查看。本来打算自己写个demo的,但是发现网上已经有了一篇个人觉得写的不错的文章,因此就不重复造轮子了。

http://blog.csdn.net/wangxiulong056/article/details/46637617

http://www.alonemonkey.com/2016/05/25/encrypt-jar-class/

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2018年5月6日

天问

追梦追自由,穷则独善其身,达则兼济天下

打赏 点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2022 天问 ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang