java.lang.ClassLoader是所有的类加载器的父类,java.lang.ClassLoader有非常多的子类加载器,比如我们用于加载jar包的java.net.URLClassLoader其本身通过继承java.lang.ClassLoader类,重写了findClass方法从而实现了加载目录class文件甚至是远程资源文件。
    既然已知ClassLoader具备了加载类的能力,那么我们不妨尝试下写一个自己的类加载器来实现加载自定义的字节码(这里以加载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. /**
    4. * Creator: yz
    5. * Date: 2019/12/17
    6. */
    7. public class TestClassLoader extends ClassLoader {
    8. // TestHelloWorld类名
    9. private static String testClassName = "com.anbai.sec.classloader.TestHelloWorld";
    10. // TestHelloWorld类字节码
    11. private static byte[] testClassBytes = new byte[]{
    12. -54, -2, -70, -66, 0, 0, 0, 51, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0,
    13. 16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
    14. 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101,
    15. 1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108,
    16. 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99,
    17. 101, 70, 105, 108, 101, 1, 0, 19, 84, 101, 115, 116, 72, 101, 108, 108, 111, 87, 111,
    18. 114, 108, 100, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 12, 72, 101, 108, 108, 111,
    19. 32, 87, 111, 114, 108, 100, 126, 1, 0, 40, 99, 111, 109, 47, 97, 110, 98, 97, 105, 47,
    20. 115, 101, 99, 47, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 84, 101, 115,
    21. 116, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 16, 106, 97, 118, 97, 47, 108,
    22. 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1,
    23. 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,
    24. 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,
    25. 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,
    26. 0, 0, 0, 2, 0, 12
    27. };
    28. @Override
    29. public Class<?> findClass(String name) throws ClassNotFoundException {
    30. // 只处理TestHelloWorld类
    31. if (name.equals(testClassName)) {
    32. // 调用JVM的native方法定义TestHelloWorld类
    33. return defineClass(testClassName, testClassBytes, 0, testClassBytes.length);
    34. }
    35. return super.findClass(name);
    36. }
    37. public static void main(String[] args) {
    38. // 创建自定义的类加载器
    39. TestClassLoader loader = new TestClassLoader();
    40. try {
    41. // 使用自定义的类加载器加载TestHelloWorld类
    42. Class testClass = loader.loadClass(testClassName);
    43. // 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld();
    44. Object testInstance = testClass.newInstance();
    45. // 反射获取hello方法
    46. Method method = testInstance.getClass().getMethod("hello");
    47. // 反射调用hello方法,等价于 String str = t.hello();
    48. String str = (String) method.invoke(testInstance);
    49. System.out.println(str);
    50. } catch (Exception e) {
    51. e.printStackTrace();
    52. }
    53. }
    54. }

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