参考文章:https://blog.csdn.net/mole_exp/article/details/122768814
Java中动态加载字节码的方法
利用 URLClassLoader 加载远程class文件
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public static void main(String[] args) {
try {
//使用file协议在本地寻找指定.class文件
//URL[] urls = new URL[]{new URL("file:///Users/fa1c0n/codeprojects/IdeaProjects/misc-classes/src/main/java/")};
//使用http协议到远程地址寻找指定.class文件
URL[] urls = new URL[]{new URL("http://127.0.0.1:8000/")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class clazz = urlClassLoader.loadClass("Exploit");
clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
|
利用 ClassLoader#defineClass 直接加载字节码
类加载 - 双亲委派模型

BootstrapClassLoader:启动类加载器/根加载器,负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.*.*都在里面。这个 ClassLoader 比较特殊,它其实不是一个ClassLoader实例对象,而是由C代码实现。用户在实现自定义类加载器时,如果需要把加载请求委派给启动类加载器,那可以直接传入null作为 BootstrapClassLoader。
ExtClassLoader:扩展类加载器,负责加载 JVM 扩展类,扩展 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,库名通常以 javax 开头。
AppClassLoader,应用类加载器/系统类加载器,直接提供给用户使用的ClassLoader,它会加载 ClASSPATH 环境变量或者 java.class.path 属性里定义的路径中的 jar 包和目录,负责加载包括开发者代码中、第三方库中的类。AppClassLoader 可以由 ClassLoader 类提供的静态方法 getSystemClassLoader() 得到。
ClassLoader.getParent() 可以获取用于委派的父级class loader,通常会返回null来表示bootstrap class loader。
双亲委派模型的代码实现

如上图,实现双亲委派的代码都集中在 java.lang.ClassLoader#loadClass()方法中,其逻辑如下:
先检查是否已被加载过;
若没有加载过则调用父加载器的loadClass()方法;
若父加载器为null则默认使用启动类加载器(Bootstrap ClassLoader)作为父加载器;
如果父加载器加载类失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。(findClass()最终会调用defineClass()加载字节码)
注意:
这里的“双亲”,指的并不是有两个父加载器,可能仅仅是英文“parent”的翻译。每个ClassLoader最多有一个父加载器,也就是parent变量。“双亲委派机制”指的就是优先让父加载器去加载类,如果父加载器没有成功加载到类,才由本ClassLoader加载。
这样可以保证安全性,防止系统类被伪造(比如自定义java.lang.Object类,肯定是无法运行的)。
对于Java程序来讲,一般的类是由AppClassLoader来加载的,而系统类则是由BootStrapClassLoader加载的。由于BootStrapClassLoader是在native层实现的,所以调用系统类的getClassLoader()方法会返回null。
通俗理解
双亲委派机制是 Java 类加载器的重要设计原则,用于确保 Java 应用的安全性和稳定性。为了让这个机制更形象,以下是详细解释和具体例子。
双亲委派机制的定义
双亲委派机制规定,当一个类加载器接收到类加载请求时,它会首先将这个请求委托给它的“父加载器”处理。只有当父加载器无法加载时,子加载器才会尝试自己加载。
这种机制确保:
- 类加载的唯一性:避免重复加载同一个类。
- 核心类的安全性:例如
java.lang.String
等核心类只能由根类加载器加载,防止被篡改。
形象的比喻
可以把双亲委派机制比作一个问题的求解过程,假设有一个学生、小组长、班主任和校长的层级关系:
- 学生有个问题想解决(例如“加载某个类”)。
- 学生先问小组长(子加载器)。
- 小组长说:“先等等,我得先问班主任(父加载器)。”
- 班主任又说:“我得先请示校长(根类加载器)。”
- 如果校长知道答案,就会直接回答。
- 如果校长说:“我也不知道这个问题的答案”,班主任才会尝试自己解决,依次递归到学生。
这个过程保证了所有请求都从最高层级开始尝试解决,避免了低层级重复回答。
而如果最高层都无法解决,就会让学生自行解决
如果我们传入一个恶意类的字节码,这个系统里当然没有这个恶意类的加载器,所以学生自行解决会生成和加载这个恶意类,从而RCE(我是这么认为的,不对的话请指正)
javassist
Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。也就是当需要字节码的时候不需要手动另外新建类然后编译为class
文件
依赖
1
2
3
4
5
|
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.23.1-GA</version>
</dependency>
|
演示
通过不新建类来构建一个不存在的对象
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
51
52
|
//创建类,这是一个单例对象
ClassPool pool = ClassPool.getDefault();
//需要构建的类
CtClass ctClass = pool.makeClass("test.Person");
//新增字段
CtField field$name = new CtField(pool.get("java.lang.String"),"name",ctClass);
//设置访问级别
field$name.setModifiers(Modifier.PRIVATE);
//设置继承
ctClass.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
//也可以给个初始值
ctClass.addField(field$name,CtField.Initializer.constant("ygsr"));
//生成get/set方法
ctClass.addMethod(CtNewMethod.setter("setName",field$name));
ctClass.addMethod(CtNewMethod.getter("getName",field$name));
//新增构造函数
//无参构造函数
CtConstructor cons$no = new CtConstructor(new CtClass[]{},ctClass);
cons$no.setBody("{name = \"ygsr\";}");
ctClass.addConstructor(cons$no);
//有参构造方法
CtConstructor cons$one = new CtConstructor(new CtClass[]{pool.get("java.lang.String")},ctClass);
// $0=this $1,$2,$3... 代表方法参数
cons$one.setBody("{$0.name = $1;}");
ctClass.addConstructor(cons$one);
//或者CtNewConstructor.make
CtConstructor constructor = CtNewConstructor.make("public Person(){ System.out.println(\"abc\");}",ctClass);
ctClass.addConstructor(constructor);
//两者只能使用一者
//创建一个名为print的方法,无参,无回显,输出name值
CtMethod ctMethod = new CtMethod(CtClass.voidType,"print",new CtClass[]{},ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(name);}");
ctClass.addMethod(ctMethod);
//当前目录
final String Path = "E:\javawork\timu\src\main\java\";
//生成.class文件
ctClass.writeFile(Path);
//将ctfClass对象转换为字节码数组
byte[] bytes = ctClass.toBytecode();
|
