Jdk17 反射限制与绕过 反射的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @CallerSensitive    @ForceInline      @IntrinsicCandidate     public  Object invoke (Object obj, Object... args)         throws  IllegalAccessException, IllegalArgumentException,           InvocationTargetException    {        if  (!override) {            Class<?> caller = Reflection.getCallerClass();            checkAccess(caller, clazz,                        Modifier.isStatic(modifiers) ? null  : obj.getClass(),                        modifiers);        }        MethodAccessor  ma  =  methodAccessor;                     if  (ma == null ) {            ma = acquireMethodAccessor();        }        return  ma.invoke(obj, args);    } 
获取到反射调用的对象和方法的时候,首先进行了checkAccess方法,对反射进行了检验
然后调用了 MethodAccessor 类的 invoke 方法进行进一步处理
1 2 3 4 5 MethodAccessor 是一个接口,定义了方法调用的具体操作,而它有三个具体的实现类: sun.reflect.DelegatingMethodAccessorImpl sun.reflect.MethodAccessorImpl sun.reflect.NativeMethodAccessorImpl 
判断的逻辑在acquireMethodAccessor() 中
判断缓存中是否存在对应的MethodAccessor 对象,存在就使用代码复用,不存在就调用 ReflectionFactory生成一个
这里把NativeMethodAccessorImpl做为DelegatingMethodAccessorImpl的参数
实际上就是给delegate参数赋值   newMethodAccessor 方法最终返回 DelegatingMethodAccessorImpl 类对象
走到DelegatingMethodAccessorImpl 类的 invoke 中,根据delegate 属性调用invoke方法
在 NativeMethodAccessorImpl 的 invoke 方法里,其会判断调用次数是否超过阀值,然后进行调用
反射的限制 Migrating From JDK 8 to Later JDK Releases 
jdk17是强封装的,意味着java*包下的所有非public字段,在我们尝试反射调用的时候会报错
1 2 3 4 5 6 7 8 9 public  class  UserService  {    public  static  void  main (String[] args)  throws   NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, InstantiationException {         byte [] bytes = Files.readAllBytes(Paths.get("D:\\Desktop\\life\\ctf\\target\\classes\\evil.class" ));         Method defineClass= ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class);         defineClass.setAccessible(true );         Class<?> shell = (Class<?>) defineClass.invoke(ClassLoader.getSystemClassLoader(), "evil" , bytes, 0 , bytes.length);         shell.newInstance();     } } 
调用的字节码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import  java.io.IOException;public  class  evil  {    public  evil ()  {     }     static  {         try  {             Runtime.getRuntime().exec("calc" );         } catch  (IOException var1) {             var1.printStackTrace();         }     } } 
在jdk8版本可以正常rce
在jdk17 会抛出如下错误
我们无法通过反射调用java.lang.ClassLoader.defineClass,这是一个protect的属性
简单调试一下
关注这个checkCanSetAccessible函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 final  void  checkCanSetAccessible (Class<?> caller, Class<?> declaringClass)  {        checkCanSetAccessible(caller, declaringClass, true );     }     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 ;         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();         }                  boolean  isClassPublic  =  Modifier.isPublic(declaringClass.getModifiers());         if  (isClassPublic && declaringModule.isExported(pn, callerModule)) {                          if  (Modifier.isPublic(modifiers)) {                 return  true ;             }                          if  (Modifier.isProtected(modifiers)                 && Modifier.isStatic(modifiers)                 && isSubclassOf(caller, declaringClass)) {                 return  true ;             }         }                  if  (declaringModule.isOpen(pn, callerModule)) {             return  true ;         }         if  (throwExceptionIfDenied) {                          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 ;     }  
可以看到返回true的条件如下
1 2 3 4 5 6 1.调用的变量所在模块和调用者所在模块相同 2.调用者所在模块跟Object所在模块相同 3.调用模块没有定义 4.调用属性值是public 5.调用属性是protected并且是static 6.在模块define中,定义了该属性值是open的 
unsafe类 	Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。
需要注意的是仅当调用getUnsafe方法的类为引导类加载器所加载时才合法,否则抛出SecurityException异常
该类位于sun.misc下,所以不受反射限制,我们可以通过反射获取相关类的实例
1 2 3 4 5 Class<?> unsafe=Class.forName("sun.misc.Unsafe" ); Field field= unsafe.getDeclaredField("theUnsafe" ); field.setAccessible(true ); Unsafe  unsafeclass  =  (Unsafe) field.get(null );System.out.println(unsafeclass); 
unsafen内部提供了很多系统层的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 返回对象成员属性在内存地址相对于此对象的内存地址的偏移量 public native long objectFieldOffset(Field f); // 获得给定对象的指定地址偏移量的值,与此类似操作还有:getInt,getDouble,getLong,getChar等 public native Object getObject(Object o, long offset); // 给定对象的指定地址偏移量设值,与此类似操作还有:putInt,putDouble,putLong,putChar等 public native void putObject(Object o, long offset, Object x); // 从对象的指定偏移量处获取变量的引用,使用volatile的加载语义 public native Object getObjectVolatile(Object o, long offset); // 存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义 public native void putObjectVolatile(Object o, long offset, Object x); // 有序、延迟版本的putObjectVolatile方法,不保证值的改变被其他线程立即看到。只有在field被volatile修饰符修饰时有效 public native void putOrderedObject(Object o, long offset, Object x); // 绕过构造方法、初始化代码来创建对象 public native Object allocateInstance(Class<?> cls) throws InstantiationException; 
Module_bypass 我们可以获取到当前调用对象之后,使用unsafe类修改当前的module(调用类)与object类相同,这样就可以绕过bypass
1 2 3 4 5 6 7 8 9 10         Class<?> unsafe=Class.forName("sun.misc.Unsafe" );          Field field= unsafe.getDeclaredField("theUnsafe" );         field.setAccessible(true );         Unsafe  unsafeclass  =  (Unsafe) field.get(null );         Module baseModule=Object.class.getModule();         Class<?> currentClass= UserService.class;         long  addr=unsafeclass.objectFieldOffset(Class.class.getDeclaredField("module" ));          unsafeclass.getAndSetObject(currentClass,addr,baseModule); 
再次尝试反射调用defineclass加载恶意字节码看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  class  UserService  {    public  static  void  main (String[] args)  throws  NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, InstantiationException, ClassNotFoundException, NoSuchFieldException {         Class<?> unsafe=Class.forName("sun.misc.Unsafe" );          Field field= unsafe.getDeclaredField("theUnsafe" );         field.setAccessible(true );         Unsafe  unsafeclass  =  (Unsafe) field.get(null );         Module baseModule=Object.class.getModule();         Class<?> currentClass= UserService.class;         long  addr=unsafeclass.objectFieldOffset(Class.class.getDeclaredField("module" ));          unsafeclass.getAndSetObject(currentClass,addr,baseModule);         byte [] bytes = Files.readAllBytes(Paths.get("D:\\Desktop\\life\\ctf\\target\\classes\\evil.class" ));         Method defineClass= ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class);         defineClass.setAccessible(true );         Class<?> shell = (Class<?>) defineClass.invoke(ClassLoader.getSystemClassLoader(), "evil" , bytes, 0 , bytes.length);         shell.newInstance();     } } 
成功反射,绕过限制
调试以下,看看区别。
可以看到成功修改了这里的module
也可以修改被调用的对象与当前的对象相同,也就是获取到java.*下的module和当前这个代码的相同,不过可能会报错(
利用 patch当前对象类的module