Class类加载流程

实际上就是ClassLoader将会调用loadclass来尝试加载类,首先将会在jvm中尝试加载我们想要加载的类,如果jvm中没有的话,将调用自身的findclass,此时要是findclass重写了,并且传入了我们想要加载的类的字节码,那么应该调用defineclass在jvm中加载该类,最后返回java.lang.class类对象。

那么既然类加载器java.lang.ClassLoader是所有的类加载器的父类,我们可以来定义其子类从而实现加载任意字节码,当然加载恶意class也是可以的,重写findclass,遵循双亲委派机制

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class class_loader extends ClassLoader { private static String testClassName = "TestHelloWorld"; private static byte[] testClassBytes = new byte[]{}; {
try{
File fileName = new File("H:\\TestHelloWorld.class");
FileInputStream in = new FileInputStream(fileName);
ByteArrayOutputStream byt = new ByteArrayOutputStream(); int b = -1;
byte[] zi = new byte[1024];
while ((b = in.read(zi)) != -1) {
byt.write(zi, 0, b);
}
byte[] class_byte = byt.toByteArray();
testClassBytes = class_byte;
}
catch (Exception e){ }
} @Override
public Class<?> findClass(String name) throws ClassNotFoundException{
if(name.equals(testClassName)){ return defineClass(testClassName,testClassBytes,0,testClassBytes.length); }
return super.findClass(name);
} public static void main(String[] args){ class_loader loader = new class_loader(); try {
Class testClass = loader.loadClass(testClassName); Object testInstance = testClass.newInstance(); Method method = testInstance.getClass().getMethod("hello"); String str = (String) method.invoke(testInstance); System.out.println(str); }catch (Exception e){ } }
}

以上代码中字节码肯定是一个恶意类,那么只要编译得到class文件即可,我们直接将其读到byte数组中,然后再通过定义的findclass中return defineclass来通过我们的字节码生成类。

TestHelloWorld.java如下所示

public class TestHelloWorld {

    public String hello(){
return "tr1ple 2333";
} }

利用自定义类加载器我们可以在webshell中实现加载并调用自己编译的类对象,比如本地命令执行漏洞调用自定义类字节码的native方法绕过RASP检测

URL类加载器

URLClassLoader也是ClassLoader的子类,那么URLClassloader可以用来加载本地或者远程资源某个目录下的资源,我们可以使用他来加载远程或本地的jar文件,如果存在security manager的话,创建一个类加载器将调用下面的方法检查是否当前是否允许创建classloader

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader; public class TestURLClassLoader { public static void main(String[] args){
try {
URL url = new URL("http://127.0.0.1:9000/cmd.jar");
URLClassLoader ucl = new URLClassLoader(new URL[]{url});
String cmd = "calc";
Class cmdClass = ucl.loadClass("cmd");
Process process = (Process) cmdClass.getMethod("exec",String.class).invoke(null,cmd); }
catch (Exception e){ } } }

线程上下文类加载器(Thread Context ClassLoader)

每个线程都有一个线程加载器,在创建该线程的时候指定,如果没有指定则从父类进行继承,如果全局都没有设置,则线程的实际类加载器为app classloader,利用线程上下文加载器,我们能够实现所有的代码热替换,热部署(相对于编译型语言来说的,动态更新class字节码文件,而不用重启应用)

类加载器的双亲委派模型

每当一个类加载器收到一个类加载请求,则首先在父类加载器的搜索范围内尝试进行加载,当父类无法加载时子类才进行加载

在rt.jar下的sun.mics.Launch中完成类加载器的初始化,1处实例化extclassloader,2处实例化app classloader,3处设置父线程的子线程上下文类加载器也为app classloader,其中ext classloder是app classloder的父亲,这两个类加载器都继承与URLclassloader,AppClassLoader而非继承ExtClassLoader,父亲和父类不一样(划重点,要记住),如下图所示,classloader的parent变量即为父亲

继承关系:

继承关系:

java.lang.Object

       --- java.lang.ClassLoader
              --- java.security.SecureClassLoader
                      ---  java.net.URLClassLoader
                            --- sun.misc.Launcher$ExtClassLoader java.lang.Object        --- java.lang.ClassLoader
              --- java.security.SecureClassLoader
                      ---  java.net.URLClassLoader
                            --- sun.misc.Launcher$AppClassLoader

jvm动态加载jar包:

对于一个jar包中的类,用的classloder是第一次jvm加载该jar包中类所使用的class loader

如何确定jvm中类的唯一性:

