1. 程式人生 > >Java安全之Unsafe類

Java安全之Unsafe類

# Java安全之Unsafe類 ## 0x00 前言 前面使用到的一些JNI程式設計和Javaagent等技術,其實在安全裡面的運用非常的有趣和微妙,這個已經說過很多次。後面還會發現一些比較有意思的技術,比如ASM和Unsafe這些。這下面就先來講解Unsafe這個類的使用和實際當中的一些運用場景。 ## 0x01 Unsafe概述 `Unsafe`是位於`sun.misc`包下的一個類,主要提供一些用於執行低級別、不安全操作的方法,如直接訪問系統記憶體資源、自主管理記憶體資源等,這些方法在提升Java執行效率、增強Java語言底層資源操作能力方面起到了很大的作用。使用該類可以獲取到底層的控制權,該類在`sun.misc`包,預設是**BootstrapClassLoader**載入的。 來看一下下面的兩張圖 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201208152642815-2097610135.png) ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201208152650660-1114482836.png) `Unsafe`類是一個不能被繼承的類且不能直接通過`new`的方式建立`Unsafe`類例項。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201208152658073-1490177099.png) ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201208152705191-912011882.png) 這裡可以看到該構造方法是`private`所以說不能直接new該物件,裡面有一個`getUnsafe()`會返回Unsafe的例項。 ```java @CallerSensitive public static Unsafe getUnsafe() { // ----- 這裡去獲取當前類的ClassLoader載入器 Class var0 = Reflection.getCallerClass(); // ----- 判斷var0是不是BootstrapClassLoader if (!VM.isSystemDomainLoader(var0.getClassLoader())) { // ----- 否:丟擲SecurityException異常 throw new SecurityException("Unsafe"); } else { // ----- 是:返回unsafe物件 return theUnsafe; } } ``` 這裡是呼叫了`isSystemDomainLoader`來判斷是否為Bootstrap類載入器,如果是,可以正常獲取Unsafe例項,否則會丟擲安全異常。 ```java public static boolean isSystemDomainLoader(ClassLoader var0) { // ----- 重點是在這裡: // --- 當結果為true時:說明var0是Bootstrap類載入器, // -- 當結果為false時:說明var0是Extension || App || Custom 等類載入器 // ----- 所以回到getUnsafe()函式,當這個函式返回false時,會直接拋異常,不允許載入Unsafe return var0 == null; } ``` 可以來測試一下 ```java package com.UNsafe; import sun.misc.Unsafe; public class test { public static void main(String[] args) { Unsafe unsafe = Unsafe.getUnsafe(); int i = unsafe.addressSize(); } } ``` ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201208152715299-195795807.png) ## 0x02 Unsafe呼叫 前面說到`Unsafe`該類功能去進行直接呼叫,那麼這時候就會想到我們的反射機制。可以利用反射去直接呼叫。在這裡面也有兩種方式去進行反射呼叫。 ### 呼叫方式一: 因為該類將他的例項化定義在`theUnsafe`成員變數裡面,所以可以使用反射直接獲取該變數的值。 ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201208152733228-758552862.png) ```java package com.UNsafe; import sun.misc.Unsafe; import java.lang.reflect.Field; public class test { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { Class aClass = Class.forName("sun.misc.Unsafe"); Field theUnsafe = aClass.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); Unsafe o = (Unsafe)theUnsafe.get(null); int i = o.addressSize(); System.out.println(i); } } ``` 結果: ```java 8 ``` ### 呼叫方式二: 還有種方式就是反射呼叫`getUnsafe()`方法,該方法會直接返回UNsafe例項物件。那麼可以反射獲取該構造方法的例項,然後呼叫該方法 ```java package com.UNsafe; import sun.misc.Unsafe; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; public class test { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException { Class aClass = Class.forName("sun.misc.Unsafe"); Constructor declaredConstructor = aClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true); Unsafe o = (Unsafe)declaredConstructor.newInstance(); int i = o.addressSize(); System.out.println(i); } } ``` 結果: ```java 8 ``` ## 0x03 Unsafe功能 ### 操作記憶體 ```java public native long allocateMemory(long bytes); //分配記憶體, 相當於C++的malloc函式 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); //獲取給定地址值,忽略修飾限定符的訪問限制。與此類似操作還有: getInt,getDouble,getLong,getChar等 public native void putObject(Object o, long offset, Object x); //為給定地址設定值,忽略修飾限定符的訪問限制,與此類似操作還有: putInt,putDouble,putLong,putChar等 public native byte getByte(long address); //獲取給定地址的byte型別的值(當且僅當該記憶體地址為allocateMemory分配時,此方法結果為確定的) public native void putByte(long address, byte x); //為給定地址設定byte型別的值(當且僅當該記憶體地址為allocateMemory分配時,此方法結果才是確定的) ``` ### 獲取系統資訊 ```java public native int addressSize(); //返回系統指標的大小。返回值為4(32位系統)或 8(64位系統)。 public native int pageSize(); //記憶體頁的大小,此值為2的冪次方。 ``` ### 執行緒排程 ```java public native void unpark(Object thread); // 終止掛起的執行緒,恢復正常.java.util.concurrent包中掛起操作都是在LockSupport類實現的,其底層正是使用這兩個方法 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); //嘗試獲取物件鎖 ``` ### 操作物件 ```java // 傳入一個Class物件並建立該例項物件,但不會呼叫構造方法 public native Object allocateInstance(Class cls) throws InstantiationException; // 獲取欄位f在例項物件中的偏移量 public native long objectFieldOffset(Field f); // 返回值就是f.getDeclaringClass() public native Object staticFieldBase(Field f); // 靜態屬性的偏移量,用於在對應的Class物件中讀寫靜態屬性 public native long staticFieldOffset(Field f); // 獲得給定物件偏移量上的int值,所謂的偏移量可以簡單理解為指標指向該變數;的記憶體地址, // 通過偏移量便可得到該物件的變數,進行各種操作 public native int getInt(Object o, long offset); // 設定給定物件上偏移量的int值 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);); // 設定給定物件的int值,使用volatile語義,即設定後立馬更新到記憶體對其他執行緒可見 public native void putIntVolatile(Object o, long offset, int x); // 獲得給定物件的指定偏移量offset的int值,使用volatile語義,總能獲取到最新的int值。 public native int getIntVolatile(Object o, long offset); // 與putIntVolatile一樣,但要求被操作欄位必須有volatile修飾 public native void putOrderedInt(Object o, long offset, int x); ``` 這裡`allocateInstance` 這個方法很有意思,可以不呼叫該構造方法,然後去獲取一個傳入物件的例項。 那麼在安全中會怎麼去使用到該方法呢?假設一個場景,某個類的構造方法被HOOK了,該構造方法是`private`修飾也不能直接去進行new該物件。如果這時候不能使用 反射的機制去進行一個呼叫,那麼這時候就可以使用到該方法進行繞過。 #### 小案例: 定義一個Persion類,並且構造方法為`private`修飾。 ```java package com.demo2; import java.io.Serializable; public class Person implements Serializable { private String name; private int age; public String getName() { return name; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } private Person() { } private Person(String name, int age) { this.name = name; this.age = age; } } ``` 編寫呼叫測試程式碼: ```java package com.UNsafe; import com.demo2.Person; import sun.misc.Unsafe; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class test { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException { Class aClass = Class.forName("sun.misc.Unsafe"); Constructor declaredConstructor = aClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true); Unsafe unsafe = (Unsafe)declaredConstructor.newInstance(); Person person = (Person)unsafe.allocateInstance(Person.class); person.setAge(20); person.setName("nice0e3"); System.out.println(person); } } ``` 執行結果: ```java Person{name='nice0e3', age=20} ``` 不採用反射和new的反射呼叫構造方法。 ### Class相關操作 ```java //靜態屬性的偏移量,用於在對應的Class物件中讀寫靜態屬性 public native long staticFieldOffset(Field f); //獲取一個靜態欄位的物件指標 public native Object staticFieldBase(Field f); //判斷是否需要初始化一個類,通常在獲取一個類的靜態屬性的時候(因為一個類如果沒初始化,它的靜態屬性也不會初始化)使用。 當且僅當ensureClassInitialized方法不生效時返回false public native boolean shouldBeInitialized(Class c); //確保類被初始化 public native void ensureClassInitialized(Class c); //定義一個類,可用於動態建立類,此方法會跳過JVM的所有安全檢查,預設情況下,ClassLoader(類載入器)和ProtectionDomain(保護域)例項來源於呼叫者 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); ``` 這裡面的`defineClass`方法也很有意思,在前面的學習中應該會對`defineClass`方法有比較深刻的印象,比如命令執行 Java的webshell工具實現、還有jsp的一些免殺都會利用到`ClassLoader`的`defineClass`這個方法去將位元組碼給還原成一個類。那麼在這裡的這個`defineClass`的作用上面也說明了,也是可以去定義一個匿名類,並且可以動態去進行一個建立。假設一個場景`ClassLoader.defineClass`不可用後就可以使用`Unsafe.defineClass`。 ### 動態載入類案例 ```java package com.UNsafe; import com.demo2.Person; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.NotFoundException; import sun.misc.Unsafe; import javax.xml.soap.SAAJResult; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.security.CodeSource; import java.security.ProtectionDomain; import java.security.cert.Certificate; public class test { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, CannotCompileException, IOException, NotFoundException { String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; String Classname ="com.nice0e3.Commandtest"; ClassPool classPool= ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass("com.nice0e3.Commandtest"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); byte[] bytes=payload.toBytecode(); //獲取系統載入器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); //建立預設保護域 ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), null, systemClassLoader, null); Class aClass = Class.forName("sun.misc.Unsafe"); Constructor declaredConstructor = aClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true); Unsafe unsafe = (Unsafe)declaredConstructor.newInstance(); Class aClass1 = unsafe.defineClass(Classname, bytes, 0, bytes.length, systemClassLoader, protectionDomain); Object o = aClass1.newInstance(); } } ``` ![](https://img2020.cnblogs.com/blog/1993669/202012/1993669-20201208152801655-1214354559.png) 在JDK 11版本以後就移除了該方法。但是前面說到的`defineAnonymousClass`方法還是存在也可以進行使用。 ### 參考文章 ```java https://www.cnblogs.com/rickiyang/p/11334887.html https://javasec.org/javase/Unsafe/ ``` ## 0x04 結尾 在這裡面由上面的案例可以看出來,結合了分析利用鏈的時候學習的Javassist動態生成類,然後去做轉換成位元組碼Unsafe去進行載入,這其實也能想到一些有趣的利用