当前位置 博文首页 > 文章内容

    CoLoo:Java安全之ClassLoader

    作者:shunshunshun18 栏目:未分类 时间:2021-10-26 4:50:53

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    Java安全之ClassLoader

    类加载机制

    Java中的源码.java后缀文件会在运行前被编译成.class后缀文件,文件内的字节码的本质就是一个字节数组 ,它有特定的复杂的内部格式,Java类初始化的时候会调用java.lang.ClassLoader加载字节码,.class文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的.class文件,并创建对应的class对象,将class文件加载到虚拟机的内存,而在JVM中类的查找与装载就是由ClassLoader完成的,而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。

    类加载方式

    Java类加载方式分为显式和隐式

    显式:利用反射来加载一个类

    隐式:通过ClassLoader来动态加载,new 一个类或者 类名.方法名返回一个类

    示例代码

    @Test
    public void loadClassTest() throws Exception {
        //1、反射加载
        Class<?> aClass = Class.forName("java.lang.Runtime");
        System.out.println(aClass.getName());
    
        //2、ClassLoader加载
        Class<?> aClass1 = ClassLoader.getSystemClassLoader().loadClass("java.lang.ProcessBuilder");
        System.out.println(aClass1.getName());
    
    }
    

    那也就是其实可以通过ClassLoader.loadClass()代替Class.forName()来获取某个类的class对象。

    ClassLoader

    ClassLoader(类加载器)主要作用就是将class文件读入内存,并为之生成对应的java.lang.Class对象

    JVM中存在3个内置ClassLoader:

    1. BootstrapClassLoader 启动类加载器 负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.xxx.* 都在里面,比如 java.util.*java.io.*java.nio.*java.lang.* 等等。
    2. ExtensionClassLoader 扩展类加载器 负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中
    3. AppClassLoader 系统类加载器 才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。

    除了Java自带的ClassLoader外,还可以自定义ClassLoader,自定义的ClassLoader都必须继承自java.lang.ClassLoader类,也包括Java提供的另外二个ClassLoader(Extension ClassLoader和App ClassLoader)在内,但是Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。

    类加载流程

    类加载指的是在.java文件编译成.class字节码文件后,当需要使用某个类时,虚拟机将会加载它的.class文件,将.class文件读入内存,并在内存中为之创建一个java.lang.Class对象。但是实现步骤看起来会比较空洞和概念化,暂时不去深入研究,理解类加载是做什么的并了解加载过程即可。后续有刚需再去深入。

    类加载大致分为三个步骤:加载、连接、初始化。

    0x01 加载

    类加载指的是将class文件读入内存,并为之创建一个java.lang.Class对象,即程序中使用任何类时,也就是任何类在加载进内存时,系统都会为之建立一个java.lang.Class对象,这个Class对象包含了该类的所有信息,如Filed,Method等,系统中所有的类都是java.lang.Class的实例。
    类的加载由类加载器完成,JVM提供的类加载器叫做系统类加载器,此外还可以通过自定义类加载器加载。
    通常可以用如下几种方式加载类的二进制数据:

    从本地文件系统加载class文件。

    从JAR包中加载class文件,如JAR包的数据库启驱动类。

    通过网络加载class文件。

    把一个Java源文件动态编译并执行加载。

    0x02 链接

    链接阶段负责把类的二进制数据合并到JRE中,其又可分为如下三个阶段:

    1. 验证:确保加载的类信息符合JVM规范,无安全方面的问题。
    2. 准备:为类的静态Field分配内存,并设置初始值。
    3. 解析:将类的二进制数据中的符号引用替换成直接引用。

    0x03 初始化

    类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化

    双亲委派机制

    基本概念

    前面提到了Java自带3个ClassLoader,包括我们也可以实现自定义ClassLoader完成类加载,但是具体某个类的加载用的是哪个ClassLoader呢。这里涉及到一个双亲委派机制(PS:这个我看网上有讲的是委托也有是委派,个人觉得委派好听,先这么叫着:D)

    这里丢个图,基本概述了双亲委派机制(先走蓝色箭头再走红色箭头)

    双亲委派简单理解:向上委派,向下加载

    当一个.class文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了(向上委派); 如果自己无法加载,会下沉到子加载器去加载,一直到最底层(向下加载)。如果没有任何加载器能加载,就会抛出ClassNotFoundException异常。

    为什么?

    那么为什么加载类的时候需要双亲委派机制呢?

    采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。

    其次是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。

    自定义ClassLoader

    先看下ClassLoader这个类中的核心方法

    ClassLoader核心方法

    1. loadClass(加载指定的Java类)

      一般实现这个方法的步骤是:执行findLoadedClass(String)去检测这个class是不是已经加载过了。
      执行父加载器的loadClass方法。如果父加载器为null则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoaderparent为null,但仍然说Bootstrap ClassLoader是它的父加载器。如果向上委托父加载器没有加载成功;则通过findClass(String)查找。
      如果class在上面的步骤中找到了,参数resolve又是true的话那么loadClass()又会调用resolveClass(Class)这个方法来生成最终的Class对象。

    2. findClass(查找指定的Java类)

    3. findLoadedClass(查找JVM已经加载过的类)

    4. defineClass(定义一个Java类)

    5. resolveClass(链接指定的Java类)

    编写自定义ClassLoader步骤

    1、编写一个类继承ClassLoader抽象类;

    2、重写findClass()方法;

    3、在findClass()方法中调用defineClass()方法即可实现自定义ClassLoader;

    0x01 编写测试类

    package classloader;
    
    public class test {
        public String hello(){
            return "hello, CoLoo!";
        }
    }
    

    0x02 编译为.class文件

    0x03 .class转换bytes

    public class ByteClass {
        public static void main(String[] args) throws IOException {
            FileInputStream fis = new FileInputStream("test.class");
            byte[] classBytes = IOUtils.readFully(fis, -1, false);
            System.out.println(Arrays.toString(classBytes));
        }
    }
    

    Output:

    [-54, -2, -70, -66, 0, 0, 0, 52, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0, 16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 9, 116, 101, 115, 116, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 13, 104, 101, 108, 108, 111, 44, 32, 67, 111, 76, 111, 111, 33, 1, 0, 16, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 116, 101, 115, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1, 0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 5, 0, 1, 0, 11, 0, 0, 0, 2, 0, 12]
    

    0x04 自定义ClassLoader

    package classloader;
    
    import java.lang.reflect.Method;
    
    public class ClassLoaderTest extends ClassLoader {
    
        private static String className = "classloader.test";
        //转换byte后的字节码
        private static byte[] classBytes = new byte[]{54, -2, -70, -66, 0, 0, 0, 52, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0, 16, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 9, 116, 101, 115, 116, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 1, 0, 13, 104, 101, 108, 108, 111, 44, 32, 67, 111, 76, 111, 111, 33, 1, 0, 16, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 47, 116, 101, 115, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1, 0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 5, 0, 1, 0, 11, 0, 0, 0, 2, 0, 12};
    
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            //只处理classloader.test类
            if (name.equals(className)) {
                //调用definClass将一个字节流定义为一个类。
                return defineClass(className, classBytes, 0, classBytes.length);
            }
            return super.findClass(name);
        }
    
        public static void main(String[] args) throws Exception {
            //创建加载器
            ClassLoaderTest clt = new ClassLoaderTest();
            //使用我们自定义的类去加载className
            Class clazz = clt.loadClass(className);
            //反射创建test类对象
            Object test = clazz.newInstance();
            //反射获取方法
            Method method = test.getClass().getMethod("hello");
            //反射去调用执行方法
            String str = (String) method.invoke(test);
            System.out.println(str);
    
        }
    }
    

    执行了test类的hello方法

    一些思考

    上面自定义ClassLoader流程也可以小结一下

    1. 准备自定义类,编译为.class文件
    2. .class文件内容专为bytes数组
    3. 自定义ClassLoader,继承ClassLoader类,重写findClass()方法,在方法中定义对指定类的处理流程
    4. 主函数创建自定义ClassLoader对象并loadClass()指定类,如果自定义的ClassLoader完成了加载则会获得该类的class对象,后续可通过反射来深入利用(如执行某个方法)。

    那ClassLoader对于安全来说能做什么?

    1、个人这里目前想到的是代替Class.forName(),通过ClassLoader.loadClass()获取class对象

    @Test
    public void classLoaderRuntime() throws Exception {
        Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
        Runtime runtime = (Runtime) aClass.getMethod("getRuntime").invoke(aClass);
        runtime.exec("open -a Calculator");
    
    }
    

    2、加载恶意类

    如webshell中(之前有次攻防捕捉到一个webshell里面用到了classloader)或者内存马中应该也可以用到。

    恶意类加载还是有必要深入学习一下,给后续学习内存马和反序列化payload打个基础。

    首先上面提到了,关于显隐式加载类是有些区别的,显示加载时(反射)可以触发该类的初始化从而调用静态代码块执行,主要是因为使用java.lang.reflect对类进行反射调用时,如果该类没有初始化会先进行类的初始化;而隐式加载,如new,ClassLoader.getSystemClassLoader.loadClass(),不会初始化类也就不执行静态代码块中的内容。

    下面简单测试了几个可能会用到的触发类初始化的类加载方式,简单列举下。

    1. 默认java.lang.ClassLoader
      • loadClass() + newInstance()
      • Class.forName()
      • Class.forName(ClassName, true, ClassLoaderName) + newInstance()
    2. org.apache.bcel.util.ClassLoader 也是fj经常用到的,需要注意的是在JDK8u251之后就没有ClassLoader这个类了
    3. URLClassLoader
    4. new

    Classloader

    package classloader;
    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;
    
    
    public class ClassLoaderBaseClass {
        public static void main(String[] args) throws Exception {
            // 1、ClassLoader.getSystemClassLoader().loadClass() + 反射newInstance() [+]
            ClassLoader.getSystemClassLoader().loadClass("classloader.CalcBaseClass2").newInstance();
    
            // 0x02 new [+]
            CalcBaseClass2 calcBaseClass2 = new CalcBaseClass2();
    
            // 0x03 Class.forName() [+]
            Class.forName("classloader.CalcBaseClass2");
    
            // 0x04 ClassLoader.getSystemClassLoader().loadClass() [-]
            ClassLoaderTest.getSystemClassLoader().loadClass("classloader.CalcBaseClass2");
    
            // 0x05 Class.forName(className, true, ClassLoaderName)
            Class.forName("classloader.CalcBaseClass", true, java.lang.ClassLoader.getSystemClassLoader());
    
            // 0x06 bcel
            JavaClass clazz = Repository.lookupClass(CalcBaseClass2.class);
            String code = Utility.encode(clazz.getBytes(), true);
            System.out.println(code);
    
            new ClassLoader().loadClass("$$BCEL$$" + code).newInstance();
    
        }
    }
    

    calc

    package classloader;
    
    import java.io.IOException;
    
    public class CalcBaseClass2 {
        static {
            try {
                Runtime.getRuntime().exec("open -a Calculator");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }
    

    Reference

    上面很多内容都是参考下面文章学习到的,可能写的并不是很正确,建议阅读下面这些文章

    javasec.org

    https://www.cnblogs.com/nice0e3/p/13719903.html

    http://code2sec.com/javafan-xu-lie-hua-lou-dong-xue-xi-shi-jian-liu-lei-de-jia-zai-ji-zhi-he-e-yi-lei-gou-zao.html

    https://blog.csdn.net/javazejian/article/details/73413292

    https://blog.csdn.net/CNAHYZ/article/details/82219210

    https://blog.csdn.net/briblue/article/details/54973413

    https://blog.csdn.net/codeyanbao/article/details/82875064

    所有内容仅限于维护网络安全学习参考
    bkbky