jdk17反射报错解决方法

利用Unsafe绕过JDK17+对反射的限制

JDK17+反射限制绕过 (pankas.top)

上述原因参考https://docs.oracle.com/en/java/javase/17/migrate/migrating-jdk-8-later-jdk-releases.html#GUID-7BB28E4D-99B3-4078-BDC4-FC24180CE82BJDK 17启动了强封装,java.的非公共字段和方法都无法反射获取调用了。

关于jdk9之后的module机制参考 https://openjdk.org/jeps/200

但注意原文提到

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

sun.misc和sun.reflect包下的我们是可以正常反射的,所以有个关键的类就可以拿来用来,就是Unsafe这个东西

关于Unsafe类可以参考https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html

同时注意 JDK17下Unsafe类下的defineClass和defineAnonymousClass已被移除,且从jdk9开始存在的另一个Unsafe类jdk.internal.misc.Unsafe也是强封装的,和java.包下的一样。如何利用Unsafe来打破这个强封装module限制呢?调试下源码,定位到关键的setAccessible这个方法

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

我们给非public字段或方法设置访问权限为true时会调用checkCanSetAccessible去检查对应的类。执行checkCanSetAccessible方法后最终关键的代码位于

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

可以看到这里是判断我们是否有权限去修改目标字段或方法的访问权限。

只要判断我们调用者类和目标类是一个module,或者调用类的module和Object类的module一样,就可以有修改权限

img

*那我们可以尝试利用Unsafe来修改当前类的module属性和java.*下类的module属性一致来绕过

img

Unsafe类中有个getAndSetObject方法,其和反射赋值功能差不多,利用这个修改调用类的module

img代码如下

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

这里面的文章已经写的很清楚了所以我也没必要再写一遍