Java反序列化基础篇-类加载器

admin 2022年6月7日23:30:36代码审计评论8 views4547字阅读15分9秒阅读模式

Java反序列化基础篇-类加载器

0x01 前言

这篇文章/笔记的话,打算从类加载器,双亲委派到代码块的加载顺序这样来讲。最后才是加载字节码。

0x02 类加载器及双亲委派

  • 说类加载器有些师傅可能没听过,但是说 Java ClassLoader,相信大家耳熟能详。

1. 类加载器有什么用

  • 加载 Class 文件

以这段简单代码为例

Student student = new Student();

我们知道,Student 本身其实是一个抽象类,是通过 new 这个操作,将其实例化的,类加载器做的便是这个工作。

ClassLoader 的工作如图所示
Java反序列化基础篇-类加载器

加载器也分多种加载器,每个加载器负责不同的功能。

主要分为这四种加载器

  1. 虚拟机自带的加载器

  2. 启动类(根)加载器

  3. 扩展类加载器

  4. 应用程序加载器

2. 几种加载器

引导类加载器

引导类加载器(BootstrapClassLoader),底层原生代码是 C++ 语言编写,属于 JVM 一部分。

不继承java.lang.ClassLoader类,也没有父加载器,主要负责加载核心 java 库(即 JVM 本身),存储在/jre/lib/rt.jar目录当中。(同时处于安全考虑,BootstrapClassLoader只加载包名为javajavaxsun等开头的类)。

扩展类加载器(ExtensionsClassLoader)

扩展类加载器(ExtensionsClassLoader),由sun.misc.Launcher$ExtClassLoader类实现,用来在/jre/lib/ext或者java.ext.dirs中指明的目录加载 java 的扩展库。Java 虚拟机会提供一个扩展库目录,此加载器在目录里面查找并加载 java 类。

App类加载器(AppClassLoader)

App类加载器/系统类加载器(AppClassLoader),由sun.misc.Launcher$AppClassLoader实现,一般通过通过(java.class.path或者Classpath环境变量)来加载 Java 类,也就是我们常说的 classpath 路径。通常我们是使用这个加载类来加载 Java 应用类,可以使用ClassLoader.getSystemClassLoader()来获取它。

3. 双亲委派机制

  • 在 Java 开发当中,双亲委派机制是从安全角度出发的。

我们这里以代码先来感受一下,双亲委派机制确实牛逼。

从报错的角度感受双亲委派机制

  • 尽量别尝试,看看就好了。要不然整个文件夹挺乱的,如果想上手尝试一下的话,我建议是新建一个项目,不要把其他的文件放一起。

新建一个 java.lang的文件夹,在其中新建 String.java的文件。

String.java

package java.lang;  

// 双亲委派的错误代码
public class String {

public String toString(){
return "hello";
}

public static void main(String[] args) {
String s = new String();
s.toString();
}
}

看着是不是没有问题,没有错误吧?
我们自己定义了一个java.lang的文件夹,并在文件夹中定义了 String.class,还定义了 String 这个类的 toString 方法。我们跑一下程序。(这里如果把 Stirng 类放到其他文件夹会直接报错,原因也是和下面一样的)

  • 结果居然报错了!而且非常离谱

Java反序列化基础篇-类加载器

我这不是已经定义了 main 方法吗??为什么还会报错,这里就提到双亲委派机制了,双亲委派机制是从安全角度出发的。

首先,我们要知道 Java 的类加载器是分很多层的,如图。

Java反序列化基础篇-类加载器

我们的类加载器在被调用时,也就是在 new class 的时候,它是以这么一个顺序去找的 BOOT ---> EXC ----> APP

如果 BOOT 当中没有,就去 EXC 里面找,如果 EXC 里面没有,就去 APP 里面找。

  • 所以我们之前报错的程序当中,定义的java.lang.String在 BOOT 当中是有的,所以我们自定义 String 时,会报错,如果要修改的话,是需要去 rt.jar 里面修改的,这里就不展开了。

从正确的角度感受双亲委派机制

前文提到我们新建的java.lang.String报错了,是因为我们定义的 String 和 BOOT 包下面的 String 冲突了,所以才会报错,我们这里定义一个 BOOT 和 EXC 都没有的对象试一试。

在其他的文件夹下,新建 Student.java

Student.java

package src.DynamicClassLoader;  

