天天看點

探索Android的HOOK之旅

探索Android的HOOK之旅

因為項目需求,最近在研究Android的hook方法,總結一下研究的結果,做一個筆記。

因為我隻研究了Android中Java層的代碼hook,Native層的hook還未實際研究與應用。

一、Hook是什麼

Hook的中文翻譯是“鈎子”,簡單的說就是,鈎住你的代碼,對你的方法執行前後做一些操作,例如:改變方法傳回值,改變傳入方法的參數,替換現有的方法。

當然,這些改變都是不可以在原有代碼上進行更改,因為改變原有方法,有時成本很大,并且很繁瑣,而且hook不光是hook自己寫的代碼,還可以hook系統原有的代碼。例如你要更改你代碼所有的文字樣式,在Android中也就是TextView,如果在應用已經完成,再去自定義一個TextView更改起來就很麻煩,此時你隻需要hook住TextView中相應的方法,就可以更改所有文字的樣式了。

二、Hook的應用

Hook 的應用場景有很多,前邊提到的hook其實并不常用,常用的hook應該就是熱更新技術了。所謂的熱更新,就是在應用不重新安裝新版本,而修複和更新一些bug和功能。

因為熱更新技術就是hook應用的完美展現,是以圍繞熱更新技術去研究hook技術,是一個不錯的方向。

三、熱更新技術

熱更新技術阿裡和騰訊都有自己的實作方案,阿裡的Sophix,微信用的Tinker,

這是三種熱更新技術的對比圖。網上有很多詳細的,這裡就不講了。

探索Android的HOOK之旅
探索Android的HOOK之旅

四、我走過的路

因為公司需求并不是熱更新,一開始我一直在尋找Android的Hook方法,準确來說是無侵入的Hook方法,就是不改變原有代碼的基礎上去hook。

最先看的到是Xposed架構,這個架構實作hook更改手機所有應用的代碼,架構很好,但是需要手機進行了root權限,這就直接pass了。

後來又找到的Legend的架構,這個的确是實作了我的需求,它隻需要很少的代碼,就可以hook到你自己應用中的方法,對方法進行更改,但是很可惜,他并不可以在Android 7.0以上的系統上運作。這個作者好像是在高二時候寫的(給跪),現在也不更新了,是以隻好放棄了。

後來我采用的是YAHFA去做的,這個架構總體還是很好的,支援7.0,雖然7.0是在測試階段,但是并沒有發現什麼錯誤。隻不過他不是在本應用中添加代碼,而是以插件的形式,把你的代碼打包成一個debug.apk,存儲到sd卡中,進行加載,進而更改代碼。這更像是熱更新的操作,隻是如果sd卡中删除了這個debug.apk,就失效了。不過熱更新用這個應該很不錯,我的需求,我更喜歡在本應用中添加hook方法,而不是插件式添加。

五、YAHFA的應用

GitHub位址:https://github.com/rk700/YAHFA

很感謝作者提供的這個架構。

在GitHub中,作者對架構的介紹已經很詳細了,我就簡單的再說一下。

下載下傳demo後導入Android studio,其中有一個demoApp和demoPlugin,還有一個library,library中就是hook的邏輯,其實hook就是代碼的反射調用,AOP程式設計的一些邏輯,不知道AOP的朋友可以百度一下。

把demoApp安裝到你的手機,把demoPlugin打包成一個debug.apk,存入你的手機記憶體,隻要在demoApp中的lication的類中指定這個dubug的路徑就好。

在demoPlugin中編寫你要hook的方法。

className是指定的hoook的類名

methodName是指定要hook 的方法

methodSig是指定的類型簽名

(Ljava/lang/String;)Ljava/net/URI;

(Ljava/lang/String;)Ljava/net/URI;
           

括号中是參數的簽名,括号外是傳回值的簽名

簽名是jni中經常用法哦的,如歸不是基本資料類型,就要L開頭,後邊跟類的全路徑名用“/”分割

基本資料類型有自己的類型簽名

探索Android的HOOK之旅

hook方法是你重寫的代碼邏輯

origin方法是你的原放啊

如果傳回值不是基本資料類型,就寫成Object

oringin方法中寫什麼都可以,不影響運作

1.靜态方法

public class Hook_url {
    public static String className = "java.net.URI";
    public static String methodName = "create";
    public static String methodSig = "(Ljava/lang/String;)Ljava/net/URI;";
    public static Object hook(String url)
    {

        Log.w("YAHFA", "in hook_url: "+url);
        Log.w("YAHFA", "網址");
        url = "http://suggest.taobao.com/sug?code=utf-8&q=襪子&callback=cb";
        return origin(url);
    }

    public static Object origin(String url)
    {
        Log.w("YAHFA", "String.startsWith() should not be here");
        return url;
    }
}
           

這是靜态方法的hook,靜态方法和靜态差不多,差別就是,非靜态的方法在hook和origin的參數中,多加一個Object 的參數

url是傳入的參數,把url更改,在調用origin,這樣就是更改了通路網址

2. 非靜态方法

public class hookbean {
    public static String className = "lab.galaxy.yahfa.demoApp.DemoBean";
    public static String methodName = "getbean";
    public static String methodSig =
            "(Ljava/lang/String;Ljava/lang/String;)Llab/galaxy/yahfa/demoApp/Bean;";

    public static Object hook(Object obj,String a, String b) {
        Log.w("YAHFA", "in getBean: "+a+b);
        a = "ni";
        b = "咱們";
        return origin(obj,a, b);
    }

    public static Object origin(Object obj,String a, String b) {
        Log.w("YAHFA", "ClassWithStaticMethod.tac() should not be here");
        return "";
    }
}
           

 這是非靜态方法的hook,傳回值是我自定義的類,是以簽名是我的全路徑,傳回值類型是Object

3. 傳回值為非基本資料類型

可以參考2,靜态和非靜态都是如此

4. 參數是非基本資料類型

public class Hook_WebViewClient_onReceivedSslError {
    public static String className = "android.webkit.WebViewClient";
    public static String methodName = "onReceivedSslError";
    public static String methodSig =
            "(Landroid/webkit/WebView;Landroid/webkit/SslErrorHandler;Landroid/net/http/SslError;)V";
    public static void hook(Object thiz, WebView webView, SslErrorHandler handler, SslError error) {
        Log.w("YAHFA", "WebViewClient.onReceivedSslError()");
        handler.proceed();
    }
}
           

參數是非基本資料類型,傳回值是void

任何情況下都可以不寫origin方法,這樣隻是不調用原方法了

5. Jni方法

以後補充

6. jar中的方法

如果需要hook第三方依賴,jar包的方法,需要把debug.apk中也添加依賴和jar

7.内部類的hook

内部類隻是編譯時的概念,一旦編譯成功,就會出現兩個不同的類,例如,類outClass中有個intClass,那麼編譯後就出現一個名為outClass.class和一個outClass$intClass.class的類。是以className中就要指定類路徑為a.b.c.outClass$intClass。

六、總結

Hook主要就是通過反射,有很多例子,都是直接反射調用類,然後更改,隻是用了架構,就不用一個個代碼去反射調用更該了,省了很多的時間去做一些你更該後的代碼優化等操作。

繼續閱讀