• 反射之获取Class


    反射之获取Class

    起因是学习java序列回显学习,不知道怎么就跑到Java RCE中类的反射获取&动态加载去了。因为对于反射的理解过于👎🐓,花了足足一天才搞清楚。

    对于Class的获取方式有很多,常见的比如

    1. 调用某个对象的getClass()方法。
    Student s=new Student();
    Class clazz=s.getClass();
    
    • 1
    • 2
    1. 调用某个类的 class 属性来获取该类对应的 Class 对象
    Class clazz=Student.class;
    
    • 1
    1. 使用 Class 类中的forName()静态方法,此种方法最安全、性能最好。
    Class clazz=Class.forName("类的全路径"); //推荐使用
    
    • 1
    1. 使用类的加载器
    ClassLoader classLoaDer = Sting.class.getClassLoader();
    Class c4 = classLoaDer.loadClass("java.lang.String");
    
    • 1
    • 2

    而在Java RCE中类的反射获取&动态加载中说重点关注的是 loadClass()forName() defineClass()

    而提到这几个函数往往便会顺带提及JVM类加载机制

    JVM类加载机制

    类的加载有3个步骤

    1. 加载,把 .class文件加载到内存,并为它创建一个java.lang.Class对象
    2. 链接,链接又包括三个小阶段
      • 验证:确保加载的类信息符合JVM规范,无安全方面的问题
      • 准备:为类的静态Field分配内存,并设置初始值,变量的初始值,如:int=0
      • 解析:将类的二进制数据中的符号引用替换成直接引用
    3. 优先对该类的父类进行初始化,然后初始化static修饰的变量和执行static代码块

    在这里插入图片描述

    loadClass()和forName()

    接下来就要说道说道loadClass()forName() 的区别。

    ClassLoder.loadClass(String name)

    loadClass(String name)内部调用了Classloder.loadClass(String name, boolean resolve)

    /**
    Params:
    name – The binary name of the class
    Returns:
    The resulting Class object
    */
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Class.forName(String className)

    forName(String className)内部则调用了Class.forName0(className, true, ClassLoader.getClassLoader(caller), caller)

    /**
    * @param     className   the fully qualified name of the desired class.
    * @return    the {@code Class} object for the class with the specified name.            
    */
    @CallerSensitive
    public static Class<?> forName(String className)
        throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2者最大的区别就是获取的class处于类加载中不同的阶段

    Class.forName得到的class是已经初始化完成的,也就是说会执行static代码块

    Classloder.loaderClass得到的class是还没有链接的

    defineClass()

    ClassLoder.defineClass()则很少提及,可却是ClassLoader的核心方法之一。

    defineClass()方法可以通过使用 .class文件字节码数组得到class实例。

    因为没有搞清楚class文件字节码数组,在这里就卡了我几小时。

    首先通过defineClass method throws java.lang.ClassFormatError: Incompatible magic value 1885430635 in class file和高赞回答提到的How do you dynamically compile and load external java classes? [duplicate]搞明白,是要读取.class文件而不是最开始我理解的class对象

    先写一个序列化测试对象SerObj

    import java.io.Serializable;
    
    public class SerObj implements Serializable {
        private int one;
        private int two;
    
        public SerObj(int one, int two) {
            this.one = one;
            this.two = two;
        }
    
        @Override
        public String toString() {
            return "SerObj{" +
                    "one=" + one +
                    ", two=" + two +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    然后编译得到SerObj.class文件

    得到后就是defineClass()获取class实例的示例代码了

    /*获取SerObj.class字节流*/
    File dir = new File("./out/production/reflect/SerObj.class");
    InputStream is = new FileInputStream(dir);
    
    /*将字节流读取到classBytes字节数组中*/
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] classBytes = new byte[Integer.parseInt(new Long(dir.length()).toString())];
    int bytesNumRead = 0;
    while ((bytesNumRead = is.read(classBytes)) != -1){
        baos.write(classBytes,0,bytesNumRead);
    }
    baos.close();
    is.close();
    
    /*反射获取defineClass方法*/
    Method defineClassMethod =
        ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class});
    defineClassMethod.setAccessible(true);
    
    /*1.直接反射获得*/
    Class cc = (Class) defineClassMethod.invoke(new ClassLoader(){}, classBytes, 0, classBytes.length);
    
    /* 获取SerObj的(int,int)构造器*/
    Constructor constructor = cc.getDeclaredConstructor(int.class,int.class);
    Object obj = constructor.newInstance(1,2);
    System.out.println(obj.toString());
    
    • 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

    到此ClassLoder.defineClass()是结束了。

    在学习的同时有额外的收获就顺便一起写在这篇博客了吧。

    getResource()

    在查找如何获得 .class的字节数组时,看到Get bytecode from loaded class里提到Class.getResource()是最好的实现,顺便学习了一下使用。

    根据文档,该方法的参数可以是一个目录也可以是一个URL链接,而他的返回值便是一个URL对象。而URL.openStream()返回一个读取了数据的InputStream,这正是我想要的

    URL url = Resources.class.getResource("/SerObj.class");
    InputStream is = url.openStream();
    
    • 1
    • 2

    剩下的部分就跟上面的一样了。

    各种姿势使用defineClass()获取class实例

    上面的东西都是因为此文Java RCE中类的反射获取&动态加载引发出来的,结果这篇文章的内容其实并不多。

    主要是讲了几个使用defineClass()获取class实例的方法。

    Method defineClassMethod =
        ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class});
    defineClassMethod.setAccessible(true);
    
    /*1.直接反射获得*/
    Class cc = 
        (Class) defineClassMethod.invoke(new ClassLoader(){}, classBytes, 0, classBytes.length);
    
    /*2.通过当前线程*/
    Class cc = 
        (Class) defineClassMethod.invoke(Thread.currentThread().getContextClassLoader(),classBytes, 0, classBytes.length);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上述2种方法一个有趣的地方是在调用Method.invoke()ClassLoader总是重新new一个。这是因为类class的唯一性取决于定义该类的classloader,从浅谈Java中DefineClass方法以及ClassLoader可见。一个ClassLoder只会加载同一个class一次!

    Java中类的唯一性由类加载器和类本身决定,如果沿用当前上下文中的类加载器实例,而POC中使用同一个类名多次攻击,可能出现类重复定义异常,而每次new一个新的classloader就可以避免这个问题。

    第三种方法提到的使用Unsafe类也非常的有趣。

    Unsafe.defineClass()是一个native方法。

    /* 3.利用Unsafe获取*/
    Field field = Unsafe.class.getDeclaredField("theUnsafe");
    field.setAccessible(true);
    Unsafe unsafe = (Unsafe) field.get(Unsafe.class);
    Class cc = unsafe.defineClass("SerObj", classBytes, 0, classBytes.length, null, null);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    参考文章

    1. 获取Class实例的四种方式
    2. Class.forName(),classloader.loadclass用法详解
    3. Java RCE中类的反射获取&动态加载
    4. 浅谈Java中DefineClass方法以及ClassLoader
  • 相关阅读:
    LoRA Land: 310个经微调的大语言模型可媲美GPT-4
    java 宠物在线商城系统Myeclipse开发mysql数据库web结构jsp编程servlet计算机网页项目
    【计网】三、数据链路层
    大数据下一代变革之必研究数据湖技术Hudi原理实战双管齐下-中
    js 处理时间格式——可指定时区进行转换
    前端经典面试题 | v-if/v-show的原理及区别
    SAP oracle 复制新实例后数据库远程连接报错 ora-01031
    如何录制视频?有了这款视频录制软件,粉丝多了,转发量也起来了
    C++笔记之vector的初始化以及assign()方法
    SQL教程之 10 个终极 SQL JOIN 问题和答案
  • 原文地址:https://blog.csdn.net/qq_40710190/article/details/126680274