Java中动态加载字节码的⽅法(持续补充)⽂章⽬录
Java中动态加载字节码的⽅法
1、利⽤ URLClassLoader 加载远程class⽂件
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("127.0.0.1:8000/")};
URLClassLoader urlClassLoader =new URLClassLoader(urls);
Class clazz = urlClassLoader.loadClass("Exploit");
}catch(Exception e){
e.printStackTrace();
}
}
2、利⽤ ClassLoader#defineClass 直接加载字节码
2.1 类加载 - 双亲委派模型
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() 得到。
2.2 双亲委派模型的代码实现
如上图,实现双亲委派的代码都集中在 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 replace方法2.3 ⾃定义ClassLoader
java.lang.ClassLoader是⼀个抽象类。创建⼀个继承⾃ClassLoader的类,并重写findClass()⽅法实现类的加载,即可完成⾃定义ClassLoader。⽰例如下:
public class MyClassLoader extends ClassLoader {
private String dirPath;
@Override
public String getName(){
return"MyClassLoader";
}
public MyClassLoader(String dirPath){
if(!dsWith("/")&&!dsWith("\\")){
dirPath +="/";
}
this.dirPath = dirPath;
}
@Override
protected Class<?>findClass(String name)throws ClassNotFoundException {
String filePath = dirPath + place('.','/')+".class";
byte[] b;
Path path;
try{
path = (new URI(filePath));
b = adAllBytes(path);
// defineClass将字节数组转换成Class对象
return defineClass(name, b,0, b.length);
}catch(IOException | URISyntaxException e){
e.printStackTrace();
return null;
}
}
}
2.4 ClassLoader#defineClass() 加载字节码
不管是加载远程class⽂件,还是本地class⽂件,Java都经历了下⾯三个⽅法的调⽤:
从前⾯的分析可知:
loadClass() 的作⽤是从已加载的类、⽗加载器位置寻类(即双亲委派机制),在前⾯没有到的情况下,调⽤当前ClassLoader 的findClass()⽅法;
findClass() 根据URL指定的⽅式来加载类的字节码,其中会调⽤defineClass();
defineClass() 的作⽤是处理传⼊的字节码,返回⼀个Class类型的对象。
使⽤ClassLoader#defineClass()直接加载类字节码的⽰例:
Exploit3.java
public class Exploit3 {
public Exploit3(){
try{
}catch(Exception e){
e.printStackTrace();
}
}
}
public class DefineClassDemo {
public static void main(String[] args){
try{
Method defineClass = DeclaredMethod("defineClass",
String.class,byte[].class,int.class,int.class);
defineClass.setAccessible(true);
byte[] codes = Decoder().decode("(class字节码的base64编码)...");            Class expClazz =(Class) defineClass.SystemClassLoader(), "Exploit3", codes,0, codes.length);
}catch(Exception e){
e.printStackTrace();
}
}
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。