// 双亲委派的正确代码
public class Student {

public String toString(){
return "Hello";
}

public static void main(String[] args) {
Student student = new Student();

System.out.println(student.getClass().getClassLoader());
System.out.println(student.toString());
}
}

并把加载器打印出来

Java反序列化基础篇-类加载器
我们定义的 Student 类在 APP 加载器中找到了。

0x03 各场景下代码块加载顺序

  • 这里的代码块主要指的是这四种

    • 静态代码块:static{}

    • 构造代码块:{}

    • 无参构造器:ClassName()

    • 有参构造器:ClassName(String name)

场景一、实例化对象

这里有两个文件,分别介绍一下用途:

  • Person.java:一个普普通通的类,里面有静态代码块、构造代码块、无参构造器、有参构造器、静态成员变量、普通成员变量、静态方法。

  • Main.java:启动类

Person.java

package src.DynamicClassLoader;  

// 存放代码块
public class Person {
public static int staticVar;
public int instanceVar;

static {
System.out.println("静态代码块");
}

{
System.out.println("构造代码块");
}

Person(){
System.out.println("无参构造器");
}
Person(int instanceVar){
System.out.println("有参构造器");
}

public static void staticAction(){
System.out.println("静态方法");
}
}

Main.java

package src.DynamicClassLoader;  

// 代码块的启动器
public class Main {
public static void main(String[] args) {
Person person = new Person();
}
}

运行结果如图

Java反序列化基础篇-类加载器

  • 结论:

通过new关键字实例化的对象,先调用静态代码块,然后调用构造代码块,最后根据实例化方式不同,调用不同的构造器。

场景二、调用静态方法

直接调用类的静态方法

Person.java 不变,修改 Main.java 启动器即可。

Main.java

package src.DynamicClassLoader;  

// 代码块的启动器
public class Main {
public static void main(String[] args) {
Person.staticAction();
}
}

Java反序列化基础篇-类加载器

  • 结论:

不实例化对象直接调用静态方法,会先调用类中的静态代码块,然后调用静态方法

场景三、对类中的静态成员变量赋值

Main.java

package src.DynamicClassLoader;  

// 代码块的启动器
public class Main {
public static void main(String[] args) {
Person.staticVar = 1;
}
}

Java反序列化基础篇-类加载器

  • 结论:

在对静态成员变量赋值前,会调用静态代码块

场景四、使用 class 获取类

package src.DynamicClassLoader;  

// 代码块的启动器
public class Main {
public static void main(String[] args) {
Class c = Person.class;
}
}

// 空屁
  • 结论:

利用class关键字获取类,并不会加载类,也就是什么也不会输出。

场景五、使用 forName 获取类

  • 这里要抛出异常一下。

我们写三种forName的方法调用。
修改 Main.java

package src.DynamicClassLoader;  

// 代码块的启动器
public class Main {
public static void main(String[] args) throws ClassNotFoundException{
Class.forName("src.DynamicClassLoader.Person");
}
}
// 静态代码块
package src.DynamicClassLoader;  

// 代码块的启动器
public class Main {
public static void main(String[] args) throws ClassNotFoundException{
Class.forName("src.DynamicClassLoader.Person", true, ClassLoader.getSystemClassLoader());
}
}
// 静态代码块
package src.DynamicClassLoader;  

// 代码块的启动器
public class Main {
public static void main(String[] args) throws ClassNotFoundException{
Class.forName("src.DynamicClassLoader.Person", false, ClassLoader.getSystemClassLoader());
}
}
//没有输出
  • 结论:

Class.forName(className)Class.forName(className, true, ClassLoader.getSystemClassLoader())等价,这两个方法都会调用类中的静态代码块,如果将第二个参数设置为false,那么就不会调用静态代码块

场景六、使用 ClassLoader.loadClass() 获取类

Main.java

package com.xiinnn.i.test;

public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("com.xiinnn.i.test.Person", false, ClassLoader.getSystemClassLoader());
}
}
//没有输出
  • 结论:

ClassLoader.loadClass()方法不会进行类的初始化,当然,如果后面再使用newInstance()进行初始化,那么会和场景一、实例化对象一样的顺序加载对应的代码块。

该内容转载自网络,更多内容请点击“阅读原文”

Java反序列化基础篇-类加载器



原文始发于微信公众号(web安全工具库):Java反序列化基础篇-类加载器

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月7日23:30:36
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  Java反序列化基础篇-类加载器 http://cn-sec.com/archives/1093182.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: