高版本反射 && 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给调用者,里面分为几种
-
反射的成员是public属性
-
反射的成员是protected受保护和static静态,并且调用者类是被调用类的子类
-
被调用的类所在模块被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即可

浙公网安备 33010602011771号