浅析高版本JDK反射类加载问题
字数 1296 2025-08-22 18:37:22
高版本JDK反射类加载问题深度解析
前言
随着JDK9引入Java平台模块系统(JPMS, Java Platform Module System),反射类加载机制发生了重大变化。本文将从模块系统原理出发,详细分析高版本JDK(特别是JDK17+)中的反射限制及其绕过方法。
JDK模块系统基础
模块访问权限
JDK9引入模块概念后,class的访问权限(public/protected/private/包访问)仅在模块内有效。模块间访问需要显式导出:
module hello.world {
exports com.itranswarp.sample;
requires java.base;
requires java.xml;
}
反射相关模块指令
-
opens package
指定某个包下所有public类在运行时可被反射,且所有类成员(包括private)都可被访问 -
opens package to modules
指定特定模块才能对包进行反射操作 -
open module moduleName{}
允许外部模块对该模块下所有类进行反射操作
反射类加载的版本差异
JDK11环境
// 示例代码
String evilClassBase64 = "xxxx";
byte[] bytes = Base64.getDecoder().decode(evilClassBase64);
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
Class cc = (Class) method.invoke(new MLet(new URL[0], Main.class.getClassLoader()), bytes, new Integer(0), new Integer(bytes.length));
cc.newInstance();
表现:提示非法反射操作警告,但不影响字节码加载
JDK17+环境
变化:
- 强封装机制直接禁止非法反射
- 报错提示:
java.base模块中的java.lang包没有对未命名模块开放反射
Unsafe类的演变
方法变化
| JDK版本 | defineClass | defineAnonymousClass |
|---|---|---|
| JDK8 | 存在 | 存在 |
| JDK11 | 移除 | 存在 |
| JDK17+ | 移除 | 移除 |
JDK8-11的用法
Field field = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
unsafe.defineAnonymousClass(Class.class, bytes, null).newInstance();
JDK17+的绕过方法
模块权限检查原理
checkCanSetAccessible方法主要检查:
- 调用者与被调用者是否同模块
- 调用者是否是Object类的模块
- 被调用类是否在未命名模块中
- 类是否为public且包已导出给调用者
- 包是否对调用者开放
利用Unsafe修改模块信息
关键步骤:
- 获取当前类和java.base模块
- 使用Unsafe修改当前类的module字段
String evilClassBase64 = "xxxx";
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为java.base
Module baseModule = Object.class.getModule();
Class currentClass = Main.class;
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.putObject(currentClass, offset, baseModule);
// 执行反射加载
Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
method.setAccessible(true);
((Class) method.invoke(ClassLoader.getSystemClassLoader(), bytes, 0, bytes.length)).newInstance();
官方预留的后门
JDK21中jdk.unsupported模块的module-info显示:
sun.miscsun.reflect
这两个包被显式开放用于反射调用,这是官方预留的兼容性方案。
总结
-
JDK8-11:可使用
Unsafe.defineAnonymousClass或直接反射ClassLoader.defineClass(有警告) -
JDK17+:
- 直接反射
ClassLoader.defineClass会被模块系统阻止 - 需先使用Unsafe将当前类模块修改为
java.base - 或利用官方预留的
sun.misc/sun.reflect包
- 直接反射
-
长期趋势:Oracle正在逐步收紧反射权限,开发者应尽快适配模块化系统,减少对内部API的依赖。