# sun.misc.Unsafe
sun.misc.Unsafe
是 Java 底层 API ( 仅限Java内部使用,反射可调用
) 提供的一个神奇的 Java 类, Unsafe
提供了非常底层的 内存、CAS、线程调度、类、对象
等操作、 Unsafe
正如它的名字一样它提供的几乎所有的方法都是不安全的,本节只讲解如何使用 Unsafe
定义 Java 类、创建类实例。
# 如何获取 Unsafe 对象
Unsafe
是 Java 内部 API,外部是禁止调用的,在编译 Java 类时如果检测到引用了 Unsafe
类也会有禁止使用的警告: Unsafe是内部专用 API, 可能会在未来发行版中删除
。
sun.misc.Unsafe
代码片段:
import sun.reflect.CallerSensitive; | |
import sun.reflect.Reflection; | |
public final class Unsafe { | |
private static final Unsafe theUnsafe; | |
static { | |
theUnsafe = new Unsafe(); | |
省去其他代码...... | |
} | |
private Unsafe() { | |
} | |
@CallerSensitive | |
public static Unsafe getUnsafe() { | |
Class var0 = Reflection.getCallerClass(); | |
if (var0.getClassLoader() != null) { | |
throw new SecurityException("Unsafe"); | |
} else { | |
return theUnsafe; | |
} | |
} | |
省去其他代码...... | |
} |
由上代码片段可以看到, Unsafe
类是一个不能被继承的类且不能直接通过 new
的方式创建 Unsafe
类实例,如果通过 getUnsafe
方法获取 Unsafe
实例还会检查类加载器,默认只允许 Bootstrap Classloader
调用。
既然无法直接通过 Unsafe.getUnsafe()
的方式调用,那么可以使用反射的方式去获取 Unsafe
类实例。
反射获取 Unsafe
类实例代码片段:
// 反射获取 Unsafe 的 theUnsafe 成员变量 | |
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe"); | |
// 反射设置 theUnsafe 访问权限 | |
theUnsafeField.setAccessible(true); | |
// 反射获取 theUnsafe 成员变量值 | |
Unsafe unsafe = (Unsafe) theUnsafeField.get(null); |
当然我们也可以用反射创建 Unsafe
类实例的方式去获取 Unsafe
对象:
// 获取 Unsafe 无参构造方法 | |
Constructor constructor = Unsafe.class.getDeclaredConstructor(); | |
// 修改构造方法访问权限 | |
constructor.setAccessible(true); | |
// 反射创建 Unsafe 类实例,等价于 Unsafe unsafe1 = new Unsafe (); | |
Unsafe unsafe1 = (Unsafe) constructor.newInstance(); |
获取到了 Unsafe
对象我们就可以调用内部的方法了。
# allocateInstance 无视构造方法创建类实例
假设我们有一个叫 com.anbai.sec.unsafe.UnSafeTest
的类,因为某种原因我们不能直接通过反射的方式去创建 UnSafeTest
类实例,那么这个时候使用 Unsafe
的 allocateInstance
方法就可以绕过这个限制了。
UnSafeTest 代码片段:
public class UnSafeTest { | |
private UnSafeTest() { | |
// 假设 RASP 在这个构造方法中插入了 Hook 代码,我们可以利用 Unsafe 来创建类实例 | |
System.out.println("init..."); | |
} | |
} |
使用 Unsafe 创建 UnSafeTest 对象:
// 使用 Unsafe 创建 UnSafeTest 类实例 | |
UnSafeTest test = (UnSafeTest) unsafe1.allocateInstance(UnSafeTest.class); |
Google 的 GSON
库在 JSON 反序列化的时候就使用这个方式来创建类实例,在渗透测试中也会经常遇到这样的限制,比如 RASP 限制了 java.io.FileInputStream
类的构造方法导致我们无法读文件或者限制了 UNIXProcess/ProcessImpl
类的构造方法导致我们无法执行本地命令等。
# defineClass 直接调用 JVM 创建类对象
ClassLoader
章节我们讲了通过 ClassLoader
类的 defineClass0/1/2
方法我们可以直接向 JVM 中注册一个类,如果 ClassLoader
被限制的情况下我们还可以使用 Unsafe
的 defineClass
方法来实现同样的功能。
Unsafe
提供了一个通过传入类名、类字节码的方式就可以定义类的 defineClass
方法:
public native Class defineClass(String var1, byte[] var2, int var3, int var4);
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
使用 Unsafe 创建 TestHelloWorld 对象:
使用 Unsafe 创建 TestHelloWorld 对象:
// 使用 Unsafe 向 JVM 中注册 com.anbai.sec.classloader.TestHelloWorld 类 | |
Class helloWorldClass = unsafe1.defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length); |
或调用需要传入类加载器和保护域的方法:
// 获取系统的类加载器 | |
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 8
以前的版本如果在 Java 8
中应该使用应该调用需要传类加载器和保护域的那个方法。 Java 11
开始 Unsafe
类已经把 defineClass
方法移除了 ( defineAnonymousClass
方法还在),虽然可以使用 java.lang.invoke.MethodHandles.Lookup.defineClass
来代替,但是 MethodHandles
只是间接的调用了 ClassLoader
的 defineClass
,所以一切也就回到了 ClassLoader
。