Java反序列化基础-类的动态加载

类加载器&双亲委派

什么是类加载器

  • 类加载器是一个负责加载器类的对象,用于实现类加载的过程中的加载这一步。
  • 每个Java类都有一个引用指向加载它的ClassLoader。而数组类是由JVM直接生成的(数组类没有对应的二进制字节流)

类加载器有哪些

JVM 中内置了三个重要的 ClassLoader:

  1. BootstrapClassLoader(启动类加载器):最顶层的加载类,由C++实现,通常表示为null,并且没有父级,主要用来加载JDK内部的核心类库,以及被-Xbootclasspath参数指定路径下的所有类。
  2. ExtensionClassLoader(扩展类加载器):主要负责 “%JRE_HOME%/lib/ext” 目录下的jar包和类以及被 “java.ext.dirs” 系统变量所指定路径下的所有类。
  3. AppClassLoader(应用程序类加载器):面向用户的加载器,负责加载应用类的path下的所有jar包和类。

还有一个是用户自定义类加载器:

  1. User ClassLoader

什么是双亲委派

当一个类加载器收到类加载器的请求的时候,它不会直接加载指定的类,而是把这个请求委托给自己的父加载器去加载器。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。
双亲委派的好处就是一个类名只会被一个加载器加载
image.png

类加载不同代码的加载顺序

这里的代码块主要有以下四种:

  • 静态代码块:static{}
  • 构造代码块:{}
  • 无参构造器:ClassName()
  • 有参构造器:ClassName(String name)

实例化对象

  • Person.java:里面有静态代码块、构造代码块、无参构造器、有参构造器、静态成员变量、普通成员变量、静态方法。
  • Main.java:启动类

Person.java

package Class;public class Person {public static int statica;public int instancea;static {System.out.println("静态代码块");}Person(){System.out.println("无参构造器");}{System.out.println("构造代码块");}public Person(int instancea) {this.instancea = instancea;System.out.println("有参构造"+ this.instancea);}public static void staticAction(){System.out.println("静态方法");}
}

Main.java

package Class;public class Main {public static void main(String[] args) throws ClassNotFoundException{Person person = new Person();}
}

运行之后可以看见,调用的顺序是 :
静态代码块->构造代码块->无参构造器
image.png

调用静态方法

调用Person的静态方法
Main.java

package Class;public class Main {public static void main(String[] args) throws ClassNotFoundException{Person.staticAction();}
}

运行Main.java后,可以看见没有实例化对象会先调用:
静态代码块->静态方法
image.png

对静态成员变量赋值

Main.java

package Class;public class Main {public static void main(String[] args) throws ClassNotFoundException{Person.statica = 1;}
}

对静态成员变量赋值,也会调用静态代码块
image.png

使用class获取类

Main.java

package Class;public class Main {public static void main(String[] args) throws ClassNotFoundException{Class c = Person.class;}
}

运行后没有输出
image.png

使用forName获取类

获取类的class

需要在main方法上主动抛出异常
Main.java

package Class;public class Main {public static void main(String[] args) throws ClassNotFoundException{Class.forName("Class.Person");}
}

运行之后,可以看见调用了静态代码块
image.png

有true

Main.java

package Class;public class Main {public static void main(String[] args) throws ClassNotFoundException{Class.forName("Class.Person",true,ClassLoader.getSystemClassLoader());}
}

调用静态代码块
image.png

有false

Main.java

package Class;public class Main {public static void main(String[] args) throws ClassNotFoundException{Class.forName("Class.Person",false,ClassLoader.getSystemClassLoader());}
}

可以看见没有调用任何代码块
image.png

使用ClassLoader.loadClass() 获取类

Main.java

