Java是以安全著称,但在Java中有一个类是一个Bug级别的存在,那就是Unsafe
. 前面已经说过Unsafe
在java中的使用,此处我们直接说用法:
避免初始化
当你想跳过对象初始化的阶段,或者绕过构造函数的检查,去实例化没有任何公共构造函数的类,可以使用allocateInstance
:
1 2 3 4 5 6 7
| class A { private long a; public A() { this.a = 1; } public long a() { return this.a; } }
|
使用构造函数、反射和unsafe初始化它,将得到不同的结果。
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) throws IllegalAccessException, InstantiationException { A o1 = new A(); System.out.println(o1.a());
A o2 = A.class.newInstance(); System.out.println(o2.a());
A o3 = (A) UnsafeUtils.getUnsafe().allocateInstance(A.class); System.out.println(o3.a()); }
|
内存崩溃(Memory corruption)
我们可以使用Unsafe
去做一些绕过安全的技术:
1 2 3 4 5 6 7
| class Guard { private int ACCESS_ALLOWED = 1;
public boolean giveAccess() { return 42 == ACCESS_ALLOWED; } }
|
当客户端调用giveAccess
代码是,始终返回的都是false
.使用Unsafe
可以绕过权限:
1 2 3 4 5
| Guard guard=new Guard(); System.out.println("修改前:"+guard.giveAccess()); Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED"); unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); System.out.println("修改后:"+guard.giveAccess());
|
计算对象的大小
sizeOf
是返回对象的自身内存大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public static long sizeOf(Object o) { Unsafe u = UnsafeUtils.getUnsafe(); HashSet<Field> fields = new HashSet<Field>(); Class c = o.getClass(); while (c != Object.class) { for (Field f : c.getDeclaredFields()) { if ((f.getModifiers() & Modifier.STATIC) == 0) { fields.add(f); } } c = c.getSuperclass(); } long maxSize = 0; for (Field f : fields) { long offset = u.objectFieldOffset(f); if (offset > maxSize) { maxSize = offset; } }
return ((maxSize/8) + 1) * 8; }
|
如果只是对象类的结构大小,那么可以更简单的实现:
1 2 3 4 5 6 7 8 9
| public static long sizeOf(Object object){ return UnsafeUtils.getUnsafe().getAddress( normalize(UnsafeUtils.getUnsafe().getInt(object, 4L)) + 12L); } private static long normalize(int value) { if(value >= 0) return value; return (~0L >>> 32) & value; }
|
浅拷贝(Shallow copy)
为了计算自身内存的大小,可以简单的添加拷贝的对象方法,标准的解决方案是使用Cloneable
修改代码,或者实现自定义的拷贝方法.
浅拷贝:
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 42 43 44 45 46 47 48 49 50 51 52
| public class ShallowCopy { static Unsafe unsafe = UnsafeUtils.getUnsafe(); public static Object shallowCopy(Object obj) { long size = sizeOf(obj); long start = toAddress(obj); long address = unsafe.allocateMemory(size); unsafe.copyMemory(start, address, size); return fromAddress(address); }
static long toAddress(Object obj) { Object[] array = new Object[] {obj}; long baseOffset = unsafe.arrayBaseOffset(Object[].class); return normalize(unsafe.getInt(array, baseOffset)); }
static Object fromAddress(long address) { Object[] array = new Object[] {null}; long baseOffset = unsafe.arrayBaseOffset(Object[].class); unsafe.putLong(array, baseOffset, address); return array[0]; } public static long sizeOf(Object o) { HashSet<Field> fields = new HashSet<Field>(); Class c = o.getClass(); while (c != Object.class) { for (Field f : c.getDeclaredFields()) { if ((f.getModifiers() & Modifier.STATIC) == 0) { fields.add(f); } } c = c.getSuperclass(); }
long maxSize = 0; for (Field f : fields) { long offset = unsafe.objectFieldOffset(f); if (offset > maxSize) { maxSize = offset; } } return ((maxSize/8) + 1) * 8; } private static long normalize(int value) { if(value >= 0) return value; return (~0L >>> 32) & value; } }
|
隐藏密码(Hide Password)
在Unsafe
类中可以删除内存中的对象,比如密码信息。用户的密码大多数都是byte[]或char[]数组,为什么要使用数组呢?
这个是出于安全的考虑,因为我们可以删除不需要的数组元素,如果是一个字符串对象的话,这可以像一个对象在内存中保存,删除该对象只是删除了引用,真正的数据还保存在内存中。
1 2 3 4 5 6 7 8 9 10 11 12
| String password = new String("l00k@myHor$e"); String fake = new String(password.replaceAll(".", "?")); System.out.println(password); System.out.println(fake);
unsafe.copyMemory( fake, 0L, null, toAddress(password), sizeOf(password));
System.out.println(password); System.out.println(fake);
|