基本流程
Java 程序的运行必须经过编写、编译和运行 3 个步骤:
1、编写:是指在 Java 开发环境中进行程序代码的输入,最终形成后缀名为 .java 的 Java 源文件。
2、编译:是指使用 Java 编译器对源文件进行错误排査的过程,编译后将生成后缀名为 .class 的字节码文件,不像C语言那样生成可执行文件。
3、运行:是指使用 Java 解释器将字节码文件翻译成机器代码,执行并显示结果。
Java 虚拟机(JVM)是运行 Java 程序的软件环境,Java 解释器是 Java 虚拟机的一部分。在运行 Java 程序时,首先会启动 JVM,然后由它来负责解释执行 Java 的字节码程序,并且 Java 字节码程序只能运行于 JVM 之上。这样利用 JVM 就可以把 Java 字节码程序和具体的硬件平台以及操作系统环境分隔开来,只要在不同的计算机上安装了针对特定平台的 JVM,Java 程序就可以运行,而不用考虑当前具体的硬件平台及操作系统环境,也不用考虑字节码文件是在何种平台上生成的。
类加载器
Java 的类加载器是一个重要的组件,负责在程序运行时动态加载 Java 类文件到 Java 虚拟机中。
java中有三种类加载器
- Bootstrap ClassLoader (引导类加载器) 该类加载器实现于JVM层,采用C++编写
- Extension ClassLoader (扩展类加载器)
- App ClassLoader (系统类加载器) 默认的类加载器
BootstrapClassLoader只加载包名为java、javax、sun等开头的类)。
扩展类加载器(ExtensionsClassLoader),由sun.misc.Launcher$ExtClassLoader类实现,用来在/jre/lib/ext或者java.ext.dirs中指明的目录加载java的扩展库.
App类加载器/系统类加载器(AppClassLoader),由sun.misc.Launcher$AppClassLoader实现,一般通过通过(java.class.path或者Classpath环境变量)来加载Java类,也就是我们常说的classpath路径。
Java类加载方式
同时你可能会有疑问,为什么平时没有看到这些类的一个使用。其实我们一般情况下都是使用的隐式的一个类加载方式。
隐式加载
通过创建类的实例、访问类的静态成员、继承类或实现接口等方式隐式加载类。
// 创建类的实例
MyClass obj = new MyClass();
// 访问类的静态成员
int value = MyClass.staticField;
// 继承类
class SubClass extends MyClass { }
// 实现接口
class MyImplementation implements MyInterface { }
显示加载
通过 Class.forName() 方法显式加载类。这种方式会触发类的初始化过程。
String className = "com.example.MyClass";
Class<?> clazz = Class.forName(className);
this.getClass().getClassLoader().loadClass(className);
如果不希望初始化类可以使用Class.forName("类名", 是否初始化类, 类加载器),而ClassLoader.loadClass默认不会初始化类方法。
URLClassLoader类
RLClassLoader继承了ClassLoader,URLClassLoader提供了加载远程资源的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用。注意这里使用JAR包,如果使用class文件,必须本地存在对应的类才行.
import java.net.URL;
import java.net.URLClassLoader;
public class RemoteClassLoaderExample {
public static void main(String[] args) {
try {
// 远程 class 文件的 URL
URL remoteUrl = new URL("http://127.0.0.1:8086/shell.jar");
// 创建 URL 数组
URL[] urls = new URL[]{remoteUrl};
// 创建 URLClassLoader 对象
URLClassLoader classLoader = new URLClassLoader(urls);
// 加载远程类
Class<?> remoteClass = classLoader.loadClass("Test2");
Object instance = remoteClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
BCEL ClassLoader
BCEL的类加载器在解析类名时会对ClassName中有$$BCEL$$
标识的类做特殊处理
package MemoryShell.BCEL;
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;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
public class BCELDemo {
public static void main(String[] args) throws Exception {
JavaClass cls = Repository.lookupClass(calc.class);
String code = Utility.encode(cls.getBytes(), true);
System.out.println(code);
new ClassLoader().loadClass("$$BCEL$$" + code).newInstance();
}
}
Xalan ClassLoader
自定义类加载器
除开上文提到的三种加载器,我们还可以自定义加载器.
本地类加载
public class ClassLoaderStudy extends ClassLoader {
private static final String testClassName = "top.longlone.Hello";
private static final byte[] testClassBytes = Base64.getDecoder().decode("yv66vgAAADQAHAoACAARBwASCgACABEIABMKAAIAFAoAAgAVBwAWBwAXAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABWhlbGxvAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwACQAKAQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIBAAZIZWxsbyAMABgAGQwAGgAbAQASdG9wL2xvbmdsb25lL0hlbGxvAQAQamF2YS9sYW5nL09iamVjdAEABmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEACHRvU3RyaW5nAQAUKClMamF2YS9sYW5nL1N0cmluZzsAIQAHAAgAAAAAAAIAAQAJAAoAAQALAAAAHQABAAEAAAAFKrcAAbEAAAABAAwAAAAGAAEAAAADAAEADQAOAAEACwAAACwAAgACAAAAFLsAAlm3AAMSBLYABSu2AAW2AAawAAAAAQAMAAAABgABAAAABQABAA8AAAACABA=");
@Override
protected 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) throws Exception {
ClassLoaderStudy loader = new ClassLoaderStudy();
Class testClass = loader.loadClass(testClassName);
Object o = testClass.newInstance();
Method sayHello = o.getClass().getMethod("hello", String.class);
String longlone = (String) sayHello.invoke(o, "Longlone");
System.out.println(longlone);
}
}
我们要继承ClassLoader类和覆盖findClass()方法.其中最核心的逻辑是defineClass方法
protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(null, b, off, len, null);
}
String name
: 这是类的全限定名。在类加载器的作用下,该类将会被加载到Java虚拟机中。byte[] b
: 这是一个字节数组,包含了一个类的字节码。这个字节数组通常是通过读取一个类文件(以 .class 结尾的文件)获得的。字节码数组应该包含有效的Java类文件的内容。int off
:这是字节数组b
中的起始偏移量,表示开始转换的位置。int len
: 这是要转换的字节数,表示从偏移量off
处开始的连续字节的长度。
Java类加载隔离
创建类加载器的时候可以指定该类加载的父类加载器,ClassLoader是有隔离机制的,不同的ClassLoader可以加载相同的Class(两者必须是非继承关系)。
假设我们有两个应用程序 App1 和 App2,它们都使用了一个名为 MyClass 的类。我们来看看如何使用不同的类加载器来隔离这两个应用程序:
// App1.java
public class App1 {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.doSomething();
}
}
// App2.java
public class App2 {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.doSomething();
}
}
// MyClass.java
public class MyClass {
public void doSomething() {
System.out.println("MyClass doing something...");
}
}
如果我们使用同一个类加载器加载这两个应用程序,那么它们将共享同一个 MyClass 实例,如果其中一个应用程序修改了 MyClass,另一个应用程序也会受到影响。
为了隔离这两个应用程序,我们可以使用不同的类加载器来加载它们:
// App1Loader.java
public class App1Loader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (name.equals("MyClass")) {
byte[] classBytes = loadClassBytes("MyClass");
return defineClass(name, classBytes, 0, classBytes.length);
}
return super.findClass(name);
}
private byte[] loadClassBytes(String name) {
// 从文件或其他地方加载 MyClass 的字节码
// ...
}
}
// App2Loader.java
public class App2Loader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (name.equals("MyClass")) {
byte[] classBytes = loadClassBytes("MyClass");
return defineClass(name, classBytes, 0, classBytes.length);
}
return super.findClass(name);
}
private byte[] loadClassBytes(String name) {
// 从文件或其他地方加载 MyClass 的字节码
// ...
}
}
// App1.java
public class App1 {
public static void main(String[] args) {
ClassLoader loader = new App1Loader();
Class<?> clazz = loader.loadClass("MyClass");
Object obj = clazz.newInstance();
// 调用 MyClass 的方法
}
}
// App2.java
public class App2 {
public static void main(String[] args) {
ClassLoader loader = new App2Loader();
Class<?> clazz = loader.loadClass("MyClass");
Object obj = clazz.newInstance();
// 调用 MyClass 的方法
}
双亲委派机制
简单来说就是类加载器接到加载类的请求时,首先会先将任务委托给父类加载器,接着请求父类加载这个类,当父类加载器无法加载时(其目录搜素范围没有找到所需要的类时),子类加载器才会进行加载使用。
我们可以看到我们自定义的类是在最下面的,所以我们是无法更改java内部的三个加载器所负责的类.
因为第一个进行加载动作的其实是最顶层的引导类加载器,那么引导类加载器去寻找的目录的java类优先级就是最高的。即使有同名的java类,也不会影响java系统运行。
类加载方式
- 命令行启动应用时候由JVM初始化加载
- 通过Class.forName()方法动态加载
- 通过ClassLoader.loadClass()方法动态加载
他们的区别是
- Class.forName(): 将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
- ClassLoader.loadClass(): 只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
- Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象.
public class loaderTest {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader loader = loaderTest.class.getClassLoader();
System.out.println(loader);
//使用ClassLoader.loadClass()来加载类,不会执行初始化块
System.out.println("使用ClassLoader.loadClass()\n--------------");
loader.loadClass("Test2");
//使用Class.forName()来加载类,默认会执行初始化块
System.out.println("使用Class.forName()\n--------------");
Class.forName("Test2");
//使用Class.forName()来加载类,并指定ClassLoader,初始化时不执行静态块
System.out.println("使用Class.forName()\n--------------");
Class.forName("Test2", false, loader);
}
}
自定义加载器
主要就是继承ClassLoader类,我们自己的一个逻辑书写在findClass方法中
public class ClassLoaderStudy extends ClassLoader {
private static final String testClassName = "Test2";
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data=loadClassData(name);
if (data != null) {
return defineClass(testClassName, data, 0, data.length);
}
return super.findClass(name);
}
private byte[] loadClassData(String className) {
String fileName = File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
try {
System.out.println(fileName);
InputStream ins = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = 0;
while ((length = ins.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
byte[] byteArray = baos.toByteArray();
Base64.Decoder base64Decoder = Base64.getDecoder();
// 对字节数组进行Base64解码
byte[] decodedData = base64Decoder.decode(byteArray);
// 进行Base64解码
return decodedData;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws Exception {
ClassLoaderStudy loader = new ClassLoaderStudy();
Class testClass = loader.loadClass("home/kali/桌面/test");
Object o = testClass.newInstance();
}
}
标签:Class,JAVA,String,流程,class,MyClass,执行,ClassLoader,加载
From: https://www.cnblogs.com/Ho1dF0rward/p/18364753