介绍 sun.misc.Unsafe
是Java底层API(仅限Java内部使用,反射可调用
)提供的一个神奇的Java类,Unsafe
提供了非常底层的内存、CAS、线程调度、类、对象
等操作、Unsafe
正如它的名字一样它提供的几乎所有的方法都是不安全的,本节只讲解如何使用Unsafe
定义Java类、创建类实例。
获取unsafe对象 Unsafe
是Java内部API,外部是禁止调用的,在编译Java类时如果检测到引用了Unsafe
类也会有禁止使用的警告:Unsafe是内部专用 API, 可能会在未来发行版中删除
1 2 3 4 5 6 7 public static Unsafe getUnsafe () throws Exception{ Class<?> aClass = Class.forName("sun.misc.Unsafe" ); Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true ); Unsafe unsafe= (Unsafe) declaredConstructor.newInstance(); return unsafe; }
1 2 3 4 5 public static Unsafe getUnsafe () throws Exception{ Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe" ); theUnsafe.setAccessible(true ); return (Unsafe)theUnsafe.get(null ); }
以上这两种方式算是现在比较常用的获取unsafe对象的方式了
基本使用 内存修改 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 public class Unsafe_Stu { public String name = "oceanzbz" ; public int age = 20 ; public static void main (String[] args) throws Exception { Unsafe unsafe = getUnsafe(); Unsafe_Stu unsafeStu = new Unsafe_Stu (); Field age1 = Class.forName("com.ocean.Unsafe_Stu" ).getDeclaredField("age" ); long l = unsafe.objectFieldOffset(age1); unsafe.putInt(unsafeStu,l,22 ); System.out.println(unsafeStu.age); Field name1 = Unsafe_Stu.class.getDeclaredField("name" ); long l1 = unsafe.objectFieldOffset(name1); unsafe.putObject(unsafeStu,l1,"zbz" ); System.out.println(unsafeStu.name); } public static Unsafe getUnsafe () throws Exception{ Class<?> aClass = Class.forName("sun.misc.Unsafe" ); Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true ); Unsafe unsafe= (Unsafe) declaredConstructor.newInstance(); return unsafe; } }
列一下其他操作内存的方法
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 public native long allocateMemory (long bytes) ; public native long reallocateMemory (long address, long bytes) ; public native void freeMemory (long address) ; public native void setMemory (Object o, long offset, long bytes, byte value) ; public native void copyMemory (Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes) ; public native Object getObject (Object o, long offset) ; public native void putObject (Object o, long offset, Object x) ; public native byte getByte (long address) ;public native void putByte (long address, byte x) ;
获取系统信息 1 2 3 4 5 public native int addressSize () ; public native int pageSize () ;
线程调度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public native void unpark (Object thread) ;public native void park (boolean isAbsolute, long time) ;@Deprecated public native void monitorEnter (Object o) ;@Deprecated public native void monitorExit (Object o) ;@Deprecated public native boolean tryMonitorEnter (Object o) ;
获取对象 unsafe可以无视构造方法创建类的实例这个特性可以用来绕过一些检测,比如这里我们使用unsafe来获取一个unsafe对象只是为了演示
1 2 3 Object o = unsafe.allocateInstance(Unsafe.class); System.out.println(o.getClass().getName())
其他方法
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 public native Object allocateInstance (Class<?> cls) throws InstantiationException;public native long objectFieldOffset (Field f) ;public native Object staticFieldBase (Field f) ;public native long staticFieldOffset (Field f) ;public native int getInt (Object o, long offset) ;public native void putInt (Object o, long offset, int x) ;public native Object getObject (Object o, long offset) ;public native void putObject (Object o, long offset, Object x) ;);public native void putIntVolatile (Object o, long offset, int x) ;public native int getIntVolatile (Object o, long offset) ;public native void putOrderedInt (Object o, long offset, int x) ;
创建VM Anonymous Class VM Anonymous Class并不等同于匿名类,这种类具有以下几个特点参考https://paper.seebug.org/1785
[!特点] 1、class名可以是已存在的class的名字,比如java.lang.File,即使如此也不会发生任何问题,java的动态编译特性将会在内存中生成名如 java.lang.File/13063602@38ed5306的class。 —将会使类名极具欺骗性 2、该class的classloader为null。 —在java中classloader为null的为来自BootstrapClassLoader的class,往往会被认定为jdk自带class 3、在JVM中存在大量动态编译产生的class(多为lamada表达式生成),这种class均不会落盘,所以不落盘并不会属于异常特征。 4、无法通过Class.forName()获取到该class的相关内容。 —严重影响通过反射排查该类安全性的检测工具 5、在部分jdk版本中,VM Anonymous Class甚至无法进行restransform。 —这也就意味着我们无法通过attach API去修复这个恶意类 6、该class在transform中的className将会是它的模板类名。 —这将会对那些通过attach方式检测内存马的工具造成极大的误导性
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 package com.ocean; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import sun.misc.Unsafe; import java.io.File; import java.lang.reflect.Constructor; public class vm_Anonymous_test { public static void main (String[] args) throws Exception { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("java.io.File" ); CtMethod make = CtMethod.make(" public String toString() {java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");return null;}" , ctClass); make.setName("toString" ); ctClass.addMethod(make); byte [] bytecode = ctClass.toBytecode(); Unsafe unsafe = getUnsafe(); Class<?> aClass = unsafe.defineAnonymousClass(File.class, bytecode, null ); System.out.println(aClass.getName()); aClass.newInstance().toString(); } public static Unsafe getUnsafe () throws Exception{ Class<?> aClass = Class.forName("sun.misc.Unsafe" ); Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true ); Unsafe unsafe= (Unsafe) declaredConstructor.newInstance(); return unsafe; } }
可以看到成功弹出计算器并且类名还很像jdk自身的类名迷惑性很大。defineAnonymousClass方法的第一个参数随便传入一个类对象即可,第二个参数需要传入一个类的字节码,这里使用javassist简单一点。第三个参数设置为null即可。执行后得到一个类对象,通过newInstance获取实例,再调用了匿名类的toString方法,弹个计算器。而后输出匿名类的类名和Unsafe的类名进行对比,可见,用defineAnonymousClass创建的类名后面,会有”/xxxxxxxx”,这里也算一个特征
class相关操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public native long staticFieldOffset (Field f) ;public native Object staticFieldBase (Field f) ;public native boolean shouldBeInitialized (Class<?> c) ;public native void ensureClassInitialized (Class<?> c) ;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);
还有一些绕过RASP的方式因为还没学到那里暂时占个坑以后补,最后也来一个其他大佬的图
参考:https://www.javasec.org/javase/Unsafe/ https://www.cnblogs.com/bitterz/p/15952315.html https://paper.seebug.org/1785/ https://www.cnblogs.com/nice0e3/p/14102892.html