探索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方法,準确來說是無侵入的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開頭,後邊跟類的全路徑名用“/”分割
基本資料類型有自己的類型簽名
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主要就是通過反射,有很多例子,都是直接反射調用類,然後更改,隻是用了架構,就不用一個個代碼去反射調用更該了,省了很多的時間去做一些你更該後的代碼優化等操作。