类加载机制
ClassLoader
一切的Java类都必须经过JVM加载后才能运行,而ClassLoader
的主要作用就是Java类文件的加载。在JVM类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器)
、Extension ClassLoader(扩展类加载器)
、App ClassLoader(系统类加载器)
,AppClassLoader
是默认的类加载器,如果类加载时我们不指定类加载器的情况下,默认会使用AppClassLoader
加载类,ClassLoader.getSystemClassLoader()
返回的系统类加载器也是AppClassLoader
。
ClassLoader
的核心方法:
loadClass
(加载指定的Java类)findClass
(查找指定的Java类)findLoadedClass
(查找JVM已经加载过的类)defineClass
(定义一个Java类)resolveClass
(链接指定的Java类)
Java类动态加载方式
构造一个恶意类Evil
import java.io.IOException;
public class Evil {
static {
try {
Runtime.getRuntime().exec(new String("calc"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
反射加载(初始化)
class Main{
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("Evil");
}
}
forName
的两个重载
Class<?> forName(String name)
Class<?> forName(String name, **boolean** initialize, ClassLoader loader)
修改一下Evil类
public class Evil {
static {
System.out.println(1);
} //A
{
System.out.println(2);
} //B
public Evil(){
System.out.println(3);
} //C
}
进行反射加载
class Main{
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("Evil").newInstance();
}
}
可以看到最先执行的是A
,然后是B
,最后是C
如果不实例化对象,
class Main{
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class.forName("Evil");
}
}
那么只执行A
,这就是类初始化
类加载器(不初始化)
class Main{
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader c =ClassLoader.getSystemClassLoader();
c.loadClass("Evil");
}
}
calc
没有执行,想要执行命令,应该换种写法
class Main{
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
ClassLoader c =ClassLoader.getSystemClassLoader();
c.loadClass("Evil").newInstance();
}
}
使用类加载器加载恶意类,类初始化在实例化对象时执行
ClassLoader加载流程
ClassLoader
调用public Class<?> loadClass(String name)
- 调用
findLoadedClass
方法检查TestHelloWorld
类是否已经初始化,如果JVM已初始化过该类则直接返回类对象。 - 如果创建当前
ClassLoader
时传入了父类加载器(new ClassLoader(父类加载器)
)就使用父类加载器,否则使用JVM的Bootstrap ClassLoader
加载。 - 如果上一步无法加载,那么调用自身的
findClass
方法尝试加载。 - 如果当前的
ClassLoader
没有重写了findClass
方法,那么直接返回类加载失败异常。如果当前类重写了findClass
方法并通过传入的类名找到了对应的类字节码,那么应该调用defineClass
方法去JVM中注册该类 - 如果调用loadClass的时候传入的
resolve
参数为true,那么还需要调用resolveClass
方法链接类,默认为false。 - 返回一个被JVM加载后的
java.lang.Class
类对象。
自写ClassLoader
如果Evil
类不在classpath,可以重写findClasss
方法,在调用defineClass
时传入Evil
的字节码,实现类的加载
.class
文件转byte[]
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
class Main{
public static void main(String[] args) throws IOException {
File f = new File("C:\\Users\\yehep\\IdeaProjects\\demo\\src\\Evil.class");
byte[] bytesArray = new byte[(int) f.length()];
FileInputStream fis = new FileInputStream(f);
fis.read(bytesArray);
fis.close();
print(bytesArray);
}
public static void print(byte[] bytesArray){
System.out.println(Arrays.toString(bytesArray));
}
}
public class EvilClassLoader extends ClassLoader{
private static String name="Evil";
private static byte[] EvilBytes = new byte[]{-54, -2, -70, -66, 0, 0, 0, 64, 0, 34, 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, 19, 106, 97, 118, 97, 47, 105, 111, 47, 73, 79, 69, 120, 99, 101, 112, 116, 105, 111, 110, 7, 0, 22, 1, 0, 26, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 82, 117, 110, 116, 105, 109, 101, 69, 120, 99, 101, 112, 116, 105, 111, 110, 10, 0, 21, 0, 24, 12, 0, 5, 0, 25, 1, 0, 24, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 84, 104, 114, 111, 119, 97, 98, 108, 101, 59, 41, 86, 7, 0, 27, 1, 0, 4, 69, 118, 105, 108, 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, 8, 60, 99, 108, 105, 110, 105, 116, 62, 1, 0, 13, 83, 116, 97, 99, 107, 77, 97, 112, 84, 97, 98, 108, 101, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 9, 69, 118, 105, 108, 46, 106, 97, 118, 97, 0, 33, 0, 26, 0, 2, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6, 0, 1, 0, 28, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 29, 0, 0, 0, 6, 0, 1, 0, 0, 0, 2, 0, 8, 0, 30, 0, 6, 0, 1, 0, 28, 0, 0, 0, 84, 0, 3, 0, 1, 0, 0, 0, 23, -72, 0, 7, 18, 13, -74, 0, 15, 87, -89, 0, 13, 75, -69, 0, 21, 89, 42, -73, 0, 23, -65, -79, 0, 1, 0, 0, 0, 9, 0, 12, 0, 19, 0, 2, 0, 29, 0, 0, 0, 22, 0, 5, 0, 0, 0, 5, 0, 9, 0, 8, 0, 12, 0, 6, 0, 13, 0, 7, 0, 22, 0, 9, 0, 31, 0, 0, 0, 7, 0, 2, 76, 7, 0, 19, 9, 0, 1, 0, 32, 0, 0, 0, 2, 0, 33
};
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
return defineClass(name, EvilBytes, 0, EvilBytes.length);
}
}
class Main{
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
EvilClassLoader e = new EvilClassLoader();
e.loadClass("Evil").newInstance();
}
}
成功执行命令
反射
Java反射(Reflection
)是Java非常重要的动态特性,通过使用反射我们不仅可以获取到任何类的成员方法(Methods
)、成员变量(Fields
)、构造方法(Constructors
)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。
获取Class对象
获取Class对象的方法
//通过类获取
Class c1=Evil.class;
//通过对象获取
Evil evil = new Evil();
Class c2 =evil.getClass();
//反射获取
Class c3 = Class.forName("Evil");
//类加载器获取
Class c4 = ClassLoader.getSystemClassLoader().loadClass("Evil");
反射java.lang.Runtime
对于一个Class
对象,newInstance()
方法是调用这个类的无参构造,而Runtime
类的构造函数是私有的,Runtime
是单例模式
反射java.lang.Runtime
执行系统命令的两种方法
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
class Main{
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class rt = Class.forName("java.lang.Runtime");//反射获取Runtime类
Method exec=rt.getMethod("exec",String.class);//获取exec方法
Method getRuntime =rt.getMethod("getRuntime");//获取getRuntime方法
Object rtObject = getRuntime.invoke(rt);//调用getRuntime方法,获取Runtime类的实例化对象
exec.invoke(rtObject,"calc");//调用exec方法执行命令
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
class Main{
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Class rt = Class.forName("java.lang.Runtime");//反射获取Runtime类
Constructor rtConstructor = rt.getDeclaredConstructor();
rtConstructor.setAccessible(true); //设置私有构造函数为公有
Object rtObject =rtConstructor.newInstance();
Method exec = rt.getMethod("exec", String.class);
exec.invoke(rtObject,"calc");
}
}
反射对类的对象的操作
对方法
Class s=Class.forName("java.lang.String");
Method[] methods=s.getDeclaredMethods();//获取所有方法
Method method = s.getDeclaredMethod("equals", Object.class);//获取方法
method.setAccessible(true);//设置访问权限
System.out.println(method.invoke(s.newInstance(),"1"));//调用方法
对属性
写一个demo类
public class demo {
public int f=1;
}
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
class Main{
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
Class c =Class.forName("demo");
Field[] fields = c.getDeclaredFields();//获取所有成员变量
Field f=c.getField("f");//获取变量f1
Object cObj=c.newInstance();
Object fObj=f.get(cObj);//获取成员值
System.out.println(fObj);
f.set(cObj,114514);//修改值
fObj=f.get(cObj);
System.out.println(fObj);
}
}
同理 setAccessible
可以修改访问权限
修改static final成员
demo类
public class demo {
public static final int f=1;
}
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
class Main{
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
Class c = Class.forName("demo");
Field f = c.getDeclaredField("f");
f.setAccessible(true);
Field modifiers = f.getClass().getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL);
System.out.println(f.get(null));
f.set(null, 114514);
System.out.println(f.get(null));
}
}
反射java.lang.ProcessBuilder
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.List;
class Main{
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
Class pb=Class.forName("java.lang.ProcessBuilder");
Constructor pbConstructor =pb.getConstructor(List.class);
Object pbObj = pbConstructor.newInstance(Arrays.asList("calc"));
Method start=pb.getMethod("start");
start.invoke(pbObj);
}
}
sun.misc.Unsafe
获取Unsafe
对象
Class u = Class.forName("sun.misc.Unsafe");
Constructor uConstructor =u.getDeclaredConstructor();
uConstructor.setAccessible(true);
Object uObj=uConstructor.newInstance();
对于这样一个类
import java.io.IOException;
public class Evil {
public Evil(){
System.exit(0);
}
public void calc() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
我们没办法通过构造函数实例化一个对象,就可以利用Unsafe
类的allocateInstance
方法无视构造方法创建类实例
Class u = Class.forName("sun.misc.Unsafe");
Constructor uConstructor =u.getDeclaredConstructor();
uConstructor.setAccessible(true);
Unsafe uObj=(Unsafe)uConstructor.newInstance();
Evil evilObj=(Evil) uObj.allocateInstance(Evil.class);
evilObj.calc();