知識(shí)總結(jié) 插件化學(xué)習(xí) Hook系統(tǒng)方法分析

這里主要講的Hook颗管,是利用java上的動(dòng)態(tài)代理實(shí)現(xiàn)替換系統(tǒng)某個(gè)類蔫慧,在方法調(diào)用過程中,利用反射蛾茉,插入自己代碼邏輯的一種方式讼呢。

安卓插件化學(xué)習(xí) Hook系統(tǒng)服務(wù)分析

Hook技術(shù)主要用的是java的動(dòng)態(tài)代理,掌握類動(dòng)態(tài)代理谦炬,其實(shí)不難理解Hook原理悦屏,只不過是找到一個(gè)我們需要的Hook點(diǎn),然后動(dòng)態(tài)代理獲取到系統(tǒng)目標(biāo)類的代理對象键思,然后就可以在InvocationHandler中對想要修改的方法邏輯插入自己需求邏輯础爬。

一、Hook的必要條件

根據(jù)上一篇中Java反射與代理機(jī)制 講的動(dòng)態(tài)代理機(jī)制吼鳞,重要的是兩點(diǎn):

1看蚜、Hook對象需要實(shí)現(xiàn)一個(gè)接口,并且這個(gè)接口方法中有我們需要注入代碼的目標(biāo)方法赔桌。

滿足這兩點(diǎn)供炎,就可以利用newProxyInstance方法構(gòu)造出目標(biāo)對象的代理類渴逻,并且在代理對象反射調(diào)用的時(shí)候,可以調(diào)用到Handler里面的對應(yīng)方法invoke方法里面音诫。

2裸卫、滿足1條件的同時(shí),獲取目標(biāo)Hook對象纽竣。

這點(diǎn)為整個(gè)Hook過程中的難點(diǎn)墓贿,要想對某個(gè)對象的方法Hook,就要得到該類的對象蜓氨,然后對于系統(tǒng)的類調(diào)用聋袋,很多情況是不容易獲取對象引用的。然后我們可以找一些靜態(tài)變量(不用獲取類實(shí)例)或者一些單例類(反正是單例子穴吹,肯定就一個(gè))幽勒,
來降低hook的難度及尋求技巧。

二港令、系統(tǒng)服務(wù)層架構(gòu)簡單分析

想要hook系統(tǒng)服務(wù)啥容,就要熟悉系統(tǒng)服務(wù)的基本架構(gòu),主要是了解應(yīng)用與系統(tǒng)交互的Binder架構(gòu)方式顷霹,最好要先了解Binder的相關(guān)知識(shí)咪惠。

結(jié)合上節(jié)講的Binder,總的來說可以理解系統(tǒng)服務(wù)的使用主要理解下面這幾點(diǎn):

  • 1.調(diào)用getSystemService(serviceName)方法可獲取服務(wù)對象在本地進(jìn)程的一個(gè)業(yè)務(wù)邏輯管理類淋淀。
  • 2.方法內(nèi)用到遠(yuǎn)端對象的遥昧,其實(shí)是調(diào)用了ServiceManager的getService方法,獲取Ixxx類或xxxManager(以下用Ixxx代替)的遠(yuǎn)端Binder實(shí)體的一個(gè)本地BinderProxy朵纷。
  • 3.調(diào)用ServiceManager的getService方法獲取遠(yuǎn)端服務(wù)的IBinder對象炭臭,這個(gè)過程需要底層Binder驅(qū)動(dòng)完成IPC通信。
  • 4.有了遠(yuǎn)端服務(wù)的IBinder對象之后袍辞,通過Stub類的asInterface方法進(jìn)行類型轉(zhuǎn)化鞋仍,獲取目標(biāo)接口對象。
  • 5.系統(tǒng)中的服務(wù)獲取都是肯定是跨進(jìn)程的搅吁,遠(yuǎn)端服務(wù)都是在system_server進(jìn)程中的威创,所以asInterface方法中返回的是Proxy代理對象,也就是本地端的中間者似芝。
  • 6.最后返回的對象其實(shí)就是這個(gè)Proxy對象那婉,而這個(gè)對象內(nèi)部使用了靜態(tài)代理方式板甘,內(nèi)部有一個(gè)來自遠(yuǎn)端的mRemote變量即IBinder對象党瓮。然后直接調(diào)用方法其實(shí)就是調(diào)用mRemote的transact方法進(jìn)行通信了。

