Java安全之ClassLoader
Java安全之ClassLoader
类加载机制
Java中的源码.java
后缀文件会在运行前被编译成.class
后缀文件,文件内的字节码的本质就是一个字节数组 ,它有特定的复杂的内部格式,Java类初始化的时候会调用java.lang.ClassLoader
加载字节码,.class
文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的.class
文件,并创建对应的class对象,将class文件加载到虚拟机的内存,而在JVM中类的查找与装载就是由ClassLoader完成的,而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。
类加载方式
Java类加载方式分为显式和隐式
显式:利用反射或ClassLoader来动态加载一个类
隐式:通过new 一个类或者 类名.方法名返回一个类
示例代码
@Test
public void loadClassTest() throws Exception {
//1、反射加载
Class<?> aClass = Class.forName("java.lang.Runtime");
System.out.println(aClass.getName());
//2、ClassLoader加载
Class<?> aClass1 = ClassLoader.getSystemClassLoader().loadClass("java.lang.ProcessBuilder");
System.out.println(aClass1.getName());
}
那也就是其实可以通过ClassLoader.loadClass()
代替Class.forName()
来获取某个类的class对象。
ClassLoader
ClassLoader(类加载器)主要作用就是将class文件读入内存,并为之生成对应的java.lang.Class对象
JVM中存在3个内置ClassLoader:
- BootstrapClassLoader 启动类加载器 负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.xxx.* 都在里面,比如
java.util.*
、java.io.*
、java.nio.*
、java.lang.*
等等。- ExtensionClassLoader 扩展类加载器 负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中
- AppClassLoader 系统类加载器 才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。
除了Java自带的ClassLoader外,还可以自定义ClassLoader,自定义的ClassLoader都必须继承自java.lang.ClassLoader类,也包括Java提供的另外二个ClassLoader(Extension ClassLoader和App ClassLoader)在内,但是Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。
类加载流程
类加载指的是在.java
文件编译成.class
字节码文件后,当需要使用某个类时,虚拟机将会加载它的.class
文件,将.class
文件读入内存,并在内存中为之创建一个java.lang.Class对象。但是实现步骤看起来会比较空洞和概念化,暂时不去深入研究,理解类加载是做什么的并了解加载过程即可。后续有刚需再去深入。
类加载大致分为三个步骤:加载、连接、初始化。
0x01 加载
类加载指的是将class文件读入内存,并为之创建一个java.lang.Class对象,即程序中使用任何类时,也就是任何类在加载进内存时,系统都会为之建立一个java.lang.Class对象,这个Class对象包含了该类的所有信息,如Filed,Method等,系统中所有的类都是java.lang.Class的实例。
类的加载由类加载器完成,JVM提供的类加载器叫做系统类加载器,此外还可以通过自定义类加载器加载。
通常可以用如下几种方式加载类的二进制数据:
从本地文件系统加载class文件。
从JAR包中加载class文件,如JAR包的数据库启驱动类。
通过网络加载class文件。
把一个Java源文件动态编译并执行加载。
0x02 链接
链接阶段负责把类的二进制数据合并到JRE中,其又可分为如下三个阶段:
- 验证:确保加载的类信息符合JVM规范,无安全方面的问题。
- 准备:为类的静态Field分配内存,并设置初始值。
- 解析:将类的二进制数据中的符号引用替换成直接引用。
0x03 初始化
类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化
双亲委派机制
基本概念
前面提到了Java自带3个ClassLoader,包括我们也可以实现自定义ClassLoader完成类加载,但是具体某个类的加载用的是哪个ClassLoader呢。这里涉及到一个双亲委派机制(PS:这个我看网上有讲的是委托也有是委派,个人觉得委派好听,先这么叫着:D)
这里丢个图,基本概述了双亲委派机制(先走蓝色箭头再走红色箭头)
双亲委派简单理解:向上委派,向下加载
当一个.class
文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader
中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass
方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader
之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader
,已经没有父加载器了,这时候开始考虑自己是否能加载了(向上委派); 如果自己无法加载,会下沉到子加载器去加载,一直到最底层(向下加载)。如果没有任何加载器能加载,就会抛出ClassNotFoundException
异常。
为什么?
那么为什么加载类的时候需要双亲委派机制呢?
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
其次是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。
自定义ClassLoader
先看下ClassLoader这个类中的核心方法
ClassLoader核心方法
loadClass
(加载指定的Java类)一般实现这个方法的步骤是:执行
findLoadedClass(String)
去检测这个class
是不是已经加载过了。
执行父加载器的loadClass
方法。如果父加载器为null则jvm内置的加载器去替代,也就是Bootstrap ClassLoader
。这也解释了ExtClassLoader
的parent
为null,但仍然说Bootstrap ClassLoader
是它的父加载器。如果向上委托父加载器没有加载成功;则通过findClass(String)
查找。
如果class在上面的步骤中找到了,参数resolve又是true的话那么loadClass()
又会调用resolveClass(Class)
这个方法来生成最终的Class对象。
findClass
(查找指定的Java类)
findLoadedClass
(查找JVM已经加载过的类)
defineClass
(定义一个Java类)
resolveClass
(链接指定的Java类)
编写自定义ClassLoader步骤
1、编写一个类继承ClassLoader抽象类;
2、重写findClass()方法;
3、在findClass()方法中调用defineClass()方法即可实现自定义ClassLoader;
0x01 编写测试类
package classloader;
public class test {
public String hello(){
return "hello, CoLoo!";
}
}
0x02 编译为.class
文件
0x03 .class
转换bytes
public class ByteClass {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("test.class");
byte[] classBytes = IOUtils.readFully(fis, -1, false);
System.out.println(Arrays.toString(classBytes));
}
}
Output:
[-54, -2, -70, -66, 0, 0, 0, 52, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0, 16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 9, 116, 101, 115, 116, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 13, 104, 101, 108, 108, 111, 44, 32, 67, 111, 76, 111, 111, 33, 1, 0, 16, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 116, 101, 115, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1, 0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 5, 0, 1, 0, 11, 0, 0, 0, 2, 0, 12]
0x04 自定义ClassLoader
package classloader;
import java.lang.reflect.Method;
public class ClassLoaderTest extends ClassLoader {
private static String className = "classloader.test";
//转换byte后的字节码
private static byte[] classBytes = new byte[]{54, -2, -70, -66, 0, 0, 0, 52, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0, 16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 9, 116, 101, 115, 116, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 13, 104, 101, 108, 108, 111, 44, 32, 67, 111, 76, 111, 111, 33, 1, 0, 16, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 116, 101, 115, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1, 0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 5, 0, 1, 0, 11, 0, 0, 0, 2, 0, 12};
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//只处理classloader.test类
if (name.equals(className)) {
//调用definClass将一个字节流定义为一个类。
return defineClass(className, classBytes, 0, classBytes.length);
}
return super.findClass(name);
}
public static void main(String[] args) throws Exception {
//创建加载器
ClassLoaderTest clt = new ClassLoaderTest();
//使用我们自定义的类去加载className
Class clazz = clt.loadClass(className);
//反射创建test类对象
Object test = clazz.newInstance();
//反射获取方法
Method method = test.getClass().getMethod("hello");
//反射去调用执行方法
String str = (String) method.invoke(test);
System.out.println(str);
}
}
执行了test类的hello方法
一些思考
上面自定义ClassLoader流程也可以小结一下
- 准备自定义类,编译为
.class
文件- 将
.class
文件内容专为bytes数组- 自定义ClassLoader,继承
ClassLoader
类,重写findClass()
方法,在方法中定义对指定类的处理流程- 主函数创建自定义
ClassLoader
对象并loadClass()
指定类,如果自定义的ClassLoader完成了加载则会获得该类的class对象,后续可通过反射来深入利用(如执行某个方法)。
那ClassLoader对于安全来说能做什么?
1、个人这里目前想到的是代替Class.forName()
,通过ClassLoader.loadClass()
获取class对象
@Test
public void classLoaderRuntime() throws Exception {
Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
Runtime runtime = (Runtime) aClass.getMethod("getRuntime").invoke(aClass);
runtime.exec("open -a Calculator");
}
2、加载恶意类
如webshell中(之前有次攻防捕捉到一个webshell里面用到了classloader)或者内存马中应该也可以用到,待研究。
3、... 应该还有很多。
Reference
上面很多内容都是参考下面文章学习到的,可能写的并不是很正确,建议阅读下面这些文章
javasec.org
https://www.cnblogs.com/nice0e3/p/13719903.html
https://blog.csdn.net/javazejian/article/details/73413292
https://blog.csdn.net/CNAHYZ/article/details/82219210
https://blog.csdn.net/briblue/article/details/54973413
https://blog.csdn.net/codeyanbao/article/details/82875064
Java安全之ClassLoader的更多相关文章
- Java 类加载机制 ClassLoader Class.forName 内存管理 垃圾回收GC
[转载] :http://my.oschina.net/rouchongzi/blog/171046 Java之类加载机制 类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指 ...
- 【Java核心】ClassLoader原理及其使用
又把博客的皮肤换了换,看着更加简洁舒心一些.前段的知识只是略懂,拿过来就能用,只是自己的审美和设计水平有限,实在难以弄出自己特别满意的东西,也算是小小的遗憾吧!言归正传,由于最近涉及到Java核心的东 ...
- 深入理解Java类加载器(ClassLoader)
深入理解Java类加载器(ClassLoader) Java学习记录--委派模型与类加载器 关于Java类加载双亲委派机制的思考(附一道面试题) 真正理解线程上下文类加载器(多案例分析) [jvm解析 ...
- 深入理解Java类加载器(ClassLoader) (转)
转自: http://blog.csdn.net/javazejian/article/details/73413292 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Ja ...
- Android解析ClassLoader(一)Java中的ClassLoader
Android解析ClassLoader(一)Java中的ClassLoader
- Java安全之 ClassLoader类加载器
Java安全之 ClassLoader类加载器 0x00 前言 前面这里抛出一个问题,Java到底是什么类型的编程语言?是编译型?还是解释型?在这个问题是其实一直都都有疑惑,如果说是解释型语言的话,那 ...
- 浅析java类加载器ClassLoader
作为一枚java猿,了解类加载器是有必要的,无论是针对面试还是自我学习. 本文从JDK提供的ClassLoader.委托模型以及如何编写自定义的ClassLoader三方面对ClassLoader做一 ...
- 运用加密技术保护Java源代码/定制ClassLoader
为什么要加密? 对于传统的C或C++之类的语言来说,要在Web上保护源代码是很容易的,只要不发布它就可以.遗憾的是,Java程序的源代码很容易被别人偷看.只要有一个反编译器,任何人都可以分析别人的代码 ...
- Java中的ClassLoader
Java中类的加载过程(如Dog类): 通过类型信息定位Dog.class文件. 载入Dog.class文件,创建相应的Class对象. 执行父类的静态字段定义时初始化语句和父类的静态初始化块 ...
随机推荐
- LeetCoded第739题题解--每日温度
每日温度 请根据每日 气温 列表,重新生成一个列表.对应位置的输出为:要想观测到更高的气温,至少需要等待的天数.如果气温在这之后都不会升高,请在该位置用 0 来代替. 例如,给定一个列表 temper ...
- java基本数据类型转换字符串
1.基本数据类型转换为字符串 int t1 = 2; String t2 = Integer.toString(t1); 2.字符串转换为基本数据类型 int t3 = Integer.parseIn ...
- js判断checkbox是否选中 .checked不管用
今天开发遇到一个小问题,记小本本记小本本 document.getElementById("id").checked //正确 //如果返回值为true代表选中 //如果返回值为f ...
- Ztree 树插件 树节点名称太长的解决方案
样式允许的情况下 给背景div加滚动条.. 或者使用省略号方法:使用addDiyDom http://blog.csdn.net/zhengbo0/article/details/17759543 ...
- BufferedReader 和BufferedWriter
BufferedWriter: private void test(String content,String destPath) throws IOException { BufferedReade ...
- Git工具的使用教程二
1.3时光穿梭机--版本回退 版本回退分为两步骤进行操作: 步骤: 1.查看版本,确定需要回到的时候点 指令: git log git log ...
- 关于Quartus构建nios软核以及eclipse建立c语言工程以及成功下载到FPGA芯片过程遇到的各种问题以及解决方法详解
这不是一篇构建nios的教程,而是遇到的各种问题以及解决方法.至于构建教程,网上一大把,我推荐正点原子的FPGA教程,比较新,比较详细,通俗易懂!!! 这里以一个点亮LED灯的Nios软核为例,很明显 ...
- Oracle环境配置之山路十八弯
Oracle数据库的安装 背景: 因为疫情原因,只能在家上网课,学习Oracle的时候需要安装数据库,但是!! 安装的时候出现了报错: 无法检查指定的位置是否位于 CFS 上. 出来这个基本无解,这个 ...
- 从零开始实现简单 RPC 框架 7:网络通信之自定义协议(粘包拆包、编解码)
当 RPC 框架使用 Netty 通信时,实际上是将数据转化成 ByteBuf 的方式进行传输. 那如何转化呢?可不可以把 请求参数 或者 响应结果 直接无脑序列化成 byte 数组发出去? 答:直接 ...
- Charles-抓取https请求
在未经设置之前,Charles是无法抓取https请求的,会出现unknown的标识.我们可以通过以下两步设置,解决该问题. 第一步:安装证书 https是在http的基础上加入ssl层,通过ssl来 ...