Android插件化原理解析——Hook機制之Binder Hook

Android系統通過Binder機制給應用程序提供了一系列的系統服務,諸如ActivityManagerService,ClipboardManager,AudioManager等;這些廣泛存在系統服務給應用程序提供了諸如任務管理,音頻,視頻等異常強大的功能。

插件框架作為各個插件的管理者,為了使得插件能夠無縫地使用這些系統服務,自然會對這些系統服務做出一定的改造(Hook),使得插件的開發和使用更加方便,從而大大降低插件的開發和維護成本。比如,Hook住ActivityManagerService可以讓插件無縫地使用startActivity方法而不是使用特定的方式(比如that語法)來啟動插件或者主程序的任意界面。

我們把這種Hook系統服務的機制稱之為Binder Hook,因為本質上這些服務提供者都是存在于系統各個進程的Binder對象。因此,要理解接下來的內容必須了解Android的Binder機制,可以參考我之前的文章Binder學習指南

閱讀本文之前,可以先clone一份understand-plugin-framework,參考此項目的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

publicObjectgetSystemService(String name){

ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);

returnfetcher ==null?null: fetcher.getService(this);

}

很簡單,所有的service對象都保存在一張map里面,我們再看這個map是怎么初始化的:

1

2

3

4

5

6

registerService(ACCOUNT_SERVICE, new ServiceFetcher(){

public Object createService(ContextImplctx){

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(ContextImplctx){

return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());

}});

但是在ActivityManager這個類內部,也使用了ServiceManager;具體來說,因為ActivityManager里面所有的核心操作都是使用ActivityManagerNative.getDefault()完成的。那么這個語句干了什么呢?

1

2

3

4

5

6

7

privatestaticfinalSingleton gDefault =newSingleton() {

protectedIActivityManagercreate(){

IBinder b = ServiceManager.getService("activity");

IActivityManager am = asInterface(b);

returnam;

}

};

因此,通過分析我們得知,系統Service的使用其實就分為兩步:

1

2

IBinder b = ServiceManager.getService("service_name");// 獲取原始的IBinder對象

IXXInterface in = IXXInterface.Stub.asInterface(b);// 轉換為Service接口

尋找Hook點

插件框架原理解析——Hook機制之動態代理里面我們說過,Hook分為三步,最關鍵的一步就是尋找Hook點。我們現在已經搞清楚了系統服務的使用過程,那么就需要找出在這個過程中,在哪個環節是最合適hook的。

由于系統服務的使用者都是對第二步獲取到的IXXInterface進行操作,因此如果我們要hook掉某個系統服務,只需要把第二步的asInterface方法返回的對象修改為為我們Hook過的對象就可以了。

asInterface過程

接下來我們分析asInterface方法,然后想辦法把這個方法的返回值修改為我們Hook過的系統服務對象。這里我們以系統剪切版服務為例,源碼位置為android.content.IClipboard,IClipboard.Stub.asInterface方法代碼如下:

publicstaticandroid.content.IClipboardasInterface(android.os.IBinder obj){

if((obj ==null)) {

returnnull;

}

android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);// Hook點

if(((iin !=null) && (iininstanceofandroid.content.IClipboard))) {

return((android.content.IClipboard) iin);

}

returnnewandroid.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

IBinderb= ServiceManager.getService("service_name");

因此,我們希望能修改這個getService方法的返回值,讓這個方法返回一個我們偽造過的IBinder對象;這樣,我們可以在自己偽造的IBinder對象的queryLocalInterface方法作處理,進而使得asInterface方法返回在queryLocalInterface方法里面處理過的值,最終實現hook系統服務的目的。

在跟蹤這個getService方法之前我們思考一下,由于系統服務是一系列的遠程Service,它們的本體,也就是Binder本地對象一般都存在于某個單獨的進程,在這個進程之外的其他進程存在的都是這些Binder本地對象的代理。因此在我們的進程里面,存在的也只是這個Binder代理對象,我們也只能對這些Binder代理對象下手。(如果這一段看不懂,建議不要往下看了,先看Binder學習指南)

然后,這個getService是一個靜態方法,如果此方法什么都不做,拿到Binder代理對象之后直接返回;那么我們就無能為力了:我們沒有辦法攔截一個靜態方法,也沒有辦法獲取到這個靜態方法里面的局部變量(即我們希望修改的那個Binder代理對象)。

接下來就可以看這個getService的代碼了:

publicstaticIBindergetService(String name){

try{

IBinder service = sCache.get(name);

if(service !=null) {

returnservice;

}else{

returngetIServiceManager().getService(name);

}

}catch(RemoteException e) {

Log.e(TAG,"error in getService", e);

}

returnnull;

}

天無絕人之路!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機制之動態代理

具體代碼如下,我們用動態代理的方式Hook掉了hasPrimaryClip(),getPrimaryClip()這兩個方法:

publicclassBinderHookHandlerimplementsInvocationHandler{

privatestaticfinalString TAG ="BinderHookHandler";

// 原始的Service對象 (IInterface)

Object base;

publicBinderHookHandler(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) {

thrownewRuntimeException("hooked failed!");

}

}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)

