Java基础:sun.misc.Unsafe

文章发布时间:

最后更新时间:

文章总字数:
737

预计阅读时间:
3 分钟

sun.misc.Unsafe是Java底层API提供的一个神奇的Java类,Unsafe提供了非常底层的内存、CAS、线程调度、类、对象等操作、Unsafe正如它的名字一样它提供的几乎所有的方法都是不安全的,今天只分析如何使用Unsafe定义Java类和创建类实例,并不涉及攻击手法。

如何获取Unsafe对象

Unsafe是Java内部API,外部是禁止调用的,在编译Java类时如果检测到引用了Unsafe类也会有禁止使用的警告:Unsafe是内部专用 API, 可能会在未来发行版中删除。

image-20230901085020325

看到源码,可以发现这个Unsafe并不允许直接使用new的方式来创建对象,只能调用getUnsafe,同时对classLoader进行了严格的限制,只能使用SystemDomainLoader(事实上就是不允许你在外部调用)。

既然无法直接通过Unsafe.getUnsafe()方式调用,那么可以采用反射来获取Unsafe实例。

Unsafe里面有这么一个成员变量:private static final Unsafe theUnsafe = new Unsafe();

那么只需要getDeclaredField即可,Unsafe.class.getDeclaredField("theUnsafe"),但是这里需要注意我们只是获取到了字段的信息,要实际获取到字段的值还要使用get方法,完整反射代码如下:

1
2
3
4
5
public static void main(String[] args) throws Exception{
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
}

当然我们也可以用反射创建Unsafe类实例的方式去获取Unsafe对象(获取默认构造函数):

1
2
3
4
5
6
    public static void main(String[] args) throws Exception{
Constructor<Unsafe> constructor = Unsafe.class.getDeclaredConstructor();
constructor.setAccessible(true);
Unsafe unsafe = (Unsafe) constructor.newInstance();
}
}

获取到了unsafe对象之后就可以调用里面的危险方法了。下面分析两个危险方法。

allocateInstance无视构造方法创建类实例

传入java.lang.class对象调用Unsafe的allocateInstance方法,可以直接跳过构造方法得到类实例。当我们因为某些原因无法使用反射获取类实例时可以考虑这种方法。

1
2
3
4
5
6
7
8
9
10
package com.test.classloader;

public class TestHelloWorld {
private TestHelloWorld(){
System.out.println("init...");
}
public String hello(){
return "Hello World!";
}
}
1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) throws Exception{
Constructor<Unsafe> constructor = Unsafe.class.getDeclaredConstructor();
constructor.setAccessible(true);
Unsafe unsafe = constructor.newInstance();
TestHelloWorld testHelloWorld = (TestHelloWorld) unsafe.allocateInstance(TestHelloWorld.class);
System.out.println(testHelloWorld.hello());
}
}

image-20230901091855994

defineClass直接调用JVM创建类对象

image-20230901092830980

Unsafe里面有我们最喜欢的defineClass,直接使用字节码在JVM注册类。要注意的是这个defineClass有六个参数,需要我们自行传入类加载器和保护域。

1
2
3
4
5
6
7
8
9
10
11
12
// 获取系统的类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();

// 创建默认的保护域
ProtectionDomain domain = new ProtectionDomain(
new CodeSource(null, (Certificate[]) null), null, classLoader, null
);

// 使用Unsafe向JVM中注册com.anbai.sec.classloader.TestHelloWorld类
Class helloWorldClass = unsafe1.defineClass(
TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length, classLoader, domain
);

Unsafe还可以通过defineAnonymousClass方法创建内部类,这里不再多做测试。

注意:Java 11开始Unsafe类已经把defineClass方法移除了(defineAnonymousClass方法还在)