Hook 技术(二)

一、前言

  上一篇文章简单介绍了一下 hook 技术,并且写了个例子,不管是你想去 SecondActivity,还是想去其他的 Activity,我们统统使用 hook 给你跳转到 ProxyActivity,虽然这样不是很好,但是至少证明了我们已经可以修改系统调用的方法的逻辑了。

上一篇文章:https://www.jianshu.com/p/3382cc765b39

二、还原 Activity

  接下来,我们就把你想要真正跳转到的 Activity 还原到界面上,想要去东京就去东京,想去巴黎就去巴黎,想去土耳其就去土耳其。
  我们需要思考一个问题,正常情况下,为什么没有在清单文件注册的 Activity 不能打开,因为 Android 系统他会通过 AMS 来进行检测,如果我们躲过了这个检测,就可以不用注册了,那么如何躲避?
  首先,App 是以事件驱动来进行运转的,本质是一个无线循环的 main 函数,那么 App 的入口是在 ActivityThread 类里面的 main 方法。
  Android 中的所有消息都存储在消息泵里面,比如开启 Activity,消息的取出是通过 Handler,消息的处理也是通过 Handler 来处理的(handleMessage)。当外界开启 Activity 的时候,handler 会发送一个值为 100 的消息,如果 handler 发送了该消息,就表明启动该 Activity 已经经过了 AMS 的检查,可以开始启动了,最终会调用 Handler.CallBack 的 handleMessage 方法对消息值为 100 的消息来进行处理。
  那么,我们要做的就是在 handler 发送了 100 的消息后,hook 他的 handleMessage 方法,然后将跳转的页面还原,将 ProxyActivity 还原成原来的 Activity 就可以了。所以我们需要 hook 的是 handler 对象以及 handleMessage 方法


image.png
image.png

  找到了 Handler 对象 mh,并且我们看到一个常量 LAUNCH_ACRTIVITY = 100,就是我们前面说的 Activity 启动的时候 Handler 会发送一个值为 100 的消息。Handler 是找到了,但是问题是它不是静态的,这个 H 属于 ActivityThread,如果 ActivityThread 是静态的,那么就可以拿到系统的 H 了。

image.png
image.png
image.png

  是静态的,那么这个 sCurrentActivityThread 就是我们的伪 hook 点。接下来就看 handleMessage 方法如何拉到系统外执行了。


image.png
image.png
image.png

  解释一下上面的操作,首先 Handler 里面有一个 Callback 接口,该接口只有一个方法,就是 handleMessage 方法,而这个 Callback 实例化是在 Handler 的构造方法里面实例化的,所以我们肯定不能这样拿到这个 Callback,因为如果你要通过构造方法来那,肯定就要 new Handler,那 new 出来的对象肯定和系统不是同一个对象,没有任何意义,而且也没有 setCallback 方法,所以还是只能通过反射来获取。

public class HookUtils {

    private Context context;

    //...

    public void hookActivityHm(){
        try {
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadField.setAccessible(true);
            //拿到系统的 ActivityThread 对象
            Object activityTreadObj = sCurrentActivityThreadField.get(null);

            //那 Handler
            Field mhField = activityThreadClass.getDeclaredField("mH");
            mhField.setAccessible(true);
            //拿到系统的 Handler,真正的 hook 点已经拿到
            Handler mhHandler = (Handler) mhField.get(activityTreadObj);

            //hook handleMessage 方法
            //先拿到 callback
            Field mCallbackField = Handler.class.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);
            /*
                接下来就是将 handleMessage 方法拉到系统外面来执行了,
                但是注意,由于 callback 就是一个接口,那么我们就用系统的
                这个对象但是重写他里面的方法,就可以完成将系统代码拉到外面来执行的目的了,
                这就是特定的情景才能使用,不一定非要使用动态代理。
             */
            //这里将 Callback 替换成我们自己定义的 Callback
            mCallbackField.set(mhHandler,new MyCallback(mhHandler));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    class MyCallback implements Handler.Callback {

        private Handler systemHandler;

        public MyCallback(Handler systemHandler){
            this.systemHandler = systemHandler;
        }

        @Override
        public boolean handleMessage(Message msg) {
            if(msg.what == 100){
                //如果是 100 启动 Activity 的消息,我们需要特殊处理
                handleLaunchActivity(msg);
            }
            //这句话不能忘,其他的全部交给系统处理
            systemHandler.handleMessage(msg);
            return true;
        }

        /**
         * 如果执行到该方法,说明启动 Activity 已经经过 AMS 的检查了,
         * 我们在这里只需要将 Activity 进行还原即可。
         */
        private void handleLaunchActivity(Message msg) {
            /*
                这里有个小知识点,就是如果是启动 Activity 的话,肯定会有如下结构:
                msg 里面有一个 obj,而该 obj 中也会有一个 intent 属性
             */
            Object obj = msg.obj;
            try {
                Field intentField = obj.getClass().getDeclaredField("intent");
                intentField.setAccessible(true);

                //这个获得的是被我们篡改后的 Intent
                Intent tamperIntent = (Intent) intentField.get(obj);
                //从该 Intent 中获取真正想要跳转到的目标 Activity
                Intent oldIntent = tamperIntent.getParcelableExtra("oldIntent");
                if(oldIntent != null){
                    //实现集中式登录
                    SharedPreferences share = context.getSharedPreferences("radish",Context.MODE_PRIVATE);
                    boolean login = share.getBoolean("login", false);
                    if(login){
                        //登录了
                        tamperIntent.setComponent(oldIntent.getComponent());
                    }else{
                        //没登录
                        ComponentName componentName = new ComponentName(context,LoginActivity.class);
                        tamperIntent.putExtra("extraIntent",oldIntent.getComponent().getClassName());
                        tamperIntent.setComponent(componentName);
                    }

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

  如果我们想为哪个界面添加白名单,那么修改下面这行代码即可。


image.png

  这样的效果,就是跳转 SecondActivity 是不需要登录的,而其他的界面是需要登录的。


最后展示.gif

注意:这里有一个坑,所有在 AndroidManifest.xml 中没有注册过的 Activity 必须 extends Activity 而不能 extends AppCompatActivity,否则会报错。具体原因,下次见。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容