Android Hook式插件化教程(一)Hook从入门到精通

Android Hook式插件化教程(一)Hook从入门到精通

1.hook的定义

hook,顾名思义就是钩子。而在我们开发中通俗来讲就是劫持,就是某段SDK源码逻辑执行的过程中,通过代码手段劫持拦截执行该逻辑,加入自己的代码逻辑。

2hook的价值

hook是中级开发通往高级开发的必经之路。
如果把谷歌比喻成 安卓的造物主,那么安卓SDK源码里面就包含了万事万物的本源。
中级开发者,只在利用万事万物,浮于表层,而高级开发者能从本源上去改变万事万物,深入核心

3.hook的学习技巧

  • java反射 熟练掌握类Class,方法Method,成员Field的使用方法
    源码内部,很多类和方法都是@hide的,外部直接无法访问,所以只能通过反射,去创建源码中的类,方法,或者成员.
  • 阅读安卓源码的能力
    hook的切入点都在源码内部,不能阅读源码,不能理清源码逻辑,则不用谈hook.
    其实使用 androidStudio来阅读源码有个坑,,有时候会看到源码里面 “一片飘红”,看似是有什么东西没有引用进来,其实是因为有部分源码没有对开发者开放,解决起来很麻烦,最好是下载缺失的源码然后导入AS中浏览就行。

4.hook的通用思路

一句话总结,就是倒序写代码。顺序写代码,容易写错,而且这跟背代码没什么区别。
例如下面一段代码

package com.xxx.
public class A {
 private B b;
}

我们通过Hook替换掉A中b的变量。则我们可通过一下代码

Class Aclass = Class.forName("com.xxx.A");
Field bField = Aclass.getDeclaredField("b");
bField.setaccessible(truee)
bField.set(@1,@2)//其中@1为Aclass的对象,@2是你自定义的变量  

其中我们可以通过bField.set(@1,@2)去反向退出@1对象怎么去获取,然后再倒叙写出@1对象的获取方法。。

5 hook 使用案例分享

接下来 我们hook来实现一个效果,效果是这样的,给一个按钮实现点击事件,在点击事件里Toast出当前的按钮的文字,然后我们用hook去拦截这一事件,做我们自己的逻辑。由于布局代码比较简单,就不贴出布局了。下面是MainActivity里面的代码

 Button button = (Button) findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "" + ((Button) v).getText(), Toast.LENGTH_SHORT).show();
            }
        });

思路很简单,就是通过监听OnClickListener发的回调方法,然后拦截onClick放假加入我们的逻辑。简单来说就是用我们记得回调替换掉系统的OnClickListener,接下来就是查看android源码了我们追踪一下setOnClickListener里到底做了什么

    /**
     * Register a callback to be invoked when this view is clicked. If this view is not
     * clickable, it becomes clickable.
     *
     * @param l The callback that will run
     *
     * @see #setClickable(boolean)
     */
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

可以看出OnClickListener直接赋值给了getListenerInfo(),再看看getListenerInfo()做了什么

ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }

这个方法返回了mListenerInfo对象,我们可以看看这个类的声明

static class ListenerInfo {
        /**
         * Listener used to dispatch focus change events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        protected OnFocusChangeListener mOnFocusChangeListener;

        /**
         * Listeners for layout change events.
         */
        private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;

        protected OnScrollChangeListener mOnScrollChangeListener;

        /**
         * Listeners for attach events.
         */
        private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;

        /**
         * Listener used to dispatch click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        public OnClickListener mOnClickListener; //替换这个对象

        /**
         * Listener used to dispatch long click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        protected OnLongClickListener mOnLongClickListener;

        /**
         * Listener used to dispatch context click events. This field should be made private, so it
         * is hidden from the SDK.
         * {@hide}
         */
        protected OnContextClickListener mOnContextClickListener;

