Android外掛化原理解析——Hook機制之Binder Hook
Android系統透過Binder機制給應用程式提供了一系列的系統服務,諸如ActivityManagerService
,ClipboardManager
, AudioManager
等;這些廣泛存在系統服務給應用程式提供了諸如任務管理,音訊,影片等異常強大的功能。
外掛框架作為各個外掛的管理者,為了使得外掛能夠無縫地使用這些系統服務,自然會對這些系統服務做出一定的改造(Hook),使得外掛的開發和使用更加方便,從而大大降低外掛的開發和維護成本。比如,Hook住ActivityManagerService
可以讓外掛無縫地使用startActivity
方法而不是使用特定的方式(比如that語法)來啟動外掛或者主程式的任意介面。
我們把這種Hook系統服務的機制稱之為Binder Hook,因為本質上這些服務提供者都是存在於系統各個程式的Binder物件。因此,要理解接下來的內容必須瞭解Android的Binder機制,可以參考我之前的文章
閱讀本文之前,可以先clone一份 ,參考此專案的binder-hook
模組。
系統服務的獲取過程
我們知道系統的各個遠端service物件都是以Binder的形式存在的,而這些Binder有一個管理者,那就是ServiceManager
;我們要Hook掉這些service,自然要從這個ServiceManager
下手,不然星羅棋佈的Binder廣泛存在於系統的各個角落,要一個個找出來還真是大海撈針。
回想一下我們使用系統服務的時候是怎麼幹的,想必這個大家一定再熟悉不過了:透過Context
物件的getSystemService
方法;比如要使用ActivityManager
:
1 |
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); |
可是這個貌似跟ServiceManager
沒有什麼關係啊?我們再檢視getSystemService
方法;(Context的實現在ContextImpl
裡面):
1 2 3 4 |
public Object getSystemService(String name) { ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name); return fetcher == null ? null : fetcher.getService(this); } |
很簡單,所有的service物件都儲存在一張map
裡面,我們再看這個map是怎麼初始化的:
1 2 3 4 5 6 |
registerService(ACCOUNT_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(ACCOUNT_SERVICE); IAccountManager service = IAccountManager.Stub.asInterface(b); return new AccountManager(ctx, service); }}); |
在ContextImpl
的靜態初始化塊裡面,有的Service是像上面這樣初始化的;可以看到,確實使用了ServiceManager
;當然還有一些service並沒有直接使用ServiceManager
,而是做了一層包裝並返回了這個包裝物件,比如我們的ActivityManager
,它返回的是ActivityManager
這個包裝物件:
1 2 3 4 |
registerService(ACTIVITY_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler()); }}); |
但是在ActivityManager
這個類內部,也使用了ServiceManager
;具體來說,因為ActivityManager裡面所有的核心操作都是使用ActivityManagerNative.getDefault()
完成的。那麼這個語句幹了什麼呢?
1 2 3 4 5 6 7 |
private static final Singleton |
因此,透過分析我們得知,系統Service的使用其實就分為兩步:
1 2 |
IBinder b = ServiceManager.getService("service_name"); // 獲取原始的IBinder物件 IXXInterface in = IXXInterface.Stub.asInterface(b); // 轉換為Service介面 |
尋找Hook點
在裡面我們說過,Hook分為三步,最關鍵的一步就是尋找Hook點。我們現在已經搞清楚了系統服務的使用過程,那麼就需要找出在這個過程中,在哪個環節是最合適hook的。
由於系統服務的使用者都是對第二步獲取到的IXXInterface
進行操作,因此如果我們要hook掉某個系統服務,只需要把第二步的asInterface
方法返回的物件修改為為我們Hook過的物件就可以了。
asInterface過程
接下來我們分析asInterface
方法,然後想辦法把這個方法的返回值修改為我們Hook過的系統服務物件。這裡我們以系統剪下版服務為例,原始碼位置為android.content.IClipboard
,IClipboard.Stub.asInterface
方法程式碼如下:
1 2 3 4 5 6 7 8 9 10 |
public static android.content.IClipboard asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // Hook點 if (((iin != null) && (iin instanceof android.content.IClipboard))) { return ((android.content.IClipboard) iin); } return new android.content.IClipboard.Stub.Proxy(obj); } |
這個方法的意思就是:先檢視本程式是否存在這個Binder物件,如果有那麼直接就是本程式呼叫了;如果不存在那麼建立一個代理物件,讓代理物件委託驅動完成跨程式呼叫。
觀察這個方法,前面的那個if語句判空返回肯定動不了手腳;最後一句呼叫建構函式然後直接返回我們也是無從下手,要修改asInterface
方法的返回值,我們唯一能做的就是從這一句下手:
1 |
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // Hook點 |
我們可以嘗試修改這個obj
物件的queryLocalInterface
方法的返回值,並保證這個返回值符合接下來的if
條件檢測,那麼就達到了修改asInterface
方法返回值的目的。
而這個obj
物件剛好是我們第一步返回的IBinder
物件,接下來我們嘗試對這個IBinder
物件的queryLocalInterface
方法進行hook。
getService過程
上文分析得知,我們想要修改IBinder
物件的queryLocalInterface
方法;獲取IBinder
物件的過程如下:
1 |
IBinder b = ServiceManager.getService("service_name"); |
因此,我們希望能修改這個getService
方法的返回值,讓這個方法返回一個我們偽造過的IBinder
物件;這樣,我們可以在自己偽造的IBinder
物件的queryLocalInterface
方法作處理,進而使得asInterface
方法返回在queryLocalInterface
方法裡面處理過的值,最終實現hook系統服務的目的。
在跟蹤這個getService
方法之前我們思考一下,由於系統服務是一系列的遠端Service,它們的本體,也就是Binder本地物件一般都存在於某個單獨的程式,在這個程式之外的其他程式存在的都是這些Binder本地物件的代理。因此在我們的程式裡面,存在的也只是這個Binder代理物件,我們也只能對這些Binder代理物件下手。(如果這一段看不懂,建議不要往下看了,先看)
然後,這個getService
是一個靜態方法,如果此方法什麼都不做,拿到Binder代理物件之後直接返回;那麼我們就無能為力了:我們沒有辦法攔截一個靜態方法,也沒有辦法獲取到這個靜態方法裡面的區域性變數(即我們希望修改的那個Binder代理物件)。
接下來就可以看這個getService
的程式碼了:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public static IBinder getService(String name) { try { IBinder service = sCache.get(name); if (service != null) { return service; } else { return getIServiceManager().getService(name); } } catch (RemoteException e) { Log.e(TAG, "error in getService", e); } return null; } |
天無絕人之路!ServiceManager
為了避免每次都進行跨程式通訊,把這些Binder代理物件快取在一張map
裡面。
我們可以替換這個map裡面的內容為Hook過的IBinder
物件,由於系統在getService
的時候每次都會優先查詢快取,因此返回給使用者的都是被我們修改過的物件,從而達到瞞天過海的目的。
總結一下,要達到修改系統服務的目的,我們需要如下兩步:
首先肯定需要偽造一個系統服務物件,接下來就要想辦法讓
asInterface
能夠返回我們的這個偽造物件而不是原始的系統服務物件。透過上文分析我們知道,只要讓
getService
返回IBinder
物件的queryLocalInterface
方法直接返回我們偽造過的系統服務物件就能達到目的。所以,我們需要偽造一個IBinder物件,主要是修改它的queryLocalInterface
方法,讓它返回我們偽造的系統服務物件;然後把這個偽造物件放置在ServiceManager
的快取map
裡面即可。
我們透過Binder機制的優先查詢本地Binder物件的這個特性達到了Hook掉系統服務物件的目的。因此queryLocalInterface
也失去了它原本的意義(只查詢本地Binder物件,沒有本地物件返回null),這個方法只是一個傀儡,是我們實現hook系統物件的橋樑:我們透過這個“漏洞”讓asInterface
永遠都返回我們偽造過的物件。由於我們接管了asInterface
這個方法的全部,我們偽造過的這個系統服務物件不能是隻擁有本地Binder物件(原始queryLocalInterface
方法返回的物件)的能力,還要有Binder代理物件操縱驅動的能力。
接下來我們就以Hook系統的剪下版服務為例,用實際程式碼來說明,如何Hook掉系統服務。
Hook系統剪下版服務
偽造剪下版服務物件
首先我們用代理的方式偽造一個剪下版服務物件,關於如何使用代理的方式進行hook以及其中的原理,可以檢視。
具體程式碼如下,我們用動態代理的方式Hook掉了hasPrimaryClip()
,getPrimaryClip()
這兩個方法:
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 |
public class BinderHookHandler implements InvocationHandler { private static final String TAG = "BinderHookHandler"; // 原始的Service物件 (IInterface) Object base; public BinderHookHandler(IBinder base, Class> stubClass) { try { Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class); // IClipboard.Stub.asInterface(base); this.base = asInterfaceMethod.invoke(null, base); } catch (Exception e) { throw new RuntimeException("hooked failed!"); } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 把剪下版的內容替換為 "you are hooked" if ("getPrimaryClip".equals(method.getName())) { Log.d(TAG, "hook getPrimaryClip"); return ClipData.newPlainText(null, "you are hooked"); } // 欺騙系統,使之認為剪下版上一直有內容 if ("hasPrimaryClip".equals(method.getName())) { return true; } return method.invoke(base, args); } } |
注意,我們拿到原始的IBinder
物件之後,如果我們希望使用被Hook之前的系統服務,並不能直接使用這個IBinder
物件,而是需要使用asInterface
方法將它轉換為IClipboard
介面;因為getService
方法返回的IBinder
實際上是一個裸Binder代理物件,它只有與驅動打交道的能力,但是它並不能獨立工作,需要人指揮它;asInterface
方法返回的IClipboard.Stub.Proxy
類的物件透過操縱這個裸BinderProxy
物件從而實現了具體的IClipboard
介面定義的操作。
偽造IBinder
物件
在上一步中,我們已經偽造好了系統服務物件,現在要做的就是想辦法讓asInterface
方法返回我們偽造的物件了;我們偽造一個IBinder
物件:
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 |
public class BinderProxyHookHandler implements InvocationHandler { private static final String TAG = "BinderProxyHookHandler"; // 絕大部分情況下,這是一個BinderProxy物件 // 只有當Service和我們在同一個程式的時候才是Binder本地物件 // 這個基本不可能 IBinder base; Class> stub; Class> iinterface; public BinderProxyHookHandler(IBinder base) { this.base = base; try { this.stub = Class.forName("android.content.IClipboard$Stub"); this.iinterface = Class.forName("android.content.IClipboard"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("queryLocalInterface".equals(method.getName())) { Log.d(TAG, "hook queryLocalInterface"); // 這裡直接返回真正被Hook掉的Service介面 // 這裡的 queryLocalInterface 就不是原本的意思了 // 我們肯定不會真的返回一個本地介面, 因為我們接管了 asInterface方法的作用 // 因此必須是一個完整的 asInterface 過的 IInterface物件, 既要處理本地物件,也要處理代理物件 // 這只是一個Hook點而已, 它原始的含義已經被我們重定義了; 因為我們會永遠確保這個方法不返回null // 讓 IClipboard.Stub.asInterface 永遠走到if語句的else分支裡面 return Proxy.newProxyInstance(proxy.getClass().getClassLoader(), // asInterface 的時候會檢測是否是特定型別的介面然後進行強制轉換 // 因此這裡的動態代理生成的型別資訊的型別必須是正確的 new Class[] { IBinder.class, IInterface.class, this.iinterface }, new BinderHookHandler(base, stub)); } Log.d(TAG, "method:" + method.getName()); return method.invoke(base, args); } } |
我們使用動態代理的方式偽造了一個跟原始IBinder
一模一樣的物件,然後在這個偽造的IBinder
物件的queryLocalInterface
方法裡面返回了我們第一步建立的偽造過的系統服務物件;注意看註釋,詳細解釋可以看
替換ServiceManager的IBinder
物件
現在就是萬事具備,只欠東風了;我們使用反射的方式修改ServiceManager
類裡面快取的Binder物件,使得getService
方法返回我們偽造的IBinder
物件,進而asInterface
方法使用偽造IBinder
物件的queryLocalInterface
方法返回了我們偽造的系統服務物件。程式碼較簡單,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
final String CLIPBOARD_SERVICE = "clipboard"; // 下面這一段的意思實際就是: ServiceManager.getService("clipboard"); // 只不過 ServiceManager這個類是@hide的 Class> serviceManager = Class.forName("android.os.ServiceManager"); Method getService = serviceManager.getDeclaredMethod("getService", String.class); // ServiceManager裡面管理的原始的Clipboard Binder物件 // 一般來說這是一個Binder代理物件 IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE); // Hook 掉這個Binder代理物件的 queryLocalInterface 方法 // 然後在 queryLocalInterface 返回一個IInterface物件, hook掉我們感興趣的方法即可. IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(), new Class>[] { IBinder.class }, new BinderProxyHookHandler(rawBinder)); // 把這個hook過的Binder代理物件放進ServiceManager的cache裡面 // 以後查詢的時候 會優先查詢快取裡面的Binder, 這樣就會使用被我們修改過的Binder了 Field cacheField = serviceManager.getDeclaredField("sCache"); cacheField.setAccessible(true); Map |
接下來,在app裡面使用剪下版,比如長按進行貼上之後,剪下版的內容永遠都是you are hooked
了;這樣,我們Hook系統服務的目的宣告完成!詳細的程式碼參見 。
也許你會問,外掛框架會這麼hook嗎?如果不是那麼外掛框架hook這些幹什麼?外掛框架當然不會做替換文字這麼無聊的事情,DroidPlugin外掛框架管理外掛使得外掛就像是主程式一樣,因此外掛需要使用主程式的剪下版,外掛之間也會共用剪下版;其他的一些系統服務也類似,這樣就可以達到外掛和宿主程式之間的天衣服縫,水乳交融!另外,ActivityManager
以及PackageManager
這兩個系統服務雖然也可以透過這種方式hook,但是由於它們的重要性和特殊性,DroidPlugin使用了另外一種方式,我們會單獨講解。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3407/viewspace-2814853/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android 外掛化原理解析(3):Hook 機制之 Binder HookAndroidHook
- Android 外掛化原理解析——Hook機制之AMS&PMSAndroidHook
- PostgreSQL外掛hook機制SQLHook
- Activity外掛化原理第二種方案:Hook IActivityManagerHook
- 外掛化知識梳理(6) Small 原始碼分析之 Hook 原理原始碼Hook
- Activity外掛化原理第一種方案:Hook InstrumentationHook
- 如何使用外掛化機制優雅的封裝你的請求hook封裝Hook
- Ajax-hook原理解析Hook
- Hook原理Hook
- 以pytorch的forward hook為例探究hook機制PyTorchForwardHook
- Hook技術之Hook ActivityHook
- Android外掛化原理解析——概要Android
- Libco Hook 機制淺析Hook
- [Android]用架構師角度看外掛化(2)-Replugin 唯一hook點Android架構PluginHook
- Android 外掛化原理解析(1):概要Android
- Android 外掛框架機制之SmallAndroid框架
- hook初識之inline hookHookinline
- Binder學習(二)Binder機制解析
- Android外掛化原理(一)Activity外掛化Android
- Android Hook框架Xposed原理與原始碼分析AndroidHook框架原始碼
- Android 外掛框架機制之預熱篇Android框架
- Android系統之Binder通訊機制Android
- Taro cli流程和外掛化機制實現原理
- React Hook原始碼解析(二)ReactHook原始碼
- Android 外掛化原理解析(7):廣播的管理Android
- webpack外掛機制之TapableWeb
- HOOK API入門之Hook自己程式的MessageBoxWHookAPI
- Binder機制之AIDLAI
- React學習之HookReactHook
- Android Binder機制淺析Android
- Android的IPC機制BinderAndroid
- [Hook] 跨程式 Binder設計與實現 - 設計篇Hook
- hook!Hook
- Android.Hook框架Cydia篇(脫殼機制作)AndroidHook框架
- Android進階(六)Binder機制Android
- android Binder機制深入淺出Android
- Android Binder機制文章轉載Android
- Android hook 技術淺析AndroidHook