参考文章:https://javasec.org/javase/Unsafe/
Unsafe类源码自我认知
这个Unsafe类位于package sun.misc
包中
Unsafe使用了1个接口,4个类
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import sun.reflect.CallerSensitive; //该为接口
import sun.reflect.Reflection;
从如下定义方式可知,该类是final类型的变量,那么也就是无法继承的!
public final class Unsafe
Unsafe定义了一堆如下属性,需要注意的是theUnsafe
private static final Unsafe theUnsafe; //这个属性保存了一个Unsafe对象,为什么呢?继续看后面
public static final int INVALID_FIELD_OFFSET = -1;
public static final int ARRAY_BOOLEAN_BASE_OFFSET;
public static final int ARRAY_BYTE_BASE_OFFSET;
public static final int ARRAY_SHORT_BASE_OFFSET;
public static final int ARRAY_CHAR_BASE_OFFSET;
public static final int ARRAY_INT_BASE_OFFSET;
public static final int ARRAY_LONG_BASE_OFFSET;
public static final int ARRAY_FLOAT_BASE_OFFSET;
public static final int ARRAY_DOUBLE_BASE_OFFSET;
public static final int ARRAY_OBJECT_BASE_OFFSET;
public static final int ARRAY_BOOLEAN_INDEX_SCALE;
public static final int ARRAY_BYTE_INDEX_SCALE;
public static final int ARRAY_SHORT_INDEX_SCALE;
public static final int ARRAY_CHAR_INDEX_SCALE;
public static final int ARRAY_INT_INDEX_SCALE;
public static final int ARRAY_LONG_INDEX_SCALE;
public static final int ARRAY_FLOAT_INDEX_SCALE;
public static final int ARRAY_DOUBLE_INDEX_SCALE;
public static final int ARRAY_OBJECT_INDEX_SCALE;
public static final int ADDRESS_SIZE;
它的构造函数是私有的,这就导致了它无法进行共有的实例化
它有一个getUnsafe方法来进行获取本身
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
它定义了一大堆的getter setter属性的方法,不止如下图所示的数量
我还看到了它关于操控内存的方法,如下方法
public native long allocateMemory(long var1); //分配内存
public native long reallocateMemory(long var1, long var3); //申请内存
public native void setMemory(Object var1, long var2, long var4, byte var6); //设置内存
public void setMemory(long var1, long var3, byte var5) { //设置内存
this.setMemory((Object)null, var1, var3, var5);
}
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);//复制内存
public void copyMemory(long var1, long var3, long var5) { //复制内存
this.copyMemory((Object)null, var1, (Object)null, var3, var5);
}
public native void freeMemory(long var1); //释放内存
还有三个特殊的如下方法:
public native Class defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6); //定义类
public native Class defineAnonymousClass(Class var1, byte[] var2, Object[] var3); //定义匿名类
public native Object allocateInstance(Class var1) throws InstantiationException; //分配实例
静态代码块,这里就要讲为什么theUnsafe会保存一个Unsafe对象了
static {
registerNatives();
Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
theUnsafe = new Unsafe(); //这里就说明了为什么theUnsafe会保存一个Unsafe对象
ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class);
ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);
ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);
ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);
ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class);
ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class);
ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class);
ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);
ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);
ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class);
ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class);
ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class);
ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);
ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class);
ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class);
ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
ADDRESS_SIZE = theUnsafe.addressSize();
}
我查了下静态代码块的概念:static代码块指的是static{}包裹的代码块,且静态代码只执行一次,可以通过Class.forName("classPath")
的方式唤醒代码的static代码块,但是也执行一次
我等下需要做个试验来证明是不是只有Class.forName("classPath")
来唤醒static代码块还是咋的!
Unsafe类的方法学习
现在在对该类的方法进行学习:
因为上面说了,不能直接实例化,因为默认的构造方法是私有的,所以现在有两种获取该类的对象的方法
第一种:通过反射来获取该类的无参构造方法,然后进行实例化!
// 获取Unsafe无参构造方法
Constructor constructor = Unsafe.class.getDeclaredConstructor();
// 修改构造方法访问权限
constructor.setAccessible(true);
// 反射创建Unsafe类实例,等价于 Unsafe unsafe1 = new Unsafe();
Unsafe unsafe1 = (Unsafe) constructor.newInstance();
第二种:通过反射来获取该类中的属性theUnsafe,然后通过Unsafe实例化对象来接受
上面自己说做的试验的其实就是这里的问题,因为自己发现笔记中做的代码是如下代码,并且运行了是可以获取到该对象的!
// 反射获取Unsafe的theUnsafe成员变量
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
// 反射设置theUnsafe访问权限
theUnsafeField.setAccessible(true);
// 反射获取theUnsafe成员变量值
Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
System.out.println(unsafe.toString());
那么是不是也说明Unsafe.class
操作也会触发static代码块???
结果如下:
关于第一个强大功能:allocateInstance,该方法可以绕过构造方法创建类实例
什么时候用到该方法呢?利用场景:开头被Hook,可以通过allocateInstace来绕过
public class UnSafeTest {
private UnSafeTest() {
// 假设RASP在这个构造方法中插入了Hook代码,我们可以利用Unsafe来创建类实例
System.out.println("init...");
}
}
那么只需要做的事情就是如下的一句话
UnSafeTest unsafetest = Unsafe对象.allocateInstace(UnSafeTest);
defineClass的妙用
什么时候用到Unsafe类中的defineClass呢?
利用场景:当ClassLoader类被限制的情况下,如果能够向JVM虚拟机注册一个类呢?通过Unsafe中的defineClass方法也是一种方法,这里自己想了下是不是不要局限于Unsafe类中,其实其他类中如果有defineClass方法的话也是可以利用的,当然自己没实战过,现在只需要记住就行了!
//这里自己通过Unsafe类的defineClass来获取MyClassLoader包中的TestHelloWorld类,这个类就是javasec第一篇中创建的类
//首先先获取Unsafe的类的实例,然后获取该构造器
Constructor SafeConstructor = Unsafe.class.getDeclaredConstructor();
//因为Unsafe构造器默认是私有的,所以还要设置权限
SafeConstructor.setAccessible(true);
//然后调用newIntance生成一个实例返回
Unsafe unsafe = (Unsafe)SafeConstructor.newInstance();
// 默认ClassLoader
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
//默认保护域
ProtectionDomain domain = new ProtectionDomain(
new CodeSource(null, (Certificate[]) null), null, classLoader, null
);
//通过Unsafe类的defineClass来绕过ClassLoader来创建TestHelloWorld类的对象
Class helloWorld = unsafe.defineClass(TEST_CLASS_NAME, TEST_CLASS_BYTES, 0, TEST_CLASS_BYTES.length, classLoader, domain);
Constructor WorldConstructor = helloWorld.getDeclaredConstructor();
TestHelloWorld testHelloWorld = (TestHelloWorld) WorldConstructor.newInstance();
System.out.println(testHelloWorld.hello());