hook控制浏览器的方法_Java-Hook技术-入门实践+反射、动态代理、热修复再看看
延續之前的MonkeyLei:Android-模塊化、組件化、插件化、熱修復-插件化-起個頭,我們復習下里面的關于反射和動態代理點的知識。然后嘗試簡單了解下Hook...
看之前文章,記得多復習下反射代理,比如使用這些....:
public class Proxy extends Object implements Serializable Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.To create a proxy for some interface Foo:InvocationHandler handler = new MyInvocationHandler(...);Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[] { Foo.class });Foo f = (Foo) proxyClass.getConstructor(new Class[] { InvocationHandler.class }).newInstance(new Object[] { handler });or more simply:Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class[] { Foo.class },handler);多多實踐。之后我們嘗試建一個Android工程,但是先用Java main方法來做相關Hook測試。后面再嘗試結合Hook Android的東西.SO..開始吧...
Then,看下Hook基本介紹吧...
一、什么是 Hook 技術Hook 技術又叫做鉤子函數,在系統沒有調用該函數之前,鉤子程序就先捕獲該消息,鉤子函數先得到控制權,這時鉤子函數既可以加工處理(改變)該函數的執行行為,還可以強制結束消息的傳遞。簡單來說,就是把系統的程序拉出來變成我們自己執行代碼片段。要實現鉤子函數,有兩個步驟:1. 利用系統內部提供的接口,通過實現該接口,然后注入進系統(特定場景下使用)2.動態代理(使用所有場景)二、Hook 技術實現的步驟Hook 技術實現的步驟也分為兩步1.找到 hook 點(Java 層),該 hook 點必須滿足以下的條件:需要 hook 的方法,所屬的對象必須是靜態的,因為我們是通過反射來獲取對象的,我們獲取的是系統的對象,所以不能夠 new 一個新的對象,必須用系統創建的那個對象,所以只有靜態的才能保證和系統的對象一致。2.將 hook 方法放到系統之外執行(放入我們自己的邏輯)我就以我覺得的比較簡單的方式來理解一下Hook,我要實現的功能是:
1. 繼承某個可以繼承的對象,然后重寫某個方法,添加中間處理,比如驗證等。完事了既可以用super調用父類的方法.
2. 然后用這個新的對象變量替換掉原有的變量,實現對象變量的動態替換
3. 重點也就是Field、Proxy的基本使用
直接看測試代碼 - HookTestMain.java
package com.skl.hooktest;import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;public class HookTestMain {private static class MyView{// 通過反射替換對象的該對象變量private MyTestView myTestView; // = new MyTestView();static class MyTestView{public void test(){System.out.println("啊啊啊");}}// 通過Proxy生成代理對象,然后替換該變量private ProcInterface other2;}/*** 重寫舊有的某個方法,作為新的對象注入替換掉MyView的myTestView變量*/private static class Other extends MyView.MyTestView{@Overridepublic void test(){System.out.println("BBBBB");super.test();}}/*** Proxy生成代理對象必須是某個接口*/private interface ProcInterface{void test();}public static void main(String[] args){try {// 創建一個對象 - 我們即將替換這個對象的某個變量,達到替換方法的效果;// --我是不是可以想象一下,如果要做方法熱修復,是不是也可以呢?// --但是這個是限于我們有該對象的前提,如果是其他情況,可能就需要你去找到某個對象 它的某個方法,進而實現替換?MyView myView = new MyView();// myView.myTestView.test();// 這是內部靜態類類的表示方法Field field = MyView.class.getDeclaredField("myTestView");field.setAccessible(true);// System.out.println(field.getName());// 用新的對象替換掉myView對象內部的對象變量Other other = new Other();field.set(myView, other);// 或者用Proxy方法生成代理對象,這種方式下,代理的對象必須實現某個接口Field field2 = MyView.class.getDeclaredField("other2");field2.setAccessible(true);ProcInterface other2 = (ProcInterface) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{ProcInterface.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("這是代理的對象呀 method=" + method);return null;}});// 替換對象變量,然后運行field2.set(myView, other2);myView.other2.test();// 重新運行該方法,達到替換方法的效果!myView.myTestView.test();} catch (NoSuchFieldException e) {System.out.println(e.getMessage());} catch (IllegalAccessException e) {System.out.println(e.getMessage());}//**************此時我們嘗試加載Apk文件,然后拿到補丁的方法,來修復上面對象的方法} }解釋步驟:
1. 定義一個MyView類,該類包含一個MyTestView類,然后定義了一個MyTestView對象變量myTestView。
private static class MyView{// 通過反射替換對象的該對象變量private MyTestView myTestView; // = new MyTestView();static class MyTestView{public void test(){System.out.println("啊啊啊");}}}2. 定一個類Other繼承MyTestView,然后重新它的test方法,同時加入自己的操作
/*** 重寫舊有的某個方法,作為新的對象注入替換掉MyView的myTestView變量*/private static class Other extends MyView.MyTestView{@Overridepublic void test(){System.out.println("BBBBB");super.test();}}3. 測試流程,首先創建一個MyView的對象,然后我們將針對這個對象進行反射操作,Hook掉它的myTestView變量
public static void main(String[] args){try {// 創建一個對象 - 我們即將替換這個對象的某個變量,達到替換方法的效果;// --我是不是可以想象一下,如果要做方法熱修復,是不是也可以呢?// --但是這個是限于我們有該對象的前提,如果是其他情況,可能就需要你去找到某個對象 它的某個方法,進而實現替換?MyView myView = new MyView();// 這是內部靜態類類的表示方法Field field = MyView.class.getDeclaredField("myTestView");field.setAccessible(true);// 用新的對象替換掉myView對象內部的對象變量Other other = new Other();field.set(myView, other);// 重新運行該方法,達到替換方法的效果!myView.myTestView.test();} catch (NoSuchFieldException e) {System.out.println(e.getMessage());} catch (IllegalAccessException e) {System.out.println(e.getMessage());}}這樣我們就替換掉了這個對象變量,插入了我們自己的操作,比如日志統計。有時候我們再不想改變原來代碼的基礎上可以這么設計,當然,還可以通過靜態代理,或者動態代理的方式實現。。
這里我們就想借此了解下Hook的思想。。。可能的大概的這樣一個概念....當我們真的去深入這塊的時候,我們會發現有更復雜的操作和邏輯。 比如有些情況下,你不能知道系統的某個靜態內部類,你沒辦法繼承重寫。那么你只能一步步的Hook到最終需要替換的目標對象(然后通過Proxy.newProxyInstance創建動態代理對象,動態代理類需要實現某個接口)
4. 上面步驟我們是重新對象的方式。然后開頭的全部代碼的其他部分,我們是采用動態代理的方式,然后反射來實現的。
重點就是Proxy的使用
ProcInterface other2 = (ProcInterface) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{ProcInterface.class}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("這是代理的對象呀 method=" + method);return null;}});至此我們就在Java main里面實踐了一把。。
And, 我們還可以利用這個思想,在Android里面實現類似的操作,比如Button按鈕的點擊事件的Hook...
開始之前,有必要了解下setOnClickListener(new View.OnClickListener() {}的內部邏輯,不然你不知道應該Hook哪個目標對象. 這里我直接粘貼出來幾個重要的源碼邏輯。。。
-- 可以看到我們最終要實現mOnClickListener接口變量的替換,而這個是在ListenerInfo里面,所以最終是替換對象的 ListenerInfo變量里面的mOnClickListener變量。 記住我們的操作都是針對Button button控件的,不能單獨New一個實例出來,你那樣不是Hook的該button的變量。
// Hook一下這個點擊事件,增加點預處理// 1.跟蹤下setOnClickListener方法都做了啥,關鍵的監聽設置在哪里:public void setOnClickListener(@Nullable OnClickListener l) {if (!isClickable()) {setClickable(true);}getListenerInfo().mOnClickListener = l;}static class ListenerInfo {/*** Listener used to dispatch click events.* This field should be made private, so it is hidden from the SDK.* {@hide}*/public OnClickListener mOnClickListener;}// 1.1 所以我們最終要實現替換getListenerInfo()->ListenerInfo的mOnClickListener為我們自己代理的監聽方法a. ListenerInfo是靜態內部類,我們拿不到,只能通過getListenerInfo獲取。所以我們通過反射拿:
Method method = View.class.getDeclaredMethod("getListenerInfo");method.setAccessible(true);Log.e("test", "method=" + method.getName());// 獲取Button的ListenerInfo對象mListenerInfoObject mListenerInfo = method.invoke(button);記住是button的
b. 拿到Object mListenerInfo后,還需要獲取ListenerInfo的mOnClickListener變量,同樣也只能通過反射獲取:
// 內部類需要使用$分隔Class<?> classListenerInfo = Class.forName("android.view.View$ListenerInfo");// 獲取內部Field mOnClickListenerField field = classListenerInfo.getDeclaredField("mOnClickListener");// 然后獲取Button的ListenerInfo對象mListenerInfo的mOnClickListener變量// --這就是真正的拿到了Button的監聽回調View.OnClickListener的實例對象final View.OnClickListener onClickListener = (View.OnClickListener) field.get(mListenerInfo);記住是上一步獲取的對象Object mListenerInfo的mOnClickListener
c. 然后上一步的Field field = classListenerInfo.getDeclaredField("mOnClickListener");獲取的field,我們就可以用set方法替換調用對象mListenerInfo的mOnClickListener變量:
之前記得創建一個動態代理對象:
// 然后準備替換為我們自己的點擊事件// 1. 創建代理點擊對象,然后替換 (這里繼承接口實現一個類也可以)Object proxyOnClickListener = Proxy.newProxyInstance(this.getClassLoader(),new Class[]{View.OnClickListener.class},new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Toast.makeText(MainActivity.this,"你點擊我嘛,我很煩的!",Toast.LENGTH_SHORT).show();}});// 2. 然后替換掉Button的點擊事件field.set(mListenerInfo, proxyOnClickListener);// End.當點擊的時候就會執行我們代理對象的invoke方法。然后你可以在invoke里面增加自己額外的操作。// --甚至你啥都不做,就這么讓點擊事件失效了,哈哈!當我們點擊的時候就會走動態代理對象的invoke方法
d. 我們Hook了這個點擊事件,插入了我們自己的處理,但是我們不能干擾其他的邏輯,所以我們invoke里面還是需要執行 被我們替換的mOnClickListener點擊事件的方法回調,所以我們要增加如下處理:
當然,如果你想完全攔截自己做一些事情,那你就不要這個處理了。
d. 到此,我們基本上就搞定了這個東東。。但是我有個疑問,我們只能針對一個控件實例,如果多個控件都想要這樣的操作應該如何搞? - - 控件的點擊事件封裝到Base頁面,統一Hook?還是說有其他的方式,再學習看看吧...
完整代碼:
MainActivity.java
package com.skl.hooktest;import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast;import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy;/** *@Author: hl *@Date: created at 2019/10/16 19:19 *@Description: https://www.jianshu.com/p/74c12164ffca?tdsourcetag=s_pcqq_aiomsg* 文章講的蠻好的,不過新手理解還是需要先搞搞反射這些知識才行。其實代理還好。你就是要知道,怎么反射調方法,獲取字段,設置字段等操作.* 慢慢熟悉吧!爭取多屢屢邏輯這些! */ public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button button = findViewById(R.id.hookClick);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.e("test", "點擊事件繼續");}});// Hook一下這個點擊事件,增加點預處理// 1.跟蹤下setOnClickListener方法都做了啥,關鍵的監聽設置在哪里:// public void setOnClickListener(@Nullable OnClickListener l) {// if (!isClickable()) {// setClickable(true);// }// getListenerInfo().mOnClickListener = l;// }// static class ListenerInfo {// /**// * Listener used to dispatch click events.// * This field should be made private, so it is hidden from the SDK.// * {@hide}// */// public OnClickListener mOnClickListener;// }// 1.1 所以我們最終要實現替換getListenerInfo()->ListenerInfo的mOnClickListener為我們自己代理的監聽方法try {Method method = View.class.getDeclaredMethod("getListenerInfo");method.setAccessible(true);Log.e("test", "method=" + method.getName());// 獲取Button的ListenerInfo對象mListenerInfoObject mListenerInfo = method.invoke(button);// 內部類需要使用$分隔Class<?> classListenerInfo = Class.forName("android.view.View$ListenerInfo");// 獲取內部Field mOnClickListenerField field = classListenerInfo.getDeclaredField("mOnClickListener");// 然后獲取Button的ListenerInfo對象mListenerInfo的mOnClickListener變量// --這就是真正的拿到了Button的監聽回調View.OnClickListener的實例對象final View.OnClickListener onClickListener = (View.OnClickListener) field.get(mListenerInfo);// 然后準備替換為我們自己的點擊事件// 1. 創建代理點擊對象,然后替換 (這里繼承接口實現一個類也可以)Object proxyOnClickListener = Proxy.newProxyInstance(this.getClassLoader(),new Class[]{View.OnClickListener.class},new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Toast.makeText(MainActivity.this,"你點擊我嘛,我很煩的!",Toast.LENGTH_SHORT).show();// 為了保證其點擊邏輯,除了插入我們的操作,我們還是要處理正常的調用邏輯return method.invoke(onClickListener, args);}});// 2. 然后替換掉Button的點擊事件field.set(mListenerInfo, proxyOnClickListener);// End.當點擊的時候就會執行我們代理對象的invoke方法。然后你可以在invoke里面增加自己額外的操作。// --甚至你啥都不做,就這么讓點擊事件失效了,哈哈!} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (NoSuchFieldException e) {e.printStackTrace();}} }要不先到這里,肚子餓了。。。
多多熟悉反射,代理等知識啊哈。。。完事了多練習吧。。
趁機可以了解下熱修復原理,以及Hook實現不用注冊manifest啟動頁面的預備知識
Dex熱修復原理
Android的熱修復 - 建議不要直接照著寫。 基礎不懂的知識,多聯系再整。 爭取你去整的時候,心里還是對流程清楚了解才行。。同時學習,實踐盡量帶上自己的邏輯和想法去實踐,多擴展。。。
附錄:https://blog.csdn.net/qq_30207527/article/details/85169582
總結
以上是生活随笔為你收集整理的hook控制浏览器的方法_Java-Hook技术-入门实践+反射、动态代理、热修复再看看的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机图形学学习报告,计算机图形学学习报
- 下一篇: ++递归 字符串全排列_超全递归技巧整理