Java安全之Unsafe类
2022-06-20 10:30:36 # Java安全

前言

Unsafe类是一个位于sun.misc包下的类,它提供了一些相对底层方法,能够让我们接触到一些更接近操作系统底层的资源,如系统的内存资源、cpu指令等。而通过这些方法,我们能够完成一些普通方法无法实现的功能,例如直接使用偏移地址操作对象、数组等等。但是在使用这些方法提供的便利的同时,也存在一些潜在的安全因素,例如对内存的错误操作可能会引起内存泄漏,严重时甚至可能引起jvm崩溃。因此在使用Unsafe前,我们必须要了解它的工作原理与各方法的应用场景,并且在此基础上仍需要非常谨慎的操作,下面我们正式开始对Unsafe的学习。

Unsafe功能

Unsafe的功能大致如下:

image-20220620103646466

查看Unsfe源码,可以看到该类被final关键字修饰,代表不能被其他类继承

image-20220620103913441

构造方法被private修饰,就表明不能通过new的方式创建Unsafe类的实例,下面的getUnsafe()方法可以返回Unsafe的实例。

image-20220620104204449

查看下isSystemDomainLoader()方法,可以看到如果var0Bootstrap类加载器,那么就会等于”null”,也就是返回true

image-20220620104837746

编写测试方法

image-20220620105153647

Unsafe调用

方式一

该类的成员变量theUnsafe定义为它的实例化,因此可以利用反射获取该变量的值

image-20220620105932116

编写测试代码

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class<?> aClass = Class.forName("sun.misc.Unsafe");
Field theUnsafe = aClass.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe o = (Unsafe)theUnsafe.get(null);
System.out.println(o);
}
}

运行结果

image-20220620110257727

方式二

因为Unsafe类里面存在getUnsafe方法,该方法的返回值是Unsafe的实例对象,所以可以通过反射调用该方法。

编写测试方法

1
2
3
4
5
6
7
8
9
public class Test1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
Class<?> aClass = Class.forName("sun.misc.Unsafe");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Unsafe o = (Unsafe)declaredConstructor.newInstance();
System.out.println(o);
}
}

运行结果

image-20220620112336568

Class相关操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//静态属性的偏移量,用于在对应的Class对象中读写静态属性
public native long staticFieldOffset(Field f);
//获取一个静态字段的对象指针
public native Object staticFieldBase(Field f);
//判断是否需要初始化一个类,通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。 当且仅当ensureClassInitialized方法不生效时返回false
public native boolean shouldBeInitialized(Class<?> c);
//确保类被初始化
public native void ensureClassInitialized(Class<?> c);
//定义一个类,可用于动态创建类,此方法会跳过JVM的所有安全检查,默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例来源于调用者
public native Class<?> defineClass(String name, byte[] b, int off, int len,
ClassLoader loader,
ProtectionDomain protectionDomain);

//定义一个匿名类,可用于动态创建类
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

学习下Unsafe.defineClass()的运用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package org.agent;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import sun.misc.Unsafe;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;

public class Test2 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, CannotCompileException, IOException, NotFoundException, NotFoundException, CannotCompileException, IOException, InvocationTargetException {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String Classname ="org.agent.Commandtest";
ClassPool classPool= ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("org.agent.Commandtest");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

byte[] bytes=payload.toBytecode();
//获取系统加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//创建默认保护域
ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), null, systemClassLoader, null);
Class<?> aClass = Class.forName("sun.misc.Unsafe");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Unsafe unsafe = (Unsafe)declaredConstructor.newInstance();
Class<?> aClass1 = unsafe.defineClass(Classname, bytes, 0, bytes.length, systemClassLoader, protectionDomain);
Object o = aClass1.newInstance();


}
}

在JDK 11版本以后就移除了该方法。但还可以利用defineAnonymousClass方法。

参考

https://www.cnblogs.com/nice0e3/p/14102892.html