Spring 3 版本内存马植入难题与突破思路
字数 1433 2025-08-29 08:30:06
Spring 3版本内存马植入难题与突破思路
前言
在Spring 3版本下植入内存马的主要难点在于反射限制,因为Spring 3要求JDK 17+,而JDK 17对反射有更严格的限制。本文将详细分析这一问题及其突破思路。
JDK反射历史变化
不同JDK版本的反射行为
-
JDK 8及以下:
- 可以自由反射访问非公共字段和方法
- 使用
setAccessible(true)可以绕过访问限制
-
JDK 9-16:
- 对
java.*包中的非公共字段和方法进行反射时会出现非法访问警告 - 但仍能成功执行
- 对
-
JDK 17+:
- 引入了更强的封装机制
- 默认禁止对
java.*包中非公共字段和方法的反射访问 - 会抛出
InaccessibleObjectException异常
问题分析
核心问题
在JDK 17+环境下,传统的反射加载字节码方式会失败,因为:
ClassLoader#defineClass方法是受保护的- 无法通过反射调用非公共方法
调试分析
关键限制点在setAccessible方法中的checkCanSetAccessible检查,其判断逻辑如下:
-
相同模块下的访问:
- 调用者类和目标类在同一个模块
- 或调用者模块与
Object类的模块相同
-
公共类且包导出:
- 声明类是public
- 该类所在的包被导出给调用者模块
- 成员是public
-
protected static成员且调用者为子类:
- 成员是protected且static
- 调用者类是声明类的子类
-
包open给调用者模块:
- 声明类的包通过open关键字开放给调用者模块
突破思路:利用Unsafe类
Unsafe类简介
sun.misc.Unsafe是Java中一个特殊的类,提供底层操作能力:
- 直接访问和操作JVM内部内存
- 执行内存分配、指针操作等
- 在JDK 17中仍可反射访问
具体实现方案
-
JDK 11及以下:
- 使用
Unsafe#defineClass或defineAnonymousClass加载字节码 - 但在JDK 17中这些方法已被移除
- 使用
-
JDK 17解决方案:
- 使用
Unsafe修改当前类的module属性 - 使其与
java.*下类的module属性一致
- 使用
关键代码实现
// 获取Unsafe实例
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
// 获取当前类的module字段
Class<?> clazz = Class.forName("java.lang.Module");
Field implModuleField = Class.class.getDeclaredField("module");
long moduleOffset = unsafe.objectFieldOffset(implModuleField);
// 获取Object类的module
Class<?> objectClass = Object.class;
Object objectModule = unsafe.getObject(objectClass, moduleOffset);
// 修改当前类的module为Object的module
unsafe.putObject(Test.class, moduleOffset, objectModule);
原理说明
- 通过
Unsafe#objectFieldOffset获取module字段偏移量 - 使用
Unsafe#getObject获取Object类的module - 使用
Unsafe#putObject将当前类的module修改为Object的module - 这样就能满足"调用者模块与Object类的模块相同"的条件
完整利用流程
- 生成恶意类的字节码
- 获取Unsafe实例
- 修改调用类的module为Object的module
- 反射调用
ClassLoader#defineClass加载字节码 - 实例化并执行恶意类
注意事项
- 模块化系统的限制是严格的,必须完全满足条件
- Unsafe的使用在不同JDK版本中可能有差异
- 该方法依赖于Unsafe的内部实现,未来版本可能失效
- 实际应用中需要考虑内存马的隐蔽性和持久化
总结
在Spring 3+JDK 17环境下植入内存马的关键在于绕过模块系统的反射限制。通过Unsafe修改调用类的module属性,使其与系统核心类处于同一模块,可以成功调用受保护的方法。这种方法利用了JDK内部机制,在保持兼容性的同时突破了安全限制。