package Class;public class Main {public static void main(String[] args) throws Exception{ClassLoader cl = ClassLoader.getSystemClassLoader();Class<?> c = cl.loadClass("Class.Person");c.newInstance();// 初始化类}
}

添加了 c.newInstance()可以看见调用了以下,否则不调用
image.png

动态加载字节码

什么是字节码?

Java中的字节码,英文名为 “bytecode”,是Java代码编译后的中间代码格式。JVM(Java虚拟机)执行使用的一类指令,字节码通常存储在 .class文件中。

javac Hello.java

这里使用 javac命令编译我们写好的java文件,可以看见生成了一个 Hello.class文件,这个就是字节码文件
image.png
我们打开Hello.class文件看一下,可以看见都是一些看不懂的文件,这些是需要JVM去做的事情,才能执行
image.png
可以看见执行的时候,也会输出成功,因为java加载了字节码
image.png

类加载器的原理

注意:

复现的时候JDK版本不可以太高,否则会不一样

JDK下载:

https://blog.csdn.net/weixin_51959343/article/details/135921731

这里主要断点调试 loadClass 这一行
image.png
这个时候就会来到它的父类抽象类 ClassLoader,然后调用 loadClass()的两个参数
image.png
继续跟进来到了 AppClassLoader的loadClass()
image.png
走到下面是双亲委派的逻辑,调用父类的loadClass重复查询
image.png
这个时候又回到了 ClassLoader()类中
image.png
来到所属的 Launcher类中,Ctrl + H 可以看见,类的流程关系是:
ClassLoader —-> SecureClassLoader —> URLClassLoader —-> APPClassLoader
image.png
后面继续断点跟踪调试可以发现,函数调用顺序:
loadClass() -> findClass() -> defineClass()

任意类加载class文件

urlClassLoader

编译一个Calc类的字节码文件

package Class;import java.io.IOException;public class Calc {static {try {Runtime.getRuntime().exec("calc");} catch (IOException e) {throw new RuntimeException(e);}}
}

image.png
然后编写urlClassLoader的启动类

package Class;public class Main {public static void main(String[] args) throws Exception{URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file://编译后的class文件位置路径//")});Class<?> c = urlClassLoader.loadClass("Class.Calc"); c.newInstance();}
}

运行之后可以看见成功弹出了计算器
image.png
也可以使用http协议进行远程加载

package Class;public class Main {public static void main(String[] args) throws Exception{URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://localhost:8080")});Class<?> c = urlClassLoader.loadClass("Class.Calc"); c.newInstance();}
}

在字节码文件目录下开启http服务
image.png
运行之后可以看见弹出计算器
image.png
也可以使用jar,

  • 前面 jar:
  • 后面 xxx.jar!/
package Class;public class Main {public static void main(String[] args) throws Exception{URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:http://localhost:8080/Calc.jar!/")});Class<?> c = urlClassLoader.loadClass("Class.Calc");c.newInstance();}
}

image.png
file协议同理

defineClass

也可以直接使用 defineClass方法加载恶意的class文件

package Class;public class Main {public static void main(String[] args) throws Exception{//获取ClassLoader类的系统类加载器赋值到c1变量ClassLoader cl = ClassLoader.getSystemClassLoader();//使用反射机制获取 ClassLoader 类的 defineClass 方法中的一些一些参数Method defineClassCalc = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);//设置权限为可访问defineClassCalc.setAccessible(true);//byte[]数组存储了,这个目录下的Calc.class字节码文件byte[] code = Files.readAllBytes(Paths.get(("E:\\语言学习\\java\\code\\Serialize1\\Serialize1\\out\\production\\Serialize1\\Class\\Calc.class")));//反射调用了之前获取到的 defineClass 方法,将字节码数组转换为 Class 对象赋值给cClass c = (Class) defineClassCalc.invoke(cl,"Class.Calc",code,0,code.length);//初始化 Class对象 cc.newInstance();}
}

image.png
缺点就是 defineClass方法是protected受保护的成员,在反序列化中难以反射调用

Unsafe

Unsafe 方法,是采用单例模式进行设计的,无法直接调用,所以需要反射

package Class;public class Main {public static void main(String[] args) throws Exception{//获取ClassLoader类的系统类加载器赋值到c1变量ClassLoader cl = ClassLoader.getSystemClassLoader();//获取 Unsafe的类Class<Unsafe> unsafeClass = Unsafe.class;//通过反射 获取 Unsafe类的 theUnsafe字段Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");//设置访问权限为true,theUnsafe是prvate成员theUnsafeField.setAccessible(true);byte[] code = Files.readAllBytes(Paths.get(("E:\\语言学习\\java\\code\\Serialize1\\Serialize1\\out\\production\\Serialize1\\Class\\Calc.class")));//获取 Unsafe对象theUnsafeField方法静态的字段的值Unsafe unsafe = (Unsafe) theUnsafeField.get(null);Class c2 = unsafe.defineClass("Class.Calc", code, 0, code.length, cl, null);c2.newInstance();}
}

image.png

总结

总的来说就是加载器字节码之间怎么构造一条链子,底层之间产生的漏洞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/624153.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于ubuntu22.04系统安装nvidia A100驱动与NVLink启用

1、官方仓库 针对驱动包下载认准nvidia官网 dpkg -i nvidia-driver-local-repo-ubuntu2204-550.54.15_1.0-1_amd64.deb apt update apt search nvidia-driver-5502、安装 根据步骤1apt search nvidia-driver-550查出版本&#xff1a;此驱动包封在nvidia-driver-local-repo-ub…

基于springboot的医护人员排班系统

随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了医护人员排班系统的开发全过程。通过分析医护人员排班系统管理的不足&#xff0c;创建了一个计算机管理医护人员排班系统的方案。文章介绍了医护人员排班系统的系统分…

【数据结构与算法】之双向链表及其实现!

​ 个人主页&#xff1a;秋风起&#xff0c;再归来~ 数据结构与算法 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c;律己则安&#xff01; 目录 1、双向链表的结构及概念 2、双向链表的实现 2.1 要实现的接口…

算法设计与分析实验报告c++实现(矩阵链相乘、投资问题、完全背包问题、数字三角形、最小生成树、背包问题)

一、实验目的 1&#xff0e;加深学生对分治法算法设计方法的基本思想、基本步骤、基本方法的理解与掌握&#xff1b; 2&#xff0e;提高学生利用课堂所学知识解决实际问题的能力&#xff1b; 3&#xff0e;提高学生综合应用所学知识解决实际问题的能力。 二、实验任务 用动态…

【微信小程序之分包】

微信小程序之分包 什么是分包分包的好处分包前的结构图分包后的结构图分包的加载规则分包的体积限制使用分包打包原则引用原则独立分包独立分包的配置方法独立分包的引用原则分包预下载配置分包的预下载分包预下载限制 什么是分包 分包指的是把一个完整小程序项目&#xff0c;…

突破编程_前端_SVG(使用 svg-pan-zoom 库进行平移与缩放)

1 svg-pan-zoom 概述 svg-pan-zoom 是一个轻量级、高性能且易于使用的 JavaScript 库&#xff0c;专为增强 SVG 图像的浏览体验而设计。它提供了平移和缩放功能&#xff0c;使用户能够无缝探索大型或复杂的 SVG 图形。这个库允许用户对SVG图像进行交互操作&#xff0c;包括缩放…

新版AndroidStudio使用switch-case语句时出现Constant expression required错误

原因: 在新版的Android Studio中使用JDK17以上版本&#xff0c;会出现switch语句报错"Constant expression required"的问题&#xff0c;这是因为在JDK17中switch语句的条件表达式支持使用枚举类型&#xff0c;而这个特性还没有被支持。 解决方法: ①在gradle.prope…

Github 2024-04-12 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-04-12统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目6TypeScript项目2Cuda项目1C++项目1C项目1HTML项目1Jupyter Notebook项目1JavaScript项目1Python - 100天从新手到大师 创建周期:22…

HTTP/1.1特性总结

优点 【简单&#xff0c;灵活和易于扩展&#xff0c;应用广泛和跨平台】 1.简单&#xff1a; http基本的报文格式就是headerbody&#xff0c;头部信息也是key-value简单的文本形式&#xff0c;易于理解&#xff0c;降低了学习和使用的门槛 2.灵活和易于扩展&#xff1a; &…

python-常用数据结构(2)

6、某企业为职工发放奖金:如果入职超过5年,且销售业绩超过15000元的员工,奖金比例为0.2;销售业绩超过10000元的员工,奖金比例为0.15:销售业绩超过5000元的员工,奖金比例为0.1;其他奖金比例为0.05。如果是人职不超过5年,且销售业绩超过4000的员工,奖金比例为0.045;否则为0.01。输…

前端ocr技术:electron+vue3中使用tesseract插件识别图片中字符

同学们可以私信我加入学习群&#xff01; 正文开始 前言一、electron各种csp问题二、试用插件总结 前言 项目需要ocr技术识别图片中的中文字符&#xff0c;本来这部分是后端的工作&#xff0c;但是因为各种原因&#xff0c;决定前端也做一个版本。 在ai时代之前&#xff0c;o…

bugku-cryton 把猪困在猪圈里

打开题目得到一串字符&#xff0c;发现最后有等号&#xff0c;尝试buase64解码&#xff0c;发现文字解码不行&#xff0c;试试图片解码 解码得到一串图片密文&#xff0c;根据经验是猪圈密码&#xff0c;查找相关表格对应 一一对应得到flag{thisispigpassword} 参考猪圈密文链…