高版本JDK加载字节码分析
字数 1291 2025-08-20 18:17:53
Java高版本动态加载字节码技术分析与绕过
1. JDK8及以下版本的动态加载字节码
在JDK8及以下版本中,可以通过反射调用ClassLoader#defineClass方法动态加载字节码:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;
public class evilByteClassloader {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
String evilClassBase64="Base64 encoding of malicious bytecode";
byte[] decode = Base64.getDecoder().decode(evilClassBase64);
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class evilClassloader = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), decode, 0, decode.length);
evilClassloader.newInstance();
}
}
这种方法可以执行任意字节码,存在严重的安全风险。
2. JDK9的模块化系统(Project Jigsaw)
JDK9引入了模块化系统,主要变化:
- 模块定义:模块是具有明确边界的代码单元,包含一组相关的包、类和资源
- 模块描述文件:
module-info.java声明模块名称、依赖关系和导出的包 - JDK自身模块化:如
java.base(核心类库)、java.sql、java.xml等
示例模块定义:
module com.example.myModule {
exports com.example.myModule.api; // 导出公共API
requires java.sql; // 声明依赖
}
在JDK9中,上述动态加载字节码的代码仍然可以运行,但会输出警告:
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by EvilClassLoader.evilByteClassloader...
WARNING: Please consider reporting this to the maintainers...
WARNING: Use --illegal-access=warn to enable warnings...
WARNING: All illegal access operations will be denied in a future release
3. JDK17的强封装(Strong Encapsulation)
JDK17开始实施强封装,默认禁止对内部API的反射访问。尝试反射调用defineClass会抛出异常:
Exception in thread "main" java.lang.reflect.InaccessibleObjectException:
Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(byte[],int,int) throws java.lang.ClassFormatError accessible:
module java.base does not "opens java.lang" to unnamed module @404b9385
原因:
defineClass方法位于java.base模块的java.lang包java.base模块没有使用opens指令开放java.lang包给未命名模块
4. 模块系统关键概念
- module:声明一个模块,如
module com.example.myModule {} - requires:声明模块依赖,如
requires com.example.otherModule; - exports:声明公开的包,如
exports com.example.myModule.api; - opens:允许反射访问指定的包,如
opens com.example.myModule.internal to com.example.otherModule;
5. JDK17+的绕过技术
5.1 绕过原理
通过修改调用类的Module属性,使其与ClassLoader类同属一个模块。关键代码逻辑在AccessibleObject.checkCanSetAccessible()中:
private boolean checkCanSetAccessible(Class<?> caller, Class<?> declaringClass, boolean throwExceptionIfDenied) {
if (caller == MethodHandle.class) {
throw new IllegalCallerException();
}
Module callerModule = caller.getModule();
Module declaringModule = declaringClass.getModule();
// 关键条件:如果调用者和声明者在同一模块,则允许访问
if (callerModule == declaringModule) return true;
// 其他条件省略...
}
5.2 使用Unsafe类修改Module属性
sun.misc.Unsafe类提供了底层内存操作能力:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;
public class JDKbypass {
public static void main(String[] args) throws Exception {
String evilClassBase64 = "Base64 encoding of malicious bytecode";
byte[] bytes = Base64.getDecoder().decode(evilClassBase64);
// 获取Unsafe实例
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
// 获取java.base模块
Module baseModule = Object.class.getModule();
Class currentClass = JDKbypass.class;
// 获取Class对象中module字段的偏移量
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
// 修改当前类的module属性
unsafe.putObject(currentClass, offset, baseModule);
// 或者使用:unsafe.getAndSetObject(currentClass, offset, baseModule);
// 现在可以正常反射调用defineClass
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();
}
}
5.3 Unsafe关键方法
-
objectFieldOffset:获取字段在对象中的偏移量
public long objectFieldOffset(Field f) -
getAndSetObject:原子性地获取并设置对象字段值
public final Object getAndSetObject(Object o, long offset, Object newValue) -
putObject:直接设置对象字段值
public void putObject(Object o, long offset, Object x)
6. 防御措施
- 升级到最新JDK:使用JDK17+并保持更新
- 启用Security Manager:限制敏感操作
- 模块化应用:使用
module-info.java明确声明依赖和开放范围 - 监控反射调用:检测异常的反射操作
- 代码审计:检查是否使用了
Unsafe等危险类
7. 总结
Java从JDK9到JDK17逐步加强了模块化和封装性,但通过Unsafe等底层API仍可能绕过这些限制。开发者应了解这些技术原理,既可用于高级开发场景,也能更好地防御潜在的安全风险。