JDK17+反射限制绕过

测试环境

jdk-17.0.5

测试代码

List<Integer> list = new ArrayList<>();
Field field2 = list.getClass().getDeclaredField("elementData"); //private static final Object[]
field2.setAccessible(true);
for (int i = 0; i < 100; i++) {
    Object[] elementData = (Object[]) field2.get(list);
    System.out.println(i + "," + "list size:" + list.size() + ", element array length:" + elementData.length);
    list.add(i);
}

在JDK17下运行报错:

Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field transient java.lang.Object[] java.util.ArrayList.elementData accessible: module java.base does not "opens java.util" to unnamed module @3b07d329
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
    at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
    at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
    at Main.main(Main.java:22)

调试分析

从field2.setAccessible(true);断点调试,跟进checkCanSetAccessible()

image-20240811170806674

一直跟进到checkCanSetAccessible(),下面是分块解析这个方法

image-20240811171033356

caller即在哪个类进行了反射操作,首先判断调用类是不是MethodHandle,如果是报错了

往下,分别获取caller和declaringClass(被反射的类)的module,如果callerModule与declaringModule或者Object.class.getModule()相等就 return true (即能够允许被反射修改)

declaringModule.isNamed()如下,如果name为null则返回true

image-20240811171804870

继续往下

image-20240811172051587

这个部分是检测被反射对象是不是Public 或者protected-static

image-20240811172336759

这段判断declaringModule的package是否对caller开放,是就能返回true,

如果前面的条件都没达到,就报错,return false

思路

image-20240811171033356

最简单的,通过修改当前类(caller)的Module 与 Object.class.getModule()相等

如何修改Module?

可以通过Unsafe#getAndSetObject来修改module进行反射修改

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

获取Unsafe实例

Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);

这个反射满足

image-20240811173826376

下一步就是通过objectFieldOffset获取module偏移,然后用getAndSetObject修改当前类的module为Object.class.getModule()的值

Module baseModule = Object.class.getModule();
Class currentClass = Main.class;
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass, addr, baseModule);

修改后再进行需要的反射

import java.lang.reflect.Field;
import sun.misc.Unsafe;
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field field = unsafeClass.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        Module baseModule = Object.class.getModule();
        Class currentClass = Main.class;
        long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
        unsafe.getAndSetObject(currentClass, addr, baseModule);


        List<Integer> list = new ArrayList<>();
        Field field2 = list.getClass().getDeclaredField("elementData");
        field2.setAccessible(true);
        for (int i = 0; i < 100; i++) {
            Object[] elementData = (Object[]) field2.get(list);
            System.out.println(i + "," + "list size:" + list.size() + ", element array length:" + elementData.length);
            list.add(i);
        }
    }
}

总结

先修改当前类的module,再反射

Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Module baseModule = Object.class.getModule();
Class currentClass = Main.class; //注意修改
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass, addr, baseModule);