理解java虚拟机的加载机制

// 反射加载TestHelloWorld示例
Class.forName("com.anbai.sec.classloader.TestHelloWorld");

// ClassLoader加载TestHelloWorld示例
this.getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld");

forName默认会初始化被加载类的静态属性和方法,如果不希望初始化类可以使用Class.forName("类名", 是否初始化类, 类加载器),而ClassLoader.loadClass默认不会初始化类方法。

package com.cxk.load;

public class Student {
    public Student() {
        System.out.println("进行了初始化构造器");
    }
    static {
        System.out.println("进入了静态方法");
    }
}进入了静态方法

Java自定义类的加载器 

java的加载机制是双亲委派机制,即从Bootstrap ClassLoader 、Extension ClassLoader、App ClassLoader的顺序,如果要加载一个类时,会先委托它的父类加载器尝试加载,一直网上,如果最上面的父类也没找到该类,那么才会在本加载器加载。

注意这个父类并不是真的继承关系,只是为了方便说明!

 1.为什么要自定义ClassLoader

 因为系统的ClassLoader只会加载指定目录的class文件,如果我们想加载自己的class文件,就可以自定义一个,里面也可以进行一些特殊的操作例如加密解密。

2.调用流程

ClassLoader::loadclass->ClassLoader::findClass->ClassLoader::defineclass

 我们只需要修改中间的findClass就可以了,自定义加载器继承ClassLoader重写findclass方法,然后里面的classDate字节码是我们可控的类,最后调用loadclass方法即可。

package com.cxk.Loader;

import java.io.*;

public class MyClassLoader extends ClassLoader {
    //指定路径
    private String path ;


    public MyClassLoader(String classPath){
        path=classPath;
    }

    /**
     * 重写findClass方法
     * @param name 是我们这个类的全路径
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class log = null;
        // 获取该class文件字节码数组
        byte[] classData = getData();

        if (classData != null) {
            // 将class的字节码数组转换成Class类的实例
            log = defineClass(name, classData, 0, classData.length);
        }
        return log;
    }

    /**
     * 将class文件转化为字节码数组
     * @return
     */
    private byte[] getData() {//主要实现的功能其实就是读取出文件的内容

        File file = new File(path);
        if (file.exists()){
            FileInputStream in = null;
            ByteArrayOutputStream out = null;
            try {
                in = new FileInputStream(file);
                out = new ByteArrayOutputStream();

                byte[] buffer = new byte[1024];
                int size = 0;
                while ((size = in.read(buffer)) != -1) {
                    out.write(buffer, 0, size);
                }

            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    in.close();
                } catch (IOException e) {

                    e.printStackTrace();
                }
            }
            return out.toByteArray();
        }else{
            return null;
        }


    }
}

 这个就是定义类的路径,然后用自定义的加载器加载。

package com.cxk.Loader;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassLoaderMain {

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
        //这个类class的路径
        String classPath = "E:\\2022java-servlet\\11\\javassist-learn\\src\\main\\java\\com\\cxk\\Loader\\Paul.class";

        MyClassLoader myClassLoader = new MyClassLoader(classPath);
        //类的全称
        String packageNamePath = "com.cxk.Loader.Paul";

        //加载Log这个class文件
        Class<?> Log = myClassLoader.loadClass(packageNamePath);

        System.out.println("类加载器是:" + Log.getClassLoader());

        //利用反射获取main方法
        Method method = Log.getDeclaredMethod("main", String[].class);
        Object object = Log.newInstance();
        String[] arg = {"ad"};
        method.invoke(object, (Object) arg);
    }


}

URLClassLoader 提供了远程加载类的功能

除此之外,跨类加载器调用类方法时需要特别注意一个基本原则:ClassLoader A和ClassLoader B可以加载相同类名的类,但是ClassLoader A中的Class A和ClassLoader B中的Class A是完全不同的对象,两者之间调用只能通过反射

 BCEL FastJson攻击链分析

 JavaClass cls = Repository.lookupClass(Evil.class);
            String code = Utility.encode(cls.getBytes(),true);
            System.out.println("$$BCEL$$"+code); 加密


int    index    = className.indexOf("$$BCEL$$");解密
String realName = className.substring(index + 8);

// BCEL解码类字节码
byte[] bytes = com.sun.org.apache.bcel.internal.classfile.Utility.decode(realName, true);

分析漏洞点在于,

com.sun.org.apache.bcel.internal.util.ClassLoader中的loadclass
if(class_name.indexOf("$$BCEL$$") >= 0)
  clazz = createClass(class_name);   进行取出前面BCEL标志的操作,

然后createClass中有解码的操作,并且

defineClass(class_name, bytes, 0, bytes.length);中的bytes可控,就是通过调用loadclass中的第一个参数就可以获得。

Java IO/NIO多种读写文件方式

使用Inputstream读取

package com.cxk.input;

import java.io.*;

public class FileInputStreamDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("E:\\123.txt");
        FileInputStream fis = new FileInputStream(file);
        int a=0;  byte[] bytes = new byte[1024];

        // 创建二进制输出流对象
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        // 循环读取文件内容
        while ((a = fis.read(bytes)) != -1) {
            // 截取缓冲区数组中的内容,(bytes, 0, a)其中的0表示从bytes数组的
            // 下标0开始截取,a表示输入流read到的字节数。
            out.write(bytes, 0, a);
        }

        System.out.println(out.toString());
    }

}

 很容易理解,这里的out.write其实就是往缓冲区中输入内容,最后out.toString输出出来。

使用OutputStream写入

package com.cxk.input;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FIleoutput {
    public static void main(String[] args) throws IOException {
        File file = new File("E://123.txt");
        FileOutputStream strea = new FileOutputStream(file);
        String content="who am i jia you xiao zi发给对方 ";
        strea.write(content.getBytes());
        strea.flush();
        strea.close();
    }
}

RandomAccessFile

是一个既可以读取也可以写入文件的类,

  // 创建RandomAccessFile对象,r表示以只读模式打开文件,一共有:r(只读)、rw(读写)、
            // rws(读写内容同步)、rwd(读写内容或元数据同步)四种模式。
            RandomAccessFile raf = new RandomAccessFile(file, "r");
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值