android hook(Toast BadTokenException案例)

什么是Hook?

 hook 技术又叫做钩子函数,在系统没有调用该函数之前,钩子程序先捕捉该消息,钩子函数先得到控制权,这时钩子函数即可以加工处理(改变)该函数的执行行为,还可以强制结束消息的传递。简单来说,就是把系统的程序拉出来,变成我们自己执行的代码片段。

Hook的实现?

实现hook我们必须要知道java的反射和动态代理。

​​​​​​Java反射机制详解_贺兰猪的博客-CSDN博Java动态代理_贺兰猪的博客-CSDN博客Java反射机制详解_贺兰猪的博客-CSDN博客

案例:Toast WindowManager$BadTokenException

tips:这一小段源码层面我们主要针对于Android7.x。

相信Android朋友们平时开发的时候应该都遇到过token失效。

按照正常的流程,是不会出现这种异常。但是由于在某些情况下, Android 进程某个 UI 线程的某个消息阻塞。导致 TNshow 方法 post 出来 0 (显示) 消息位于该消息之后,迟迟没有执行。这时候,NotificationManager 的超时检测结束,删除了 WMS 服务中的 token 记录。也就是如图所示,删除 token 发生在 Android 进程 show 方法之前。这就导致了我们上面的异常。

整个toast显示原理及分析大家可以完整的看下QQ音乐技术团队的分析,(上图来源也是那)[Android] Toast问题深度剖析(一) - 腾讯云开发者社区-腾讯云

用sleep的方式并没有复现出来这个token is valid,但阅读Toast源代码后可以用另一种方式来复现这个BadTokenException:

val mw = getSystemService(WINDOW_SERVICE) as WindowManager
        val tv = TextView(this)
        tv.layoutParams = WindowManager.LayoutParams(1, 1)
        tv.text = "模拟toast悬浮窗"
        val params = WindowManager.LayoutParams()
        params.type = WindowManager.LayoutParams.TYPE_TOAST
        mw.addView(tv, params)
        Toast.makeText(this, "xxxx", Toast.LENGTH_SHORT).show()

因为type==TYPE_TOAST的类型的toast不能重复添加,所以这样也会报一个BadTokenException,接下来我们就要通过这个demo,用hook的解决方案来解决这个异常。

阅读源码我们发现,在Android 7.0 Toast.java  handleShow方法:

和在Android 8.0 Toast.java上:

对比发现在Android 8.0中,在WindowManager进行addView的时候8.0进行了一层try catch保护,而在7.0上并没有。那么我们就可以参考8.0的方法,直接catch住这个异常。

寻找hook点

尽量hook静态变量和单例对象

尽量hook public的对象和方法

查看调用链,mHandler中发了一个消息,在handlerMessage中处理这个toast显示的消息后调用handlerShow,mHandler是TN类中的变量,Toast 里面有一个变量mTN(TN类)。所以就很简单了,我们hook点就定位这个mTN,然后反射替换TN的内部成员变量mHandler,对handleMessage方法添加try-catch做到保护即可。

public class HookToastUtil {
    private static Field sField_N;
    private static Field sField_TN_Handler;
    private static Toast mToast;


    private HookToastUtil() {

    }

    public static void show(Context context, CharSequence message, int duration) {
        if (mToast == null) {
            mToast = Toast.makeText(context.getApplicationContext(), message, duration);
            hook(mToast);
        } else {
            mToast.setDuration(duration);
            mToast.setText(message);

            mToast.show();
        }
    }

    public static void show(Context context, @StringRes int resId, int duration) {
        if (mToast == null) {
            mToast = Toast.makeText(context.getApplicationContext(), resId, duration);
            hook(mToast);
        } else {
            mToast.setDuration(duration);
            mToast.setText(context.getString(resId));
        }
        mToast.show();
    }

    private static void hook(Toast toast) {
        if (Build.VERSION.SDK_INT != Build.VERSION_CODES.N_MR1) {
            return;
        }

        try {
            Class<?> cls = Class.forName("android.widget.Toast");
            sField_N = cls.getDeclaredField("mTN");
            sField_N.setAccessible(true);
            sField_TN_Handler = sField_N.getType().getDeclaredField("mHandler");
            sField_TN_Handler.setAccessible(true);
            Object tn = sField_N.get(toast);
            Handler handler = (Handler) sField_TN_Handler.get(tn);

            sField_TN_Handler.set(tn, new ReplaceHandler(handler));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static class ReplaceHandler extends Handler {
        private Handler tnHandler;

        public ReplaceHandler(Handler handler) {
            this.tnHandler = handler;
        }

        @Override
        public void handleMessage(Message msg) {
            try{
                tnHandler.handleMessage(msg);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

 调用HookToastUtil.show(this,"111",1000)  替换之前的Toast.makeText就不会报错了

猜你喜欢

转载自blog.csdn.net/u013773608/article/details/130048177