类加载机制

Sl0th Lv4

类加载

基本术语

类加载器

类加载器:通过一个类全限定名称来获取其二进制文件(.class)流的工具。

类加载器都是抽象类ClassLoader的子类

java类编译后的.class文件开头字节码是cafe babe ,字节码被ClassLoder加载到JVM中

Bootstrap ClassLoader 最顶层ClassLoader

即使一个ClassLoader继承了ClassLoader其父类也是AppClassLoader

JVM框架图

image-20221009155611940
image-20221009155611940

编译

1
javac TestHelloWorld.java

反汇编生成字节码

1
javap -c TestHelloWorld

JVM在执行TestHelloWorld之前会先解析class二进制内容,JVM执行的其实就是如上javap命令生成的字节码。

ClassLoder

一切的Java类都必须经过JVM加载后才能运行,而ClassLoader的主要作用就是Java类文件的加载。在JVM类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器)Extension ClassLoader(扩展类加载器)App ClassLoader(系统类加载器)AppClassLoader是默认的类加载器,如果类加载时我们不指定类加载器的情况下,默认会使用AppClassLoader加载类,ClassLoader.getSystemClassLoader()返回的系统类加载器也是AppClassLoader

获取类加载器时有时会返回一个null对象,可能是该类被JVM初始化是被Bootstrap ClassLoader(引导类加载器)加载(该类加载器实现于JVM层,采用C++编写,获取该类加载器加载的类的ClassLoader时会返回null

ClassLoader类有如下核心方法:

  1. loadClass(加载指定的Java类)
  2. findClass(查找指定的Java类)
  3. findLoadedClass(查找JVM已经加载过的类)
  4. defineClass(定义一个Java类)
  5. resolveClass(链接指定的Java类)

类加载流程

ClassLoader加载com.anbai.sec.classloader.TestHelloWorld类重要流程如下:

  • ClassLoader调用public Class<?> loadClass(String name)方法加载com.anbai.sec.classloader.TestHelloWorld类。
  • 调用findLoadedClass方法检查TestHelloWorld类是否已经初始化,如果JVM已初始化过该类则直接返回类对象。
  • 如果创建当前ClassLoader时传入了父类加载器(new ClassLoader(父类加载器))就使用父类加载器加载TestHelloWorld类,否则使用JVM的Bootstrap ClassLoader加载。
  • 如果上一步无法加载TestHelloWorld类,那么调用自身的findClass方法尝试加载TestHelloWorld类。
  • 如果当前的ClassLoader没有重写了findClass方法,那么直接返回类加载失败异常。如果当前类重写了findClass方法并通过传入的com.anbai.sec.classloader.TestHelloWorld类名找到了对应的类字节码,那么应该调用defineClass方法去JVM中注册该类。
  • 如果调用loadClass的时候传入的resolve参数为true,那么还需要调用resolveClass方法链接类,默认为false。
  • 返回一个被JVM加载后的java.lang.Class类对象。

自定义ClassLoader

java.lang.ClassLoader是所有的类加载器的父类,其子类加载器通过继承其并重写了findClass方法实现了新的功能

因此我们也可以通过重写findClass方法来自定义一个类加载器

自定义类demo.ClassloaderDemo.TestHelloWorld

1
2
3
4
5
6
7
8
package demo.ClassLoaderDemo;

public class TestHelloWorld {
public String hello(){
String a="小雨奕奕,永远热恋";
return a;
}
}

自定义类加载器

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
package demo.ClassLoaderDemo;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestClassLoader extends ClassLoader{
private static String testClassName = "demo.ClassLoaderDemo.TestHelloWorld";
private static byte[] testClassBytes = 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,19,84,101,115,116,72,101,108,108,111,87,111,114,108,100,46,106,97,118,97,12,0,5,0,6,1,0,12,72,101,108,108,111,32,87,111,114,108,100,126,1,0,35,100,101,109,111,47,67,108,97,115,115,76,111,97,100,101,114,68,101,109,111,47,84,101,115,116,72,101,108,108,111,87,111,114,108,100,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,6,0,1,0,11,0,0,0,2,0,12,
};// TestHelloWorld类字节码
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// 只处理TestHelloWorld类
if (name.equals(testClassName)) {
// 调用JVM的native方法定义TestHelloWorld类
return defineClass(testClassName, testClassBytes, 0, testClassBytes.length);
}

return super.findClass(name);
}

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException {
TestClassLoader a = new TestClassLoader();
try {//反射获取hello方法调用
Class<?> aClass = a.loadClass(testClassName);
Object o = aClass.newInstance();
Method hello = o.getClass().getMethod("hello");
String invoke = (String)hello.invoke(o);
System.out.println(invoke);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}

}
}

image-20221009165342719
image-20221009165342719

利用自定义类加载器我们可以在webshell中实现加载并调用自己编译的类对象,比如本地命令执行漏洞调用自定义类字节码的native方法绕过RASP检测,也可以用于加密重要的Java类字节码(只能算弱加密了)。

URLClassLoader

URLClassLoader继承了ClassLoaderURLClassLoader提供了加载远程资源的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用。

TODO….

  • 标题: 类加载机制
  • 作者: Sl0th
  • 创建于 : 2022-07-02 00:04:37
  • 更新于 : 2024-11-11 18:23:06
  • 链接: http://sl0th.top/2022/07/02/类加载机制/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论