@Override

publicObjectinvoke(Object proxy, Method method, Object[] args)throwsThrowable{

// 把剪切版的內容替換為 "you are hooked"

if("getPrimaryClip".equals(method.getName())) {

Log.d(TAG,"hook getPrimaryClip");

returnClipData.newPlainText(null,"you are hooked");

}

// 欺騙系統,使之認為剪切版上一直有內容

if("hasPrimaryClip".equals(method.getName())) {

returntrue;

}

returnmethod.invoke(base, args);

}

}

注意,我們拿到原始的IBinder對象之后,如果我們希望使用被Hook之前的系統服務,并不能直接使用這個IBinder對象,而是需要使用asInterface方法將它轉換為IClipboard接口;因為getService方法返回的IBinder實際上是一個裸Binder代理對象,它只有與驅動打交道的能力,但是它并不能獨立工作,需要人指揮它;asInterface方法返回的IClipboard.Stub.Proxy類的對象通過操縱這個裸BinderProxy對象從而實現了具體的IClipboard接口定義的操作。

偽造IBinder對象

在上一步中,我們已經偽造好了系統服務對象,現在要做的就是想辦法讓asInterface方法返回我們偽造的對象了;我們偽造一個IBinder對象:

publicclassBinderProxyHookHandlerimplementsInvocationHandler{

privatestaticfinalString TAG ="BinderProxyHookHandler";

// 絕大部分情況下,這是一個BinderProxy對象

// 只有當Service和我們在同一個進程的時候才是Binder本地對象

// 這個基本不可能

IBinder base;

Class stub;

Class iinterface;

publicBinderProxyHookHandler(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

publicObjectinvoke(Object proxy, Method method, Object[] args)throwsThrowable{

if("queryLocalInterface".equals(method.getName())) {

Log.d(TAG,"hook queryLocalInterface");

// 這里直接返回真正被Hook掉的Service接口

// 這里的 queryLocalInterface 就不是原本的意思了

// 我們肯定不會真的返回一個本地接口, 因為我們接管了 asInterface方法的作用

// 因此必須是一個完整的 asInterface 過的 IInterface對象, 既要處理本地對象,也要處理代理對象

// 這只是一個Hook點而已, 它原始的含義已經被我們重定義了; 因為我們會永遠確保這個方法不返回null

// 讓 IClipboard.Stub.asInterface 永遠走到if語句的else分支里面

returnProxy.newProxyInstance(proxy.getClass().getClassLoader(),

// asInterface 的時候會檢測是否是特定類型的接口然后進行強制轉換

// 因此這里的動態代理生成的類型信息的類型必須是正確的

newClass[] { IBinder.class, IInterface.class,this.iinterface },

newBinderHookHandler(base, stub));

}

Log.d(TAG,"method:"+ method.getName());

returnmethod.invoke(base, args);

}

}

我們使用動態代理的方式偽造了一個跟原始IBinder一模一樣的對象,然后在這個偽造的IBinder對象的queryLocalInterface方法里面返回了我們第一步創建的偽造過的系統服務對象;注意看注釋,詳細解釋可以看代碼

替換ServiceManager的IBinder對象

現在就是萬事具備,只欠東風了;我們使用反射的方式修改ServiceManager類里面緩存的Binder對象,使得getService方法返回我們偽造的IBinder對象,進而asInterface方法使用偽造IBinder對象的queryLocalInterface方法返回了我們偽造的系統服務對象。代碼較簡單,如下:

finalString 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(),

newClass[] { IBinder.class },

newBinderProxyHookHandler(rawBinder));

// 把這個hook過的Binder代理對象放進ServiceManager的cache里面

// 以后查詢的時候 會優先查詢緩存里面的Binder, 這樣就會使用被我們修改過的Binder了

Field cacheField = serviceManager.getDeclaredField("sCache");

cacheField.setAccessible(true);

Map cache = (Map) cacheField.get(null);

cache.put(CLIPBOARD_SERVICE, hookedBinder);

接下來,在app里面使用剪切版,比如長按進行粘貼之后,剪切版的內容永遠都是you are hooked了;這樣,我們Hook系統服務的目的宣告完成!詳細的代碼參見github

也許你會問,插件框架會這么hook嗎?如果不是那么插件框架hook這些干什么?插件框架當然不會做替換文本這么無聊的事情,DroidPlugin插件框架管理插件使得插件就像是主程序一樣,因此插件需要使用主程序的剪切版,插件之間也會共用剪切版;其他的一些系統服務也類似,這樣就可以達到插件和宿主程序之間的天衣服縫,水乳交融!另外,ActivityManager以及PackageManager這兩個系統服務雖然也可以通過這種方式hook,但是由于它們的重要性和特殊性,DroidPlugin使用了另外一種方式,我們會單獨講解


轉:http://weishu.me/2016/02/16/understand-plugin-framework-binder-hook/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容