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

Android系統(tǒng)通過Binder機制給應(yīng)用程序提供了一系列的系統(tǒng)服務(wù)缕探,諸如ActivityManagerService愉棱,ClipboardManager唆铐, AudioManager等;這些廣泛存在系統(tǒng)服務(wù)給應(yīng)用程序提供了諸如任務(wù)管理奔滑,音頻艾岂,視頻等異常強大的功能。

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

我們把這種Hook系統(tǒng)服務(wù)的機制稱之為Binder Hook钞啸,因為本質(zhì)上這些服務(wù)提供者都是存在于系統(tǒng)各個進程的Binder對象。因此喇潘,要理解接下來的內(nèi)容必須了解Android的Binder機制体斩,可以參考我之前的文章Binder學(xué)習(xí)指南

閱讀本文之前,可以先clone一份 understand-plugin-framework颖低,參考此項目的binder-hook 模塊硕勿。另外,插件框架原理解析系列文章見索引枫甲。

系統(tǒng)服務(wù)的獲取過程

我們知道系統(tǒng)的各個遠程service對象都是以Binder的形式存在的,而這些Binder有一個管理者扼褪,那就是ServiceManager想幻;我們要Hook掉這些service,自然要從這個ServiceManager下手话浇,不然星羅棋布的Binder廣泛存在于系統(tǒng)的各個角落脏毯,要一個個找出來還真是大海撈針。

回想一下我們使用系統(tǒng)服務(wù)的時候是怎么干的幔崖,想必這個大家一定再熟悉不過了:通過Context對象的getSystemService方法食店;比如要使用ActivityManager

ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

可是這個貌似跟ServiceManager沒有什么關(guān)系啊赏寇?我們再查看getSystemService方法吉嫩;(Context的實現(xiàn)在ContextImpl里面):

public Object getSystemService(String name) {
    ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
    return fetcher == null ? null : fetcher.getService(this);
}

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

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的靜態(tài)初始化塊里面自娩,有的Service是像上面這樣初始化的;可以看到渠退,確實使用了ServiceManager忙迁;當然還有一些service并沒有直接使用ServiceManager脐彩,而是做了一層包裝并返回了這個包裝對象,比如我們的ActivityManager姊扔,它返回的是ActivityManager這個包裝對象:

registerService(ACTIVITY_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
                }});

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

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            IActivityManager am = asInterface(b);
            return am;
        }
    };

因此删豺,通過分析我們得知共虑,系統(tǒng)Service的使用其實就分為兩步:

IBinder b = ServiceManager.getService("service_name"); // 獲取原始的IBinder對象
IXXInterface in = IXXInterface.Stub.asInterface(b); // 轉(zhuǎn)換為Service接口

尋找Hook點

插件框架原理解析——Hook機制之動態(tài)代理里面我們說過,Hook分為三步呀页,最關(guān)鍵的一步就是尋找Hook點妈拌。我們現(xiàn)在已經(jīng)搞清楚了系統(tǒng)服務(wù)的使用過程,那么就需要找出在這個過程中蓬蝶,在哪個環(huán)節(jié)是最合適hook的尘分。

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

asInterface過程

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

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對象,如果有那么直接就是本進程調(diào)用了禾锤;如果不存在那么創(chuàng)建一個代理對象私股,讓代理對象委托驅(qū)動完成跨進程調(diào)用。

觀察這個方法恩掷,前面的那個if語句判空返回肯定動不了手腳倡鲸;最后一句調(diào)用構(gòu)造函數(shù)然后直接返回我們也是無從下手,要修改asInterface方法的返回值黄娘,我們唯一能做的就是從這一句下手:

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

我們可以嘗試修改這個obj對象的queryLocalInterface方法的返回值峭状,并保證這個返回值符合接下來的if條件檢測,那么就達到了修改asInterface方法返回值的目的逼争。

而這個obj對象剛好是我們第一步返回的IBinder對象优床,接下來我們嘗試對這個IBinder對象的queryLocalInterface方法進行hook。

getService過程

上文分析得知誓焦,我們想要修改IBinder對象的queryLocalInterface方法羔巢;獲取IBinder對象的過程如下:

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

因此,我們希望能修改這個getService方法的返回值,讓這個方法返回一個我們偽造過的IBinder對象竿秆;這樣启摄,我們可以在自己偽造的IBinder對象的queryLocalInterface方法作處理,進而使得asInterface方法返回在queryLocalInterface方法里面處理過的值幽钢,最終實現(xiàn)hook系統(tǒng)服務(wù)的目的歉备。

