浅析高版本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;
}

反射相关模块指令

  1. opens package
    指定某个包下所有public类在运行时可被反射,且所有类成员(包括private)都可被访问

  2. opens package to modules
    指定特定模块才能对包进行反射操作

  3. 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方法主要检查:

  1. 调用者与被调用者是否同模块
  2. 调用者是否是Object类的模块
  3. 被调用类是否在未命名模块中
  4. 类是否为public且包已导出给调用者
  5. 包是否对调用者开放

利用Unsafe修改模块信息

关键步骤:

  1. 获取当前类和java.base模块
  2. 使用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.misc
  • sun.reflect

这两个包被显式开放用于反射调用,这是官方预留的兼容性方案。

总结

  1. JDK8-11:可使用Unsafe.defineAnonymousClass或直接反射ClassLoader.defineClass(有警告)

  2. JDK17+

    • 直接反射ClassLoader.defineClass会被模块系统阻止
    • 需先使用Unsafe将当前类模块修改为java.base
    • 或利用官方预留的sun.misc/sun.reflect
  3. 长期趋势:Oracle正在逐步收紧反射权限,开发者应尽快适配模块化系统,减少对内部API的依赖。

高版本JDK反射类加载问题深度解析 前言 随着JDK9引入Java平台模块系统(JPMS, Java Platform Module System),反射类加载机制发生了重大变化。本文将从模块系统原理出发,详细分析高版本JDK(特别是JDK17+)中的反射限制及其绕过方法。 JDK模块系统基础 模块访问权限 JDK9引入模块概念后,class的访问权限(public/protected/private/包访问)仅在模块内有效。模块间访问需要显式导出: 反射相关模块指令 opens package 指定某个包下所有public类在运行时可被反射,且所有类成员(包括private)都可被访问 opens package to modules 指定特定模块才能对包进行反射操作 open module moduleName{} 允许外部模块对该模块下所有类进行反射操作 反射类加载的版本差异 JDK11环境 表现 :提示非法反射操作警告,但不影响字节码加载 JDK17+环境 变化 : 强封装机制直接禁止非法反射 报错提示: java.base模块中的java.lang包没有对未命名模块开放反射 Unsafe类的演变 方法变化 | JDK版本 | defineClass | defineAnonymousClass | |---------|-------------|-----------------------| | JDK8 | 存在 | 存在 | | JDK11 | 移除 | 存在 | | JDK17+ | 移除 | 移除 | JDK8-11的用法 JDK17+的绕过方法 模块权限检查原理 checkCanSetAccessible 方法主要检查: 调用者与被调用者是否同模块 调用者是否是Object类的模块 被调用类是否在未命名模块中 类是否为public且包已导出给调用者 包是否对调用者开放 利用Unsafe修改模块信息 关键步骤: 获取当前类和java.base模块 使用Unsafe修改当前类的module字段 官方预留的后门 JDK21中 jdk.unsupported 模块的 module-info 显示: sun.misc sun.reflect 这两个包被显式开放用于反射调用,这是官方预留的兼容性方案。 总结 JDK8-11 :可使用 Unsafe.defineAnonymousClass 或直接反射 ClassLoader.defineClass (有警告) JDK17+ : 直接反射 ClassLoader.defineClass 会被模块系统阻止 需先使用Unsafe将当前类模块修改为 java.base 或利用官方预留的 sun.misc / sun.reflect 包 长期趋势 :Oracle正在逐步收紧反射权限,开发者应尽快适配模块化系统,减少对内部API的依赖。