hook控制浏览器的方法_Java-Hook技术-入门实践+反射、动态代理、热修复再看看
延續(xù)之前的MonkeyLei:Android-模塊化、組件化、插件化、熱修復(fù)-插件化-起個(gè)頭,我們復(fù)習(xí)下里面的關(guān)于反射和動(dòng)態(tài)代理點(diǎn)的知識(shí)。然后嘗試簡(jiǎn)單了解下Hook...
看之前文章,記得多復(fù)習(xí)下反射代理,比如使用這些....:
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);多多實(shí)踐。之后我們嘗試建一個(gè)Android工程,但是先用Java main方法來(lái)做相關(guān)Hook測(cè)試。后面再嘗試結(jié)合Hook Android的東西.SO..開(kāi)始吧...
Then,看下Hook基本介紹吧...
一、什么是 Hook 技術(shù)Hook 技術(shù)又叫做鉤子函數(shù),在系統(tǒng)沒(méi)有調(diào)用該函數(shù)之前,鉤子程序就先捕獲該消息,鉤子函數(shù)先得到控制權(quán),這時(shí)鉤子函數(shù)既可以加工處理(改變)該函數(shù)的執(zhí)行行為,還可以強(qiáng)制結(jié)束消息的傳遞。簡(jiǎn)單來(lái)說(shuō),就是把系統(tǒng)的程序拉出來(lái)變成我們自己執(zhí)行代碼片段。要實(shí)現(xiàn)鉤子函數(shù),有兩個(gè)步驟:1. 利用系統(tǒng)內(nèi)部提供的接口,通過(guò)實(shí)現(xiàn)該接口,然后注入進(jìn)系統(tǒng)(特定場(chǎng)景下使用)2.動(dòng)態(tài)代理(使用所有場(chǎng)景)二、Hook 技術(shù)實(shí)現(xiàn)的步驟Hook 技術(shù)實(shí)現(xiàn)的步驟也分為兩步1.找到 hook 點(diǎn)(Java 層),該 hook 點(diǎn)必須滿足以下的條件:需要 hook 的方法,所屬的對(duì)象必須是靜態(tài)的,因?yàn)槲覀兪峭ㄟ^(guò)反射來(lái)獲取對(duì)象的,我們獲取的是系統(tǒng)的對(duì)象,所以不能夠 new 一個(gè)新的對(duì)象,必須用系統(tǒng)創(chuàng)建的那個(gè)對(duì)象,所以只有靜態(tài)的才能保證和系統(tǒng)的對(duì)象一致。2.將 hook 方法放到系統(tǒng)之外執(zhí)行(放入我們自己的邏輯)我就以我覺(jué)得的比較簡(jiǎn)單的方式來(lái)理解一下Hook,我要實(shí)現(xiàn)的功能是:
1. 繼承某個(gè)可以繼承的對(duì)象,然后重寫(xiě)某個(gè)方法,添加中間處理,比如驗(yàn)證等。完事了既可以用super調(diào)用父類(lèi)的方法.
2. 然后用這個(gè)新的對(duì)象變量替換掉原有的變量,實(shí)現(xiàn)對(duì)象變量的動(dòng)態(tài)替換
3. 重點(diǎn)也就是Field、Proxy的基本使用
直接看測(cè)試代碼 - 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{// 通過(guò)反射替換對(duì)象的該對(duì)象變量private MyTestView myTestView; // = new MyTestView();static class MyTestView{public void test(){System.out.println("啊啊啊");}}// 通過(guò)Proxy生成代理對(duì)象,然后替換該變量private ProcInterface other2;}/*** 重寫(xiě)舊有的某個(gè)方法,作為新的對(duì)象注入替換掉MyView的myTestView變量*/private static class Other extends MyView.MyTestView{@Overridepublic void test(){System.out.println("BBBBB");super.test();}}/*** Proxy生成代理對(duì)象必須是某個(gè)接口*/private interface ProcInterface{void test();}public static void main(String[] args){try {// 創(chuàng)建一個(gè)對(duì)象 - 我們即將替換這個(gè)對(duì)象的某個(gè)變量,達(dá)到替換方法的效果;// --我是不是可以想象一下,如果要做方法熱修復(fù),是不是也可以呢?// --但是這個(gè)是限于我們有該對(duì)象的前提,如果是其他情況,可能就需要你去找到某個(gè)對(duì)象 它的某個(gè)方法,進(jìn)而實(shí)現(xiàn)替換?MyView myView = new MyView();// myView.myTestView.test();// 這是內(nèi)部靜態(tài)類(lèi)類(lèi)的表示方法Field field = MyView.class.getDeclaredField("myTestView");field.setAccessible(true);// System.out.println(field.getName());// 用新的對(duì)象替換掉myView對(duì)象內(nèi)部的對(duì)象變量Other other = new Other();field.set(myView, other);// 或者用Proxy方法生成代理對(duì)象,這種方式下,代理的對(duì)象必須實(shí)現(xiàn)某個(gè)接口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("這是代理的對(duì)象呀 method=" + method);return null;}});// 替換對(duì)象變量,然后運(yùn)行field2.set(myView, other2);myView.other2.test();// 重新運(yùn)行該方法,達(dá)到替換方法的效果!myView.myTestView.test();} catch (NoSuchFieldException e) {System.out.println(e.getMessage());} catch (IllegalAccessException e) {System.out.println(e.getMessage());}//**************此時(shí)我們嘗試加載Apk文件,然后拿到補(bǔ)丁的方法,來(lái)修復(fù)上面對(duì)象的方法} }解釋步驟:
1. 定義一個(gè)MyView類(lèi),該類(lèi)包含一個(gè)MyTestView類(lèi),然后定義了一個(gè)MyTestView對(duì)象變量myTestView。
private static class MyView{// 通過(guò)反射替換對(duì)象的該對(duì)象變量private MyTestView myTestView; // = new MyTestView();static class MyTestView{public void test(){System.out.println("啊啊啊");}}}2. 定一個(gè)類(lèi)Other繼承MyTestView,然后重新它的test方法,同時(shí)加入自己的操作
/*** 重寫(xiě)舊有的某個(gè)方法,作為新的對(duì)象注入替換掉MyView的myTestView變量*/private static class Other extends MyView.MyTestView{@Overridepublic void test(){System.out.println("BBBBB");super.test();}}3. 測(cè)試流程,首先創(chuàng)建一個(gè)MyView的對(duì)象,然后我們將針對(duì)這個(gè)對(duì)象進(jìn)行反射操作,Hook掉它的myTestView變量
public static void main(String[] args){try {// 創(chuàng)建一個(gè)對(duì)象 - 我們即將替換這個(gè)對(duì)象的某個(gè)變量,達(dá)到替換方法的效果;// --我是不是可以想象一下,如果要做方法熱修復(fù),是不是也可以呢?// --但是這個(gè)是限于我們有該對(duì)象的前提,如果是其他情況,可能就需要你去找到某個(gè)對(duì)象 它的某個(gè)方法,進(jìn)而實(shí)現(xiàn)替換?MyView myView = new MyView();// 這是內(nèi)部靜態(tài)類(lèi)類(lèi)的表示方法Field field = MyView.class.getDeclaredField("myTestView");field.setAccessible(true);// 用新的對(duì)象替換掉myView對(duì)象內(nèi)部的對(duì)象變量Other other = new Other();field.set(myView, other);// 重新運(yùn)行該方法,達(dá)到替換方法的效果!myView.myTestView.test();} catch (NoSuchFieldException e) {System.out.println(e.getMessage());} catch (IllegalAccessException e) {System.out.println(e.getMessage());}}這樣我們就替換掉了這個(gè)對(duì)象變量,插入了我們自己的操作,比如日志統(tǒng)計(jì)。有時(shí)候我們?cè)俨幌敫淖冊(cè)瓉?lái)代碼的基礎(chǔ)上可以這么設(shè)計(jì),當(dāng)然,還可以通過(guò)靜態(tài)代理,或者動(dòng)態(tài)代理的方式實(shí)現(xiàn)。。
這里我們就想借此了解下Hook的思想。。。可能的大概的這樣一個(gè)概念....當(dāng)我們真的去深入這塊的時(shí)候,我們會(huì)發(fā)現(xiàn)有更復(fù)雜的操作和邏輯。 比如有些情況下,你不能知道系統(tǒng)的某個(gè)靜態(tài)內(nèi)部類(lèi),你沒(méi)辦法繼承重寫(xiě)。那么你只能一步步的Hook到最終需要替換的目標(biāo)對(duì)象(然后通過(guò)Proxy.newProxyInstance創(chuàng)建動(dòng)態(tài)代理對(duì)象,動(dòng)態(tài)代理類(lèi)需要實(shí)現(xiàn)某個(gè)接口)
4. 上面步驟我們是重新對(duì)象的方式。然后開(kāi)頭的全部代碼的其他部分,我們是采用動(dòng)態(tài)代理的方式,然后反射來(lái)實(shí)現(xiàn)的。
重點(diǎn)就是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("這是代理的對(duì)象呀 method=" + method);return null;}});至此我們就在Java main里面實(shí)踐了一把。。
And, 我們還可以利用這個(gè)思想,在Android里面實(shí)現(xiàn)類(lèi)似的操作,比如Button按鈕的點(diǎn)擊事件的Hook...
開(kāi)始之前,有必要了解下setOnClickListener(new View.OnClickListener() {}的內(nèi)部邏輯,不然你不知道應(yīng)該Hook哪個(gè)目標(biāo)對(duì)象. 這里我直接粘貼出來(lái)幾個(gè)重要的源碼邏輯。。。
-- 可以看到我們最終要實(shí)現(xiàn)mOnClickListener接口變量的替換,而這個(gè)是在ListenerInfo里面,所以最終是替換對(duì)象的 ListenerInfo變量里面的mOnClickListener變量。 記住我們的操作都是針對(duì)Button button控件的,不能單獨(dú)New一個(gè)實(shí)例出來(lái),你那樣不是Hook的該button的變量。
// Hook一下這個(gè)點(diǎn)擊事件,增加點(diǎn)預(yù)處理// 1.跟蹤下setOnClickListener方法都做了啥,關(guān)鍵的監(jiān)聽(tīng)設(shè)置在哪里: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 所以我們最終要實(shí)現(xiàn)替換getListenerInfo()->ListenerInfo的mOnClickListener為我們自己代理的監(jiān)聽(tīng)方法a. ListenerInfo是靜態(tài)內(nèi)部類(lèi),我們拿不到,只能通過(guò)getListenerInfo獲取。所以我們通過(guò)反射拿:
Method method = View.class.getDeclaredMethod("getListenerInfo");method.setAccessible(true);Log.e("test", "method=" + method.getName());// 獲取Button的ListenerInfo對(duì)象mListenerInfoObject mListenerInfo = method.invoke(button);記住是button的
b. 拿到Object mListenerInfo后,還需要獲取ListenerInfo的mOnClickListener變量,同樣也只能通過(guò)反射獲取:
// 內(nèi)部類(lèi)需要使用$分隔Class<?> classListenerInfo = Class.forName("android.view.View$ListenerInfo");// 獲取內(nèi)部Field mOnClickListenerField field = classListenerInfo.getDeclaredField("mOnClickListener");// 然后獲取Button的ListenerInfo對(duì)象mListenerInfo的mOnClickListener變量// --這就是真正的拿到了Button的監(jiān)聽(tīng)回調(diào)View.OnClickListener的實(shí)例對(duì)象final View.OnClickListener onClickListener = (View.OnClickListener) field.get(mListenerInfo);記住是上一步獲取的對(duì)象Object mListenerInfo的mOnClickListener
c. 然后上一步的Field field = classListenerInfo.getDeclaredField("mOnClickListener");獲取的field,我們就可以用set方法替換調(diào)用對(duì)象mListenerInfo的mOnClickListener變量:
之前記得創(chuàng)建一個(gè)動(dòng)態(tài)代理對(duì)象:
// 然后準(zhǔn)備替換為我們自己的點(diǎn)擊事件// 1. 創(chuàng)建代理點(diǎn)擊對(duì)象,然后替換 (這里繼承接口實(shí)現(xiàn)一個(gè)類(lèi)也可以)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,"你點(diǎn)擊我嘛,我很煩的!",Toast.LENGTH_SHORT).show();}});// 2. 然后替換掉Button的點(diǎn)擊事件field.set(mListenerInfo, proxyOnClickListener);// End.當(dāng)點(diǎn)擊的時(shí)候就會(huì)執(zhí)行我們代理對(duì)象的invoke方法。然后你可以在invoke里面增加自己額外的操作。// --甚至你啥都不做,就這么讓點(diǎn)擊事件失效了,哈哈!當(dāng)我們點(diǎn)擊的時(shí)候就會(huì)走動(dòng)態(tài)代理對(duì)象的invoke方法
d. 我們Hook了這個(gè)點(diǎn)擊事件,插入了我們自己的處理,但是我們不能干擾其他的邏輯,所以我們invoke里面還是需要執(zhí)行 被我們替換的mOnClickListener點(diǎn)擊事件的方法回調(diào),所以我們要增加如下處理:
當(dāng)然,如果你想完全攔截自己做一些事情,那你就不要這個(gè)處理了。
d. 到此,我們基本上就搞定了這個(gè)東東。。但是我有個(gè)疑問(wèn),我們只能針對(duì)一個(gè)控件實(shí)例,如果多個(gè)控件都想要這樣的操作應(yīng)該如何搞? - - 控件的點(diǎn)擊事件封裝到Base頁(yè)面,統(tǒng)一Hook?還是說(shuō)有其他的方式,再學(xué)習(xí)看看吧...
完整代碼:
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* 文章講的蠻好的,不過(guò)新手理解還是需要先搞搞反射這些知識(shí)才行。其實(shí)代理還好。你就是要知道,怎么反射調(diào)方法,獲取字段,設(shè)置字段等操作.* 慢慢熟悉吧!爭(zhēng)取多屢屢邏輯這些! */ 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", "點(diǎn)擊事件繼續(xù)");}});// Hook一下這個(gè)點(diǎn)擊事件,增加點(diǎn)預(yù)處理// 1.跟蹤下setOnClickListener方法都做了啥,關(guān)鍵的監(jiān)聽(tīng)設(shè)置在哪里:// 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 所以我們最終要實(shí)現(xiàn)替換getListenerInfo()->ListenerInfo的mOnClickListener為我們自己代理的監(jiān)聽(tīng)方法try {Method method = View.class.getDeclaredMethod("getListenerInfo");method.setAccessible(true);Log.e("test", "method=" + method.getName());// 獲取Button的ListenerInfo對(duì)象mListenerInfoObject mListenerInfo = method.invoke(button);// 內(nèi)部類(lèi)需要使用$分隔Class<?> classListenerInfo = Class.forName("android.view.View$ListenerInfo");// 獲取內(nèi)部Field mOnClickListenerField field = classListenerInfo.getDeclaredField("mOnClickListener");// 然后獲取Button的ListenerInfo對(duì)象mListenerInfo的mOnClickListener變量// --這就是真正的拿到了Button的監(jiān)聽(tīng)回調(diào)View.OnClickListener的實(shí)例對(duì)象final View.OnClickListener onClickListener = (View.OnClickListener) field.get(mListenerInfo);// 然后準(zhǔn)備替換為我們自己的點(diǎn)擊事件// 1. 創(chuàng)建代理點(diǎn)擊對(duì)象,然后替換 (這里繼承接口實(shí)現(xiàn)一個(gè)類(lèi)也可以)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,"你點(diǎn)擊我嘛,我很煩的!",Toast.LENGTH_SHORT).show();// 為了保證其點(diǎn)擊邏輯,除了插入我們的操作,我們還是要處理正常的調(diào)用邏輯return method.invoke(onClickListener, args);}});// 2. 然后替換掉Button的點(diǎn)擊事件field.set(mListenerInfo, proxyOnClickListener);// End.當(dāng)點(diǎn)擊的時(shí)候就會(huì)執(zhí)行我們代理對(duì)象的invoke方法。然后你可以在invoke里面增加自己額外的操作。// --甚至你啥都不做,就這么讓點(diǎn)擊事件失效了,哈哈!} 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();}} }要不先到這里,肚子餓了。。。
多多熟悉反射,代理等知識(shí)啊哈。。。完事了多練習(xí)吧。。
趁機(jī)可以了解下熱修復(fù)原理,以及Hook實(shí)現(xiàn)不用注冊(cè)manifest啟動(dòng)頁(yè)面的預(yù)備知識(shí)
Dex熱修復(fù)原理
Android的熱修復(fù) - 建議不要直接照著寫(xiě)。 基礎(chǔ)不懂的知識(shí),多聯(lián)系再整。 爭(zhēng)取你去整的時(shí)候,心里還是對(duì)流程清楚了解才行。。同時(shí)學(xué)習(xí),實(shí)踐盡量帶上自己的邏輯和想法去實(shí)踐,多擴(kuò)展。。。
附錄:https://blog.csdn.net/qq_30207527/article/details/85169582
總結(jié)
以上是生活随笔為你收集整理的hook控制浏览器的方法_Java-Hook技术-入门实践+反射、动态代理、热修复再看看的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 计算机图形学学习报告,计算机图形学学习报
- 下一篇: ++递归 字符串全排列_超全递归技巧整理