在跟蹤這個getService方法之前我們思考一下,由于系統(tǒng)服務(wù)是一系列的遠程Service匪燕,它們的本體蕾羊,也就是Binder本地對象一般都存在于某個單獨的進程,在這個進程之外的其他進程存在的都是這些Binder本地對象的代理帽驯。因此在我們的進程里面龟再,存在的也只是這個Binder代理對象,我們也只能對這些Binder代理對象下手尼变。(如果這一段看不懂利凑,建議不要往下看了,先看Binder學(xué)習(xí)指南)

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

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

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里面的內(nèi)容為Hook過的IBinder對象院领,由于系統(tǒng)在getService的時候每次都會優(yōu)先查找緩存束凑,因此返回給使用者的都是被我們修改過的對象,從而達到瞞天過海的目的栅盲。

總結(jié)一下,要達到修改系統(tǒng)服務(wù)的目的废恋,我們需要如下兩步:

  1. 首先肯定需要偽造一個系統(tǒng)服務(wù)對象谈秫,接下來就要想辦法讓asInterface能夠返回我們的這個偽造對象而不是原始的系統(tǒng)服務(wù)對象。
  2. 通過上文分析我們知道鱼鼓,只要讓getService返回IBinder對象的queryLocalInterface方法直接返回我們偽造過的系統(tǒng)服務(wù)對象就能達到目的拟烫。所以,我們需要偽造一個IBinder對象迄本,主要是修改它的queryLocalInterface方法硕淑,讓它返回我們偽造的系統(tǒng)服務(wù)對象;然后把這個偽造對象放置在ServiceManager的緩存map里面即可。

我們通過Binder機制的優(yōu)先查找本地Binder對象的這個特性達到了Hook掉系統(tǒng)服務(wù)對象的目的置媳。因此queryLocalInterface也失去了它原本的意義(只查找本地Binder對象于樟,沒有本地對象返回null),這個方法只是一個傀儡拇囊,是我們實現(xiàn)hook系統(tǒng)對象的橋梁:我們通過這個“漏洞”讓asInterface永遠都返回我們偽造過的對象迂曲。由于我們接管了asInterface這個方法的全部,我們偽造過的這個系統(tǒng)服務(wù)對象不能是只擁有本地Binder對象(原始queryLocalInterface方法返回的對象)的能力寥袭,還要有Binder代理對象操縱驅(qū)動的能力路捧。

接下來我們就以Hook系統(tǒng)的剪切版服務(wù)為例,用實際代碼來說明传黄,如何Hook掉系統(tǒng)服務(wù)杰扫。

Hook系統(tǒng)剪切版服務(wù)

偽造剪切版服務(wù)對象

首先我們用代理的方式偽造一個剪切版服務(wù)對象,關(guān)于如何使用代理的方式進行hook以及其中的原理膘掰,可以查看插件框架原理解析——Hook機制之動態(tài)代理章姓。

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

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 {

        // 把剪切版的內(nèi)容替換為 "you are hooked"
        if ("getPrimaryClip".equals(method.getName())) {
            Log.d(TAG, "hook getPrimaryClip");
            return ClipData.newPlainText(null, "you are hooked");
        }

        // 欺騙系統(tǒng),使之認為剪切版上一直有內(nèi)容
        if ("hasPrimaryClip".equals(method.getName())) {
            return true;
        }

        return method.invoke(base, args);
    }
}

注意啤覆,我們拿到原始的IBinder對象之后,如果我們希望使用被Hook之前的系統(tǒng)服務(wù)惭聂,并不能直接使用這個IBinder對象窗声,而是需要使用asInterface方法將它轉(zhuǎn)換為IClipboard接口;因為getService方法返回的IBinder實際上是一個裸Binder代理對象辜纲,它只有與驅(qū)動打交道的能力笨觅,但是它并不能獨立工作,需要人指揮它耕腾;asInterface方法返回的IClipboard.Stub.Proxy類的對象通過操縱這個裸BinderProxy對象從而實現(xiàn)了具體的IClipboard接口定義的操作见剩。

偽造IBinder 對象