对于任何一个类,都需要由加载它的类加载器和这个类本身一同确立在java虚拟机中的唯一性,也就是说不同的类加载器加载同一个类实际上是不同的class对象

需要自定义类加载器时,我们要重写的是findclass方法,这样是遵循双亲委派模型的,而不能重写loadclass

loadclass 类加载的顺序

1.首先调用findloadedclass查看要加载的类是否已经被加载

2.若没有被加载,并且父亲不为null,则调用父类的loadclass尝试加载class,使用双亲委派进行加载

3.如果父亲的loadclass都没有找到需要加载的类,则调用当前classloader的findclass去找要加载的类

如果以上两个步骤能够找到需要加载的类的话(findclass返回就是class类型),并且resolveclass为true的话此时将调用resolveclass对class类型的实例进行链接,默认调用loadclass(“class类名”),是不调用resolveclass进行链接的

所以从findclass的注释中也可以看到jdk是建议我们去在自己的classloader中重写该方法的,因为一般来说class文件来自本地文件系统,但是像rmi机制这种,class文件可以放在远程

在findclass中一般将调用defineclass来创建class

defineclass其中有两种:

第一种直接传字节数组,偏移,以及字节数组长度,保护域为null,此时调用defineclass创建的class是没有名称的,那这种情况是不会创建保护域的,这种是不推荐使用的

第二种是加上name参数,这也是常用的一种,安全的创建class类型实例的方法,将创建保护域

看下具体这种情况是如何给class实例封装保护域的,下图所示是一些操作方法