        /**
         * Listener used to build the context menu.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        protected OnCreateContextMenuListener mOnCreateContextMenuListener;

        private OnKeyListener mOnKeyListener;

        private OnTouchListener mOnTouchListener;

        private OnHoverListener mOnHoverListener;

        private OnGenericMotionListener mOnGenericMotionListener;

        private OnDragListener mOnDragListener;

        private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;

        OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;

        OnCapturedPointerListener mOnCapturedPointerListener;

        private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
    }

所以一目了然了,只要我们把ListenerInfo的mOnClickListener替换成我们自己的动态代理,那问题就解决了,接下来开始实现,用倒叙写代码的方式助于我们理清思路

        //获取ListenerInfo Class
        Class listenerInfoClass = Class.forName("android.view.View$ListenerInfo");
        //mOnClickListener Field
        Field onClicktenerField = listenerInfoClass.getField("mOnClickListener");
        //由于mOnClickListener是Public 不用授权访问
        final Object onClicktenerObj = onClicktenerField.get(listenerInfoObj);

所以问题在于listenerInfoObj这个对象我们怎样才能获取了,所以向上反推listenerInfoObj的由来。上述我们提到 listenerInfo是有以下方法得来的

ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }

所以只有通过执行这个方法我们才能得到listenerInfo对象,所以通过反射去获取

        //获取View对象
        Class viewClass = Class.forName("android.view.View");
        //获取getListenerInfo方法
        Method getListenerInfoMethod = viewClass.getDeclaredMethod("getListenerInfo");
        //由于getListenerInfo不是公开的,所以必须授权虚拟机去访问
        getListenerInfoMethod.setAccessible(true);
        //传入hook的按钮对象获取
        Object listenerInfoObj = getListenerInfoMethod.invoke(view);

所以得到listenerInfoObj这个对象,然后执行以下方法

        //将系统的onclictener替换成我写的动态代理
        onClicktenerField.set(listenerInfoObj, @2);

而@2是我们要替换的动态代理,而动态代理就是监听onclicklistener的方法回调。

// 1.监听 onClick,当用户点击按钮的时候-->onClick, 我们自己要先拦截这个事件
        // 动态代理
        // mOnClickListener 本质是==OnClickListener
        Object mOnClickListenerProxy = Proxy.newProxyInstance(MainActivity.class.getClassLoader(), // 1加载器

                new Class[]{View.OnClickListener.class}, // 2要监听的接口,监听什么接口,就返回什么接口

                new InvocationHandler() { // 3监听接口方法里面的回调

                    /**
                     *
                     * void onClick(View v);
                     *
                     * onClick ---> Method
                     * View v ---> Object[] args
                     *
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 加入了自己逻辑
                        Log.d("hook", "拦截到了 OnClickListener的方法了");
                        Button button = new Button(MainActivity.this);
                        button.setText("我是hook的新button");
                        // 让系统程序片段 --- 正常继续的执行下去
                        return method.invoke(mOnClickListenerObj, button);
                    }
                });

代码里有注释,InvocationHandler里就是我们要做的事情,这里是实例化新的按钮。
而Hook的关键必须不能阻断系统执行其他流程,所以这里比如返回return method.invoke(mOnClickListenerObj, button); 按道理应该会Toast出“我是hook的新button”,最后最后别忘里最核心的一步

        //将系统的onclictener替换成我写的动态代理
        onClicktenerField.set(listenerInfoObj, onClickListenerPoxy);

这样替换掉就能生效里,接下来看看效果。
在这里插入图片描述
以下是完整代码

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "" + ((Button) v).getText(), Toast.LENGTH_SHORT).show();
            }
        });




        // 在不修改以上代码的情况下,通过Hook把 ((Button) v).getText() 内容给修改
        try {
            hook(button); // button就是View对象
        } catch (Exception e) {
            e.printStackTrace();

            Toast.makeText(this, "Hook失败" + e.toString(), Toast.LENGTH_SHORT).show();
        }
    }

    private void hook(View view) throws Exception {

      
        Class mViewClass = Class.forName("android.view.View");
        Method getListenerInfoMethod = mViewClass.getDeclaredMethod("getListenerInfo");
        getListenerInfoMethod.setAccessible(true); // 授权
        // 执行方法
        Object  mListenerInfo = getListenerInfoMethod.invoke(view);

        // 替 换  public OnClickListener mOnClickListener; 替换我们自己的
        Class mListenerInfoClass = Class.forName("android.view.View$ListenerInfo");

        Field mOnClickListenerField = mListenerInfoClass.getField("mOnClickListener");

        final Object mOnClickListenerObj = mOnClickListenerField.get(mListenerInfo); // 需要@1对象


        // 1.监听 onClick,当用户点击按钮的时候-->onClick, 我们自己要先拦截这个事件
        // 动态代理
        // mOnClickListener 本质是==OnClickListener
        Object mOnClickListenerProxy = Proxy.newProxyInstance(MainActivity.class.getClassLoader(), // 1加载器

                new Class[]{View.OnClickListener.class}, // 2要监听的接口,监听什么接口,就返回什么接口

                new InvocationHandler() { // 3监听接口方法里面的回调

                    /**
                     *
                     * void onClick(View v);
                     *
                     * onClick ---> Method
                     * View v ---> Object[] args
                     *
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 加入了自己逻辑
                        Log.d("hook", "拦截到了 OnClickListener的方法了");
                        Button button = new Button(MainActivity.this);
                        button.setText("同学们大家好....");

                        // 让系统程序片段 --- 正常继续的执行下去
                        return method.invoke(mOnClickListenerObj, button);
                    }
                });



        // 狸猫换太子 把系统的 mOnClickListener  换成 我们自己写的 动态代理
        mOnClickListenerField.set(mListenerInfo, mOnClickListenerProxy); // 替换的 我们自己的动态代理
    }
}

接下来的我分享Android Hook式插件化教程(二)Hook系统源码 敬请关注

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值