知識總結 插件化學習 Hook系統方法分析

這里主要講的Hook,是利用java上的動態代理實現替換系統某個類,在方法調用過程中,利用反射,插入自己代碼邏輯的一種方式。

安卓插件化學習 Hook系統服務分析

Hook技術主要用的是java的動態代理,掌握類動態代理,其實不難理解Hook原理,只不過是找到一個我們需要的Hook點,然后動態代理獲取到系統目標類的代理對象,然后就可以在InvocationHandler中對想要修改的方法邏輯插入自己需求邏輯。

一、Hook的必要條件

根據上一篇中Java反射與代理機制 講的動態代理機制,重要的是兩點:

1、Hook對象需要實現一個接口,并且這個接口方法中有我們需要注入代碼的目標方法。

滿足這兩點,就可以利用newProxyInstance方法構造出目標對象的代理類,并且在代理對象反射調用的時候,可以調用到Handler里面的對應方法invoke方法里面。

2、滿足1條件的同時,獲取目標Hook對象。

這點為整個Hook過程中的難點,要想對某個對象的方法Hook,就要得到該類的對象,然后對于系統的類調用,很多情況是不容易獲取對象引用的。然后我們可以找一些靜態變量(不用獲取類實例)或者一些單例類(反正是單例子,肯定就一個),
來降低hook的難度及尋求技巧。

二、系統服務層架構簡單分析

想要hook系統服務,就要熟悉系統服務的基本架構,主要是了解應用與系統交互的Binder架構方式,最好要先了解Binder的相關知識。

結合上節講的Binder,總的來說可以理解系統服務的使用主要理解下面這幾點:

  • 1.調用getSystemService(serviceName)方法可獲取服務對象在本地進程的一個業務邏輯管理類。
  • 2.方法內用到遠端對象的,其實是調用了ServiceManager的getService方法,獲取Ixxx類或xxxManager(以下用Ixxx代替)的遠端Binder實體的一個本地BinderProxy。
  • 3.調用ServiceManager的getService方法獲取遠端服務的IBinder對象,這個過程需要底層Binder驅動完成IPC通信。
  • 4.有了遠端服務的IBinder對象之后,通過Stub類的asInterface方法進行類型轉化,獲取目標接口對象。
  • 5.系統中的服務獲取都是肯定是跨進程的,遠端服務都是在system_server進程中的,所以asInterface方法中返回的是Proxy代理對象,也就是本地端的中間者。
  • 6.最后返回的對象其實就是這個Proxy對象,而這個對象內部使用了靜態代理方式,內部有一個來自遠端的mRemote變量即IBinder對象。然后直接調用方法其實就是調用mRemote的transact方法進行通信了。

三、Android系統中常用Hook點

安卓系統中有很多我們可以直接動態代理的地方,要想了解和發現這些可hook的點,需要我們熟練的通讀和理解源碼知識、比如應用的啟動過程、四大組件的啟動過程、Handler源碼分析、View繪制流程等一系列基本知識體系。

通用的hook方案來接管系統各類ManagerService

我們想要獲取系統的一個服務都會用到這么一段代碼如下:

    XXXManager manager=(XXXManager)getSystemService(XXX_SERVICE);

然后分析getSystemService方法的具體實現,可以發現此類Manager自身系統服務相關方法在應用本地提供的一個代理類,真正的實現方法會同行getService()方法IPC到系統進程。

那么我們分析下安卓源碼的實現,SystemServiceRegistry提供了本地可類Manager的獲取接口,任何找個Manager,分析,比如進入ActivityManager,發現每個方法進步都會調用
ActivityManagerNative.getDefault()方法獲取遠端proxy來IPC系統進程的實現。

其他Manager類似,最終都會通過下面方法,來獲取遠端對象的Proxy。

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

分析到這里,是不是就可以肯定只要我們Hook掉這個本地的代理,就可以騙掉系統遠端實現,并在這個代理類中注入我們需求邏輯,那接下來看看這個本地代理的獲取源碼,符合動態代理要求嗎?

     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);
        }
    }

   
    ......省略
    }

該類中,主要看有個getService方法,和一個sCache 緩存。

sCache中保存的是遠端服務的一個Ibinder對象,很明顯他是實現Ibinder接口的, 并且這兩個東西都是靜態的,意味著我們可以反射調用getService,然后把sCache里的目標Ibinder替換為我們的動態代理對象。

Hook掉這個對象是不是就可以攔截系統方法了呢? 答案是否定的。

應為這里hook掉的是一個IBinder接口,只是Binder驅動給我們的一個BinderProxy,BinderProxy是Binder內部final類型的類只是實現類IBinder接口,并沒有我們需要攔截的方法。

那怎么才能夠攔截我們的目標方法呢? 當然是找到有這些方法的接口類,比如:IActivityManager、IServiceManager、IClipboard等...

那么這些接口類是怎么通過Binder驅動返回的BinderProxy對象來轉化的呢? 做過AIDL開發的,應該很熟悉下面代碼

    //參數就是返回的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,就會返回這個方法里的內容給外界調用的地方(即Ixx類的接口賦值)。

queryLocalInterface方法又正好是IBinder接口中的方法,那么我們已經Hook掉的BinderProxy,再次hook掉BinderProxy的queryLocalInterface()方法,就可以完全替換系統層Ixx
類的遠端服務的本地代理接口。

關鍵代碼如下:

    @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接口的所有系統方法,并且注入自己的代碼邏輯。

到這里大功告成,通過這種方法,基本上系統方法都可以hook掉。但是有沒有別的辦法呢?這種在辦法至少需要有兩個hook點,是否有必要呢?

其實hook是一項技巧活,Hook的次數需要實際情況的而定,要想通過動態代理實現hook,就需要從上面說的兩點出發,獲取可hook的對象及地點,找這兩個點的難易程度決定了整個hook過程的次數及難易程度。

AMS服務的hook分析

在插件化實現過程中,Hook系統AMS是最基本也是最重要的學習內容, 接管AMS才能定制化相關插件邏輯,為應用層開發解耦。

Hook的技術需要靈活應用,比如AMS的Hook本來可以用上面的通用方法Hook掉,那具體問題具體分析,有沒有更加簡單的辦法呢? 有!

想要找到AMS精確的hook點,需要對應用的啟動有一定了解,可以這篇文章分析AMS遠端服務調用機制以及Activity的啟動流程。

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

image.png

看下應用啟動過程源碼及ActivityManager源碼,會發現遠端代理的獲取并沒有每次都采用getService,而是采用單例形式保存在一個靜態變量里。

    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;
        }
    };

從上面代碼可以看出,這里獲取帶遠端的BinderProxy后,通過asInterface()方法轉化成我們的hook目標接口類,并且返回后保存在一個靜態變量里。

這樣就為我們反射和動態代理這個點提供了方便。

1、使用反射獲取這個gDefault里保存的IActivityManager;
2、動態代理產生一個代理類,替換掉這個IActivityManager;

代碼如下:

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

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

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

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

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

四、總結

android平臺動態代理方式hook系統,首選需要對hook的整理邏輯很熟悉,并且能給靈活找到hook地方,核心規則就是:能獲取到要hook點的類對象,然后動態代理替換掉。

通過閱讀源碼發現,安卓平臺架構中,很多地方都是類似的框架,所以我們可以用同樣的辦法取hook掉系統的其他服務。

——————
歡迎轉載,請標明出處:常興E站 www.canking.win

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內容