前言
就像以前看到过的,Java8以后的一些版本对反射操作做了限制,比如Java9就对反射操作进行了一定程度的WARNING。
这里学习一下具体的限制版本和绕过方式,顺带一说的是,IDEA切换Maven项目的Java版本时,也要记得把pom.xml里面的改一下。
环境搭建
搞两个版本的Java,分别是Java11和Java17。
Java11
首先,反射使用TemplatesImpl加载类依旧行得通,计算器正常弹出,只是会发生大量WARNING:
public static void main(String[] args) {
try {
Class<?> cls = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Object templates = cls.getConstructor().newInstance();
Class<?> cls2 = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
Object factory = cls2.getConstructor().newInstance();
setField(templates, "_name", "Twings");
setField(templates, "_bytecodes", new byte[][]{getBytes()});
setField(templates, "_tfactory", factory);
Method m = cls.getDeclaredMethod("getOutputProperties");
m.invoke(templates);
}catch (Exception e) {
log.info(e.getMessage());
}
}
private static byte[] getBytes() {
return Base64.getDecoder().decode("yv66vgAAADQAIwoAAwAOBwAPBwAQBwARAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABtMb3JnL2V4YW1wbGUvVGVzdEphdmFzc2lzdDsBAApTb3VyY2VGaWxlAQASVGVzdEphdmFzc2lzdC5qYXZhDAAFAAYBABlvcmcvZXhhbXBsZS9UZXN0SmF2YXNzaXN0AQAQamF2YS9sYW5nL09iamVjdAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAEwEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMABUAFgoAFAAXAQAIY2FsYy5leGUIABkBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAbABwKABQAHQEADVN0YWNrTWFwVGFibGUBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwAgCgAhAA4AIQACACEAAQAEAAAAAgABAAUABgABAAcAAAAvAAEAAQAAAAUqtwAisQAAAAIACAAAAAYAAQAAAAUACQAAAAwAAQAAAAUACgALAAAACAASAAYAAQAHAAAAJAADAAIAAAAPpwADAUy4ABgSGrYAHlexAAAAAQAfAAAAAwABAwABAAwAAAACAA0=");
}
WARNING:
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.example.Main (file:/D:/Java1.8/Unsafe/Unsafe/target/classes/) to constructor com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl()
WARNING: Please consider reporting this to the maintainers of org.example.Main
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
该版本下Unsafe类去除了defineClass函数,只能通过defineAnonymousClass函数来加载类字节码:
Class<?> cls = Class.forName("sun.misc.Unsafe");
Field field = getField(cls, "theUnsafe");
Object unSafe = field.get(null);
Method method = unSafe.getClass().getDeclaredMethod("defineAnonymousClass", Class.class, byte[].class, Object[].class);
Class<?> c = (Class<?>)method.invoke(unSafe, new Object[]{Runtime.class, getBytes(), null});
c.newInstance();
以前在Java9行得通,但是在Java11.0.23版本下出现了问题:
Host class java/lang/Runtime and anonymous class org/example/TestJavassist are in different packages
看起来是待加载类和hostClass参数所属包不同导致的,应该可以通过修改待加载类的所属包来解决,但如果直接修改包名为java.lang来匹配就会遇到问题:
Prohibited package name: java.lang
解决方法一是修改类加载的hostClass或者在已知环境的条件下修改待加载类的包:
Class<?> cls = Class.forName("sun.misc.Unsafe");
Field field = getField(cls, "theUnsafe");
Object unSafe = field.get(null);
Method method = unSafe.getClass().getDeclaredMethod("defineAnonymousClass", Class.class, byte[].class, Object[].class);
Class<?> c = (Class<?>)method.invoke(unSafe, new Object[]{Main.class, getBytes(), null});
c.newInstance();
另一个方法好像也可以用于Java17的绕过,所以在下一部分再研究。
Java17
在该版本下,TemplatesImpl类也无法直接使用了,会爆出异常:
信息: class org.example.Main cannot access class com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl (in module java.xml) because module java.xml does not export com.sun.org.apache.xalan.internal.xsltc.trax to unnamed module @4dd8dc3
可以看出,Java17加强了对反射的限制,禁止了跨模块的反射访问,报错发生在getDeclaredConstructor之后的newInstance步骤,也就是说无法通过反射获得对象实例进行函数调用。根据参考文章的说法,现在采用的是强封装,所有反射在访问java.*包下非public属性和函数时,将抛出InaccessibleObjectException异常。
而且Unsafe中的defineClass函数和defineAnonymousClass函数都被移除了,不能直接进行类加载,但是根据参考文章,此时官方还预留了sun.misc和sun.reflect两个包,这两个包仍然可以可以被反射调用,所以就有一种可能性,这两个包内存在某个类,通过反射调用它的某个函数可以绕过模块限制。
此时我们需要使用ClassLoader的defineClass函数进行类加载,所以我们就需要想办法让此时正在运行的代码(如Main函数)与ClassLoader属于同一个包,比如修改当前代码所属的包。
而Unsafe类正好有这个功能:
Class<?> unSafe=Class.forName("sun.misc.Unsafe");
Field unSafeField=unSafe.getDeclaredField("theUnsafe");
unSafeField.setAccessible(true);
Unsafe unSafeClass = (Unsafe) unSafeField.get(null);
Module baseModule = Object.class.getModule();
Class<?> currentClass= Main.class;
long addr = unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module"));
unSafeClass.getAndSetObject(currentClass, addr, baseModule);
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] bytes = getBytes();
Class<?> calc= (Class<?>) defineClass.invoke(ClassLoader.getSystemClassLoader(), "org.example.TestJavassist", bytes, 0, bytes.length);
calc.newInstance();
需要注意的是,字节码需要重新生成,去掉TemplatesImpl使用时加上的superClass。
可以看到,虽然可以进行类加载,但是由于模块隔离,想要调用TemplatesImpl等类的函数就需要先获得其module并改写当前类,然而这种方法又需要反射其Class和Field,看起来好像没什么办法。
至于对反序列化的影响,改日再学习。