在上一步中,我們已經(jīng)偽造好了系統(tǒng)服務(wù)對象扫俺,現(xiàn)在要做的就是想辦法讓asInterface方法返回我們偽造的對象了苍苞;我們偽造一個IBinder對象:

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點而已, 它原始的含義已經(jīng)被我們重定義了; 因為我們會永遠確保這個方法不返回null
            // 讓 IClipboard.Stub.asInterface 永遠走到if語句的else分支里面
            return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),

                    // asInterface 的時候會檢測是否是特定類型的接口然后進行強制轉(zhuǎn)換
                    // 因此這里的動態(tài)代理生成的類型信息的類型必須是正確的
                    new Class[] { IBinder.class, IInterface.class, this.iinterface },
                    new BinderHookHandler(base, stub));
        }

        Log.d(TAG, "method:" + method.getName());
        return method.invoke(base, args);
    }
}

我們使用動態(tài)代理的方式偽造了一個跟原始IBinder一模一樣的對象,然后在這個偽造的IBinder對象的queryLocalInterface方法里面返回了我們第一步創(chuàng)建的偽造過的系統(tǒng)服務(wù)對象狼纬;注意看注釋羹呵,詳細解釋可以看代碼

替換ServiceManager的IBinder對象

現(xiàn)在就是萬事具備,只欠東風(fēng)了疗琉;我們使用反射的方式修改ServiceManager類里面緩存的Binder對象冈欢,使得getService方法返回我們偽造的IBinder對象,進而asInterface方法使用偽造IBinder對象的queryLocalInterface方法返回了我們偽造的系統(tǒng)服務(wù)對象盈简。代碼較簡單凑耻,如下:

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里面
// 以后查詢的時候 會優(yōu)先查詢緩存里面的Binder, 這樣就會使用被我們修改過的Binder了
Field cacheField = serviceManager.getDeclaredField("sCache");
cacheField.setAccessible(true);
Map<String, IBinder> cache = (Map) cacheField.get(null);
cache.put(CLIPBOARD_SERVICE, hookedBinder);

接下來太示,在app里面使用剪切版,比如長按進行粘貼之后香浩,剪切版的內(nèi)容永遠都是you are hooked了类缤;這樣,我們Hook系統(tǒng)服務(wù)的目的宣告完成弃衍!詳細的代碼參見 github呀非。

也許你會問,插件框架會這么hook嗎镜盯?如果不是那么插件框架hook這些干什么岸裙?插件框架當然不會做替換文本這么無聊的事情,DroidPlugin插件框架管理插件使得插件就像是主程序一樣速缆,因此插件需要使用主程序的剪切版降允,插件之間也會共用剪切版;其他的一些系統(tǒng)服務(wù)也類似艺糜,這樣就可以達到插件和宿主程序之間的天衣服縫剧董,水乳交融!另外破停,ActivityManager以及PackageManager這兩個系統(tǒng)服務(wù)雖然也可以通過這種方式hook翅楼,但是由于它們的重要性和特殊性,DroidPlugin使用了另外一種方式真慢,我們會單獨講解毅臊。

喜歡就點個贊吧~持續(xù)更新,請關(guān)注github項目 understand-plugin-framework 和我的 博客!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末黑界,一起剝皮案震驚了整個濱河市管嬉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌朗鸠,老刑警劉巖蚯撩,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異烛占,居然都是意外死亡胎挎,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門忆家,熙熙樓的掌柜王于貴愁眉苦臉地迎上來犹菇,“玉大人,你說我怎么就攤上這事弦赖。” “怎么了浦辨?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵蹬竖,是天一觀的道長沼沈。 經(jīng)常有香客問我,道長币厕,這世上最難降的妖魔是什么列另? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮旦装,結(jié)果婚禮上页衙,老公的妹妹穿的比我還像新娘。我一直安慰自己阴绢,他們只是感情好店乐,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著呻袭,像睡著了一般眨八。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上左电,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天廉侧,我揣著相機與錄音,去河邊找鬼篓足。 笑死段誊,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的栈拖。 我是一名探鬼主播连舍,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辱魁!你這毒婦竟也來了烟瞧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤染簇,失蹤者是張志新(化名)和其女友劉穎参滴,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锻弓,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡砾赔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了青灼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暴心。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖杂拨,靈堂內(nèi)的尸體忽然破棺而出专普,到底是詐尸還是另有隱情,我是刑警寧澤弹沽,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布檀夹,位于F島的核電站筋粗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏炸渡。R本人自食惡果不足惜娜亿,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蚌堵。 院中可真熱鬧买决,春花似錦、人聲如沸吼畏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宫仗。三九已至够挂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間藕夫,已是汗流浹背孽糖。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留毅贮,地道東北人办悟。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像滩褥,于是被迫代替她去往敵國和親病蛉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容