反射重拾
半年没碰java了
先写点基础回忆一下
反射弹计算器
public class Test {public static void main(String[] args) throws Exception {Class<?> clazz = Class.forName("java.lang.Runtime");clazz.getDeclaredMethod("exec", String.class).invoke(clazz.getDeclaredMethod("getRuntime").invoke(clazz),"calc");}
}
public class Test {public static void main(String[] args) throws Exception {Class.forName("java.lang.ProcessBuilder").getDeclaredMethod("start").invoke(Class.forName("java.lang.ProcessBuilder").getConstructor(String[].class).newInstance(new String[][]{{"calc"}}));}
}
import java.lang.reflect.Method;
import java.util.Map;public class Test {public static void main(String[] args) throws Exception {Class clazz = Class.forName("java.lang.ProcessImpl");Method start = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);start.setAccessible(true);start.invoke(clazz, new String[]{"calc"}, null, null, null, false);}
}
修改Final
步骤如下
获取字段 -> 修改修饰符 -> 修改值
MyClass
public class MyClass {private final int password = 114514;@Overridepublic String toString() {return "MyClass{" +"password='" + password + '\'' +'}';}
}
Test
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;public class Test {public static void main(String[] args) throws Exception {Class<?> clazz = Class.forName("MyClass");Field fieldPassword = clazz.getDeclaredField("password");fieldPassword.setAccessible(true);Field modifiers = Field.class.getDeclaredField("modifiers");modifiers.setAccessible(true);modifiers.setInt(fieldPassword,fieldPassword.getModifiers() & ~Modifier.FINAL);MyClass myClass = new MyClass();fieldPassword.set(myClass,1919810);System.out.println(myClass);// MyClass{password='114514'}System.out.println(fieldPassword.get(myClass)); // 1919810}
}
为什么修改了之后我们输出的仍然是原先的值呢?
这是因为java编译器对final修饰属性进行的内联优化 即编译时将final的值直接放到了引用他的地方,即使通过反射修改了该属性 也没啥用
java会对如下final修饰的类型进行优化
byte short int long float double boolean char LiteralString(直接双引号括起来的字符串)
new 的String比较特殊 可以被有效修改 其余类型的包装类也是如此
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;public class Test {public static void main(String[] args) throws Exception {Field fieldPassword = Class.forName("MyClass").getDeclaredField("password");Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);getDeclaredFields0.setAccessible(true);Field modifiers = null;Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);for (Field f: fields){if (f.toString().contains("modifiers")) {modifiers = f;break;}}modifiers.setAccessible(true);int recoverNum = fieldPassword.getModifiers();modifiers.setInt(fieldPassword,0);MyClass myClass = new MyClass();System.out.println(myClass);fieldPassword.set(myClass,"1919810");modifiers.set(fieldPassword,recoverNum);System.out.println(myClass);System.out.println(fieldPassword.get(myClass));}
}
高版本下不能通过getDeclaredFiled获取Field的属性
但是可以通过getDeclaredFileds0来获得
这里没有考虑高版本绕过反射限制 采用的添加启动参数
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
后面学完绕过高版本反射限制之后会补充
绕过高版本反射限制
jdk 17 下运行
import java.lang.reflect.Method;
import java.util.Map;public class HighVersionBypass {public static void main(String[] args) throws Exception {Class clazz = Class.forName("java.lang.ProcessImpl");Method start = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);start.setAccessible(true);start.invoke(clazz, new String[]{"calc"}, null, null, null, false);}
}
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make static java.lang.Process java.lang.ProcessImpl.start(java.lang.String[],java.util.Map,java.lang.String,java.lang.ProcessBuilder$Redirect[],boolean) throws java.io.IOException accessible: module java.base does not "opens java.lang" to unnamed module @3b07d329
可以通过Unsafe类进行反射
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。
那么如何获得一个Unsafe对象呢
Unsafe提供静态方法getUnsafe获取Unsafe实例,当且仅当调用getUnsafe方法的类为引导类加载器所加载时才合法,否则抛出SecurityException异常
那么从getUnsafe
方法的使用限制条件出发,通过Java命令行命令-Xbootclasspath/a
把调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载,从而通过Unsafe.getUnsafe
方法安全的获取Unsafe实例。
挺麻烦 一般用反射
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe)field.get(null);
再看看setAccessible的实现
传入true的时候会调用checkCanSetAccessible
跟进去 一路跟到
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 callerboolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());if (isClassPublic && declaringModule.isExported(pn, callerModule)) {// member is publicif (Modifier.isPublic(modifiers)) {return true;}// member is protected-staticif (Modifier.isProtected(modifiers)&& Modifier.isStatic(modifiers)&& isSubclassOf(caller, declaringClass)) {return true;}}// package is open to callerif (declaringModule.isOpen(pn, callerModule)) {return true;}if (throwExceptionIfDenied) {// not accessibleString msg = "Unable to make ";if (this instanceof Field)msg += "field ";msg += this + " accessible: " + declaringModule + " does not \"";if (isClassPublic && Modifier.isPublic(modifiers))msg += "exports";elsemsg += "opens";msg += " " + pn + "\" to " + callerModule;InaccessibleObjectException e = new InaccessibleObjectException(msg);if (printStackTraceWhenAccessFails()) {e.printStackTrace(System.err);}throw e;}return false;}
如果调用类和目标类是同一个module 则可以修改
我们可以通过Unsafe#getAndSetObject来修改module
三个参数对应 操作对象 偏移 值
也就是要修改当前的module 为java.base
我们可以通过Unsafe的staticFieldOffset 或 objectFieldOffset来查找偏移量
在此 我们需要将HighVersionBypass类的module属性改为java.base
此处的getAndSetObject可以用putObject替换