首先调用preDefineClass,传入要定义的name以及null的保护域,由注释可以看到主要检测要定义的name是不是以java.*开头的,以及要验证创建的class是不是和包内其它class的签名是一样的,其中checkname主要检测定义的name是否符合规范,不能出现/,不能出现[,接下来判断定义name包名不能是java开头的

此时定义pd为默认的保护域,如注释所言,为新创建的class建立一个默认的保护域

那保护域又是什么东西,保护域是由codesource(codesource感觉就是当前字节码文件的来源,和codebase类似,rmi中就用到了codebase的概念,就把它理解为一个存放字节码文件的位置),codesource是对codebase的扩展,加入了证书(x509证书,具体不再细看)的概念,用证书来验证url所指处的class文件

permissions 一组权限集合

以及当前所用的类加载器,以及一组主体的抽象概念(principal数组),感觉是x509证书用来验证这个要被创建的类和包中类的关系的一个接口,从它重写的tostring、equals就能大概猜出来

创建完默认的保护域后,此时调用defineClassSourceLocation拿到codesource的地址

接下来就调用definClass1来创建class实例,可以看到是个native方法,具体操作是看不到的,如何验证字节码合法性也是看不到的,也包括相应的证书的验证,为什么不让我们看到,因为如果我们能看到具体定义过程,就可以自己按照规则地去定义class,那么检查就没有意义了

如果能够创建class实例的话,接下来就从保护域中拿出证书给该class签名,至此我们想要加载的class已经从文件系统上的字节码文件变成了class类型的实例

再回到最开始的loadclass方法里,当前的findclass找到了需要加载的class字节码文件并创建class实例以后,此时我们想要的类已经位于jvm中了,此时最后一步就是判断是否resolve进行链接(默认不进行链接)

那么class的链接也是native实现的,所以也是看不到的,链接完以后,还差最后一步初始化,就能使用该类了

举个

javaweb-codereview 学习记录-3的更多相关文章

  1. javaWeb后端学习记录

    java后端学习重点: 1.java语言特性: 基础知识,集合,多线程,并发,JVM,NIO,网络编程,设计模式.  (★★★★★) jdk源码中有大量的数据结构与java语言细节.jdk源码着重看c ...

  2. java后端学习记录2019

    学习计划 2019年计划 1.学习计算机基础,并加以实践.包括LeetCode刷题.数据库原理(索引和锁.Sql优化等).网络协议(Http.Tcp).操作系统(加深Linux).<Http权威 ...

  3. Spring Boot学习记录(二)--thymeleaf模板 - CSDN博客

    ==他的博客应该不错,没有细看 Spring Boot学习记录(二)--thymeleaf模板 - CSDN博客 http://blog.csdn.net/u012706811/article/det ...

  4. Quartz 学习记录1

    原因 公司有一些批量定时任务可能需要在夜间执行,用的是quartz和spring batch两个框架.quartz是个定时任务框架,spring batch是个批处理框架. 虽然我自己的小玩意儿平时不 ...

  5. Java 静态内部类与非静态内部类 学习记录.

    目的 为什么会有这篇文章呢,是因为我在学习各种框架的时候发现很多框架都用到了这些内部类的小技巧,虽然我平时写代码的时候基本不用,但是看别人代码的话至少要了解基本知识吧,另外到底内部类应该应用在哪些场合 ...

  6. Apache Shiro 学习记录4

    今天看了教程的第三章...是关于授权的......和以前一样.....自己也研究了下....我觉得看那篇教程怎么说呢.....总体上是为数不多的精品教程了吧....但是有些地方确实是讲的太少了.... ...

  7. JavaWeb基础学习体系与学习思路

    对于JAVAWEB的学习,首先一定要明确的是学习整体框架和思路,要有一个把控.对于WEB,很多人认为是做网页,简单的把静态网页与JAVAWEB与网页设计一概而论. 拿起一本JS就开始无脑的学习,学了一 ...

  8. UWP学习记录12-应用到应用的通信

    UWP学习记录12-应用到应用的通信 1.应用间通信 “共享”合约是用户可以在应用之间快速交换数据的一种方式. 例如,用户可能希望使用社交网络应用与其好友共享网页,或者将链接保存在笔记应用中以供日后参 ...

  9. UWP学习记录11-设计和UI

    UWP学习记录11-设计和UI 1.输入和设备 通用 Windows 平台 (UWP) 中的用户交互组合了输入和输出源(例如鼠标.键盘.笔.触摸.触摸板.语音.Cortana.控制器.手势.注视等)以 ...

  10. UWP学习记录10-设计和UI之控件和模式7

    UWP学习记录10-设计和UI之控件和模式7 1.导航控件 Hub,中心控件,利用它你可以将应用内容整理到不同但又相关的区域或类别中. 中心的各个区域可按首选顺序遍历,并且可用作更具体体验的起始点. ...

随机推荐

  1. Shave Beaver! CodeForces - 331B2 (线段树)

    题面 The Smart Beaver has recently designed and built an innovative nanotechnologic all-purpose beaver ...

  2. How to output the target message in dotnet build command line

    How can I output my target message when I using dotnet build in command line. I use command line to ...

  3. sed工具

    SED是一个非交互式文本编辑器,它可对文本文件和标准输入进行编辑,标准输入可以来自键盘输入.文本重定向.字符串.变量,甚至来自于管道的文本,与VIM编辑器类似,它一次处理一行内容,Sed可以编辑一个或 ...

  4. Android studio 使用git仓库记录

    studio 绑定git settings --> verson control -->git 在项目文件目录右击打开git bash here操作界面 查看git项目安装位置 找到id_ ...

  5. Oracle单引号拼接和替换

    1.oracle拼接一个单引号: 正常写法:''''|| 由于单引号存在转义,第一个和最后一个是指定你要使用的字符,第二个’是单引号的转义字符,所以需要第三个‘才是真正你要拼接的那个. 也可以用 ch ...

  6. 【Linux】awk笔记

    awk是一种处理文本文件的语言,是一个强大的文本分析工具. 实例 ①显示文件行中匹配项 # 每行按空格或TAB分割,输出文本中的1.4项 yunduo@yunduo-ThinkCentre-XXXX: ...

  7. Xgboost参数调节

    转自:https://segmentfault.com/a/1190000014040317 整体: # 1.调试n_estimators cv_params = {'n_estimators': [ ...

  8. STM32的RTC晶振不起振的原因及解决方法

    STM32的RTC晶振经常出现不起振的问题,这已经是“业界共识”了.很多人在各种电子论坛上求助类似于“求高手指点!RTC晶振不起振怎么办”的问题,而其答案基本可以概括为“这次高手帮不了你了” 更有阴谋 ...

  9. Unity3D小游戏开发之两个我踩过的坑

    最近在开发一个植物大战僵尸小游戏,今天写了一早上的代码,踩了两个坑,这两个坑的位置分别位于触发器和数据转换,写这篇博文以此来让其他程序员不要再去踩这两个坑. 1.我在做简易僵尸模型的时候,这个僵尸模型 ...

  10. 洛谷$1541$ 乌龟棋 线性$DP$

    Luogu   CH Sol f[i]表示走到第i个格子时获得的最大分数 发现转移与各个爬行卡片的数量有关,一共只有4种卡片 所以就把这四种卡片的已使用张数也放进状态,f[i][a][b][c][d] ...