三盐类、Android系統(tǒng)中常用Hook點(diǎn)

安卓系統(tǒng)中有很多我們可以直接動(dòng)態(tài)代理的地方寞奸,要想了解和發(fā)現(xiàn)這些可hook的點(diǎn)呛谜,需要我們熟練的通讀和理解源碼知識(shí)、比如應(yīng)用的啟動(dòng)過程、四大組件的啟動(dòng)過程、Handler源碼分析蠢护、View繪制流程等一系列基本知識(shí)體系勒奇。

通用的hook方案來接管系統(tǒng)各類ManagerService

我們想要獲取系統(tǒng)的一個(gè)服務(wù)都會(huì)用到這么一段代碼如下:

    XXXManager manager=(XXXManager)getSystemService(XXX_SERVICE);

然后分析getSystemService方法的具體實(shí)現(xiàn),可以發(fā)現(xiàn)此類Manager自身系統(tǒng)服務(wù)相關(guān)方法在應(yīng)用本地提供的一個(gè)代理類驰弄,真正的實(shí)現(xiàn)方法會(huì)同行g(shù)etService()方法IPC到系統(tǒng)進(jìn)程。

那么我們分析下安卓源碼的實(shí)現(xiàn),SystemServiceRegistry提供了本地可類Manager的獲取接口妒牙,任何找個(gè)Manager,分析对妄,比如進(jìn)入ActivityManager湘今,發(fā)現(xiàn)每個(gè)方法進(jìn)步都會(huì)調(diào)用
ActivityManagerNative.getDefault()方法獲取遠(yuǎn)端proxy來IPC系統(tǒng)進(jìn)程的實(shí)現(xiàn)。

其他Manager類似剪菱,最終都會(huì)通過下面方法摩瞎,來獲取遠(yuǎn)端對象的Proxy。

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

分析到這里孝常,是不是就可以肯定只要我們Hook掉這個(gè)本地的代理旗们,就可以騙掉系統(tǒng)遠(yuǎn)端實(shí)現(xiàn),并在這個(gè)代理類中注入我們需求邏輯构灸,那接下來看看這個(gè)本地代理的獲取源碼蚪拦,符合動(dòng)態(tài)代理要求嗎?

     public final class ServiceManager {
    private static final String TAG = "ServiceManager";

    private static IServiceManager sServiceManager;
    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }

    /**
     * Returns a reference to a service with the given name.
     * 
     * @param name the name of the service to get
     * @return a reference to the service, or <code>null</code> if the service doesn't exist
     */
    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;
    }

    /**
     * Place a new @a service called @a name into the service
     * manager.
     * 
     * @param name the name of the new service
     * @param service the service object
     */
    public static void addService(String name, IBinder service) {
        try {
            getIServiceManager().addService(name, service, false);
        } catch (RemoteException e) {
            Log.e(TAG, "error in addService", e);
        }
    }

   
    ......省略
    }

該類中冻押,主要看有個(gè)getService方法驰贷,和一個(gè)sCache 緩存。

sCache中保存的是遠(yuǎn)端服務(wù)的一個(gè)Ibinder對象洛巢,很明顯他是實(shí)現(xiàn)Ibinder接口的括袒, 并且這兩個(gè)東西都是靜態(tài)的,意味著我們可以反射調(diào)用getService稿茉,然后把sCache里的目標(biāo)Ibinder替換為我們的動(dòng)態(tài)代理對象锹锰。

Hook掉這個(gè)對象是不是就可以攔截系統(tǒng)方法了呢? 答案是否定的漓库。

