利用ClassLoader#defineClass直接加载字节码
不管是加载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用
-
loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行findClass
-
findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass
-
defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
// name 为类名 (可设置为 null)
// b 为字节码数组
// off 为数组的偏移值 (从第几位开始为字节码数据)
// len 为数组的长度
-
所以可见,真正核心的部分其实是defineClass ,他决定了如何将一段字节流转变成一个Java类,Java默认的ClassLoader#defineClass 是一个native方法,逻辑在JVM的C语言代码中。
-
注意一点,在defineClass 被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在defineClass 时也无法被直接调用到。所以,如果我们要使用defineClass 在目标机器上执行任意代码,需要想办法调用构造函数。
-
如下示例,因为系统的ClassLoader#defineClass 是一个保护属性,所以我们无法直接在外部访问,不得不使用反射的形式来调用。在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我们常用的一个攻击链TemplatesImpl 的基石。
获取 ClassLoader, 以下是常用几种获取 ClassLoader 的方式
ClassLoader loader = Thread.currentThread().getContextClassLoader();
ClassLoader loader = ClassLoader.getSystemClassLoader();
ClassLoader loader = this.getClass().getClassLoader();
举例:
public class HelloDefineClass {
public static void main(String[] args) throws Exception {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); // 注意 getDeclaredMethod 只能获取当前类的所有方法, 而 defineClass 其实是在 AppClassLoader 的父类 java.lang.ClassLoader 里面, 所以需要通过 ClassLoader.class 来获取 Class 对象
defineClass.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
hello.newInstance();
}
}