應(yīng)為這里hook掉的是一個(gè)IBinder接口恃慧,只是Binder驅(qū)動(dòng)給我們的一個(gè)BinderProxy,BinderProxy是Binder內(nèi)部final類型的類只是實(shí)現(xiàn)類IBinder接口渺蒿,并沒有我們需要攔截的方法痢士。

那怎么才能夠攔截我們的目標(biāo)方法呢? 當(dāng)然是找到有這些方法的接口類茂装,比如:IActivityManager怠蹂、IServiceManager善延、IClipboard等...

那么這些接口類是怎么通過Binder驅(qū)動(dòng)返回的BinderProxy對象來轉(zhuǎn)化的呢? 做過AIDL開發(fā)的城侧,應(yīng)該很熟悉下面代碼

    //參數(shù)就是返回的BinderProxy
    public static Ixxx asInterface(IBinder obj) {
            if(obj == null) {
                return null;
            } else {
                IInterface iin = obj.queryLocalInterface("android.app.Ixxx");
                return (Ixxx)(iin != null && iin instanceof Ixxx?(Ixxx)iin:new Ixxx.Stub.Proxy(obj));
            }
        }

可以看出易遣,只要obj.queryLocalInterface返回不為null,就會(huì)返回這個(gè)方法里的內(nèi)容給外界調(diào)用的地方(即Ixx類的接口賦值)嫌佑。

queryLocalInterface方法又正好是IBinder接口中的方法豆茫,那么我們已經(jīng)Hook掉的BinderProxy,再次hook掉BinderProxy的queryLocalInterface()方法屋摇,就可以完全替換系統(tǒng)層Ixx
類的遠(yuǎn)端服務(wù)的本地代理接口澜薄。

關(guān)鍵代碼如下:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if("queryLocalInterface".equals(method.getName()){
                return Proxy.newProxyInstance(getClassLoader(),
                                            new Class[]{Ixxx},
                                            new HookBinderInvocationHandler();
            }
    }

通過以上代碼,就可以在HookBinderInvocationHandler類中摊册,的invoke方法中連接Ixx接口的所有系統(tǒng)方法肤京,并且注入自己的代碼邏輯。

到這里大功告成茅特,通過這種方法忘分,基本上系統(tǒng)方法都可以hook掉。但是有沒有別的辦法呢白修?這種在辦法至少需要有兩個(gè)hook點(diǎn)妒峦,是否有必要呢?

其實(shí)hook是一項(xiàng)技巧活兵睛,Hook的次數(shù)需要實(shí)際情況的而定肯骇,要想通過動(dòng)態(tài)代理實(shí)現(xiàn)hook,就需要從上面說的兩點(diǎn)出發(fā)祖很,獲取可hook的對象及地點(diǎn)笛丙,找這兩個(gè)點(diǎn)的難易程度決定了整個(gè)hook過程的次數(shù)及難易程度。

AMS服務(wù)的hook分析

在插件化實(shí)現(xiàn)過程中假颇,Hook系統(tǒng)AMS是最基本也是最重要的學(xué)習(xí)內(nèi)容, 接管AMS才能定制化相關(guān)插件邏輯胚鸯,為應(yīng)用層開發(fā)解耦。

Hook的技術(shù)需要靈活應(yīng)用笨鸡,比如AMS的Hook本來可以用上面的通用方法Hook掉姜钳,那具體問題具體分析,有沒有更加簡單的辦法呢形耗? 有哥桥!

想要找到AMS精確的hook點(diǎn),需要對應(yīng)用的啟動(dòng)有一定了解激涤,可以這篇文章分析AMS遠(yuǎn)端服務(wù)調(diào)用機(jī)制以及Activity的啟動(dòng)流程拟糕。

通用Hook方案中,由于第一次hook無法獲取Ixx類的接口對象,所以多了一次hook已卸。然后是不是只要我們一次性獲取Ixx類的實(shí)例對象,就可以一次Hook完成接管系統(tǒng)服務(wù)硼一。

image.png

看下應(yīng)用啟動(dòng)過程源碼及ActivityManager源碼累澡,會(huì)發(fā)現(xiàn)遠(yuǎn)端代理的獲取并沒有每次都采用getService,而是采用單例形式保存在一個(gè)靜態(tài)變量里般贼。

    ActivityManagerNative.getDefault()
    
    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };

從上面代碼可以看出愧哟,這里獲取帶遠(yuǎn)端的BinderProxy后,通過asInterface()方法轉(zhuǎn)化成我們的hook目標(biāo)接口類哼蛆,并且返回后保存在一個(gè)靜態(tài)變量里蕊梧。

這樣就為我們反射和動(dòng)態(tài)代理這個(gè)點(diǎn)提供了方便。

1腮介、使用反射獲取這個(gè)gDefault里保存的IActivityManager肥矢;
2、動(dòng)態(tài)代理產(chǎn)生一個(gè)代理類叠洗,替換掉這個(gè)IActivityManager甘改;

代碼如下:

    try{
        Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");

        // 獲取 gDefault 這個(gè)字段, 想辦法替換它
        Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
        gDefaultField.setAccessible(true);
        Object gDefault = gDefaultField.get(null);

        // 4.x以上的gDefault是一個(gè) android.util.Singleton對象; 我們?nèi)〕鲞@個(gè)單例里面的字段
        Class<?> singleton = Class.forName("android.util.Singleton");
        Field mInstanceField = singleton.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);

        // ActivityManagerNative 的gDefault對象里面原始的 IActivityManager對象
        Object rawIActivityManager = mInstanceField.get(gDefault);

        // 創(chuàng)建一個(gè)這個(gè)對象的代理對象, 然后替換這個(gè)字段, 讓我們的代理對象幫忙干活
        Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
        new Class<?>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));
        mInstanceField.set(gDefault, proxy);
    }

四、總結(jié)

android平臺(tái)動(dòng)態(tài)代理方式hook系統(tǒng)灭抑,首選需要對hook的整理邏輯很熟悉十艾,并且能給靈活找到hook地方,核心規(guī)則就是:能獲取到要hook點(diǎn)的類對象腾节,然后動(dòng)態(tài)代理替換掉忘嫉。

通過閱讀源碼發(fā)現(xiàn),安卓平臺(tái)架構(gòu)中案腺,很多地方都是類似的框架庆冕,所以我們可以用同樣的辦法取hook掉系統(tǒng)的其他服務(wù)。

——————
歡迎轉(zhuǎn)載劈榨,請標(biāo)明出處:常興E站 www.canking.win

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末愧杯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鞋既,更是在濱河造成了極大的恐慌力九,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邑闺,死亡現(xiàn)場離奇詭異跌前,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)陡舅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門抵乓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事灾炭【ビ螅” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵蜈出,是天一觀的道長田弥。 經(jīng)常有香客問我,道長铡原,這世上最難降的妖魔是什么偷厦? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮燕刻,結(jié)果婚禮上只泼,老公的妹妹穿的比我還像新娘。我一直安慰自己卵洗,他們只是感情好请唱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著过蹂,像睡著了一般籍滴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上榴啸,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天孽惰,我揣著相機(jī)與錄音,去河邊找鬼鸥印。 笑死勋功,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的库说。 我是一名探鬼主播狂鞋,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼潜的!你這毒婦竟也來了骚揍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤啰挪,失蹤者是張志新(化名)和其女友劉穎信不,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亡呵,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抽活,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锰什。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片下硕。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡丁逝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梭姓,到底是詐尸還是另有隱情霜幼,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布誉尖,位于F島的核電站罪既,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏释牺。R本人自食惡果不足惜萝衩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一回挽、第九天 我趴在偏房一處隱蔽的房頂上張望没咙。 院中可真熱鬧,春花似錦千劈、人聲如沸祭刚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涡驮。三九已至,卻和暖如春喜滨,著一層夾襖步出監(jiān)牢的瞬間捉捅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工虽风, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棒口,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓辜膝,卻偏偏與公主長得像无牵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子厂抖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

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