Android插件化(三)(Hook startActivity并且绕过AMS检查偷梁换柱)

前言

一般我们的app都是有登陆这个功能的,但是有些app并不是一进入就需要登陆的,而是用到一些和登陆相关的功能,比如要用到用户的信息这些。那么当我们要跳到购物车界面,但是我们的购物车是要用到用户信息的,那么我们就的先去登陆,登陆完成之后接着跳到购物车界面。

流程

一般情况下我们是点击之前判断有没有登陆,没有的话 去登陆,然后登陆完成之后,在登陆界面根据来源去跳转下一个页面,一般都是这样写的。但是假如业务复杂了有商品详情页,点赞,评论这些呢,有好多,那么是不是我们都要在登陆界面写很多这样无用的代码呢?肯定不能这样写了,这个时候就要想有什么办法能在跳转的过程中来判断有没有登陆呢,如果没有登陆,去登陆,登陆完成后跳转目标页面,有登陆,就直接跳转。那么重点来了,继续。

Hook

Hook技术也称为钩子函数,钩子函数世纪是一个处理消息的程序段,通过系统调用,把它挂入系统,在系统没有调用该函数前,钩子函数就先捕获消息,这样钩子函数的到控制权,这时钩子函数即可以加工处理该函数的执行行为。

Hook技术实现途径

  1. 找到hook点(就是寻找要改变的方法的过程)。
  2. 找到hook点,将我们要执行前的代码和执行后的代码插入到hook点

先看看效果:
。。。gif图片没传上去,贴了一个外部链接
http://note.youdao.com/noteshare?id=3ae2c1faeb9943337247d8fa4357b24e

如何实现Hook

1、利用系统内部提供的接口,通过实现该接口,然后输入进系统

2、动态代理

3、Hook的条件:要hook的那个方法所属的对象一定是静态的,如果不是静态的,是new出来的,那么拿不到系统的对象,hook就没有意义。

分析绕过AMS检查

  • 那么我们要在登陆的过程中做事情,并且还不想侵入我们的业务代码,只能看源码里面在我们调用了startActivity之后系统干了什么事情,能不能找一个hook点

在调用startActivity的时候,内部是最终调用的startActivityForResult

Activity类
if (mParent == null) {
    options = transferSpringboardActivityOptions(options);
    //这里调用的mInstrumentation里面的execStartActivity的方法 然后得到的返回值
    Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);
          ....
        }
        

那么我们继续往下看,只看核心主干代码,其他细节的先不看

Instrumentation类
public ActivityResult execStartActivity(...){
...
//这里是又是通过ActivityManager.getService()得到一个对象 然后去startActivity的
 int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
...
}

从上面看出又调用到了ActivityManager这个类,当然每个版本的里面或多或少都有些不同 我这里是28(8.0),23的源码这里是ActivityManagerNative,所以hook的话 适配起来比较麻烦

ActivityManager

...
/**
*获取一个单例的对象
*/
public static IActivityManager getService(){
//这里调用的get 其实就是得到这个单例的对象 
    return IActivityManagerSingleton.get();
}
...
private static final Singleton<IActivityManager> IActivityManagerSingleton=new Singleton<IActivityManager>(){

 @Override
    protected void create(){
       final IBinder b= ServiceManager.getService(Context.ACTIVITY_SERVICE);
       final IActivityManager am=  IActivityManager.Sub.asInterface(b);
       return am;
    }
}
...

从上面看出 获得一个IActivityManager的对象,是从这个单例身上获取的, 也就是这个单例身上的mInstnce这个Field,然后是实现了onCreate方法去得到的一个IBinder对象,得到的

public abstract class Singleton<T> {
    private T mInstance;// 其实这里返回的就是am

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

上面的源码看完了,那么我们分析下到底该怎么做

我们最终肯定要得到系统通过Binder来返回的IActivityManager这个对象,而且不能new,你new来那和系统没有一毛钱关系,要想拿到这个IActivityManager对象,就要获取Singleton这个类对象,它正好是ActivityManager的一个静态属性。那么直接通过反射拿到ActivityManager的Class对象,然后拿它的静态属性,

  1. 先通过反射拿到ActivityManager的Class对象,然后拿它的静态属性IActivityManagerSingleton

    Class<?> mActivityManagerClass=Class.forName("android.app.ActivityManager");
    Field mActivitySigletonField=mActivityManagerClass.getDeclaredField("IActivityManagerSingleton");
    
  2. 因为这个属性是静态的,所以设置为可访问的

    mActivitySigletonField.setAccessible(true);
    // 然后我们获取singleton这个对象
    Object mSingletonObj=mActivitySigletonField.get(null);
    
  3. 然后我们开始拿IActivityManagerSingleton这个对象内部的mInstance属性,也就是我们最重要获取的

    Class<?> mSingletonClass=Class.forName("android.util.Singleton");
    Field mInstanceField=mzsingletonClass.getDeclaredField("mInstance");
    

mInstanceField.setAccessible(true);
// 然后获取mInstance这个对象
Object mIactivityManagerObj=mInstanceField.get(mSingletonObj);


4. 我们现在拿到了这个IActivityManager这个由IBinder返回的对象 ,我们要代理它, 绕过AMS检查。一般要想在目标对象之前插入我们的代码,有两种方法,第一种是设置接口,就是内部有提供接口,第二种就是动态代理,然后这里IActivityManager本身就是一个接口,那设置接口这条路行不通,只能是动态代理了(

[https://www.jianshu.com/p/bb8434645a76]: 

)



```java
// 创建一个类,实现InvocationHandler这个接口 并且要持有真实对象的引用
startActivityInvocation startActivityInvocation = new startActivityInvocation(mIactivityManagerObj);

Object mProxyActivityManager = Proxy.newProxyInstance(getClass().getClassLoader(), mIactivityManagerObj.getClass().getInterfaces(), startActivityInvocation);

// 实现InVocationHandler接口
class startActivityInvocation implements InvocationHandler {
         private final Object obj;

        // 代理对象内部持有真实对象的引用
        public startActivityInvocation(Object mIActivityManagerObj) {
            this.obj = mIActivityManagerObj;
        }
             @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Intent oldIntent = null;
            if ("startActivity".equals(method.getName())) {
                Log.e(TAG, "hook到了startActivity");
                // hook startActivity 我们拿到要启动的Intent
                int index = 0;// 记录一下是第几个参数
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Intent) {
                        index = i;
                        oldIntent = (Intent) args[i];
                    }
                }
                Intent newIntent = new Intent();
              // 清单文件只有ProxyActivity是注册的,其他activity是没有注册的。
                //为什么要用ProxyActivity 因为这一步是要绕过AMS的检查,清单文件注册了的
                newIntent.setComponent(new ComponentName(context, ProxyActivity.class));
                newIntent.putExtra("oldIntent", oldIntent);

                //将真实的Intent隐藏起来创建新的 然后给系统
                args[index] = newIntent; // 然后给原样放进去
            }
            return method.invoke(obj, args);
        }
}

上面是将我们真实跳转的activity先给隐藏起来,躲过AMS的检查,到这里已经完成了第一步

偷梁换柱

我们的Activity在一系列的调用之后,它的创建消息是在ActivityThread里面的handle里面进行处理的,那么我们就要拿到ActivityThread这个对象的handler对象,但是个是成员变量里面直接new的,所以拿它肯定不行,那么在往上找看ActivityThread

ActivityThread类
...
  private static volatile ActivityThread sCurrentActivityThread;

    final H mH = new H();
...

可以看到有一个sCurrentActivityThread 这个静态属性,而且是自己持有自己,那这就好办了

  1. 先通过这个静态变量 拿到ActivityThread的对象

    Class<?> mActivityThreadClass=Class.forName("android.app.ActivityThread");
    Field sCurrentActivityThreadField=mActivityThreadClass.getDeclaredField("sCurrentActivityThread");
    sCurrentActivityThreadField.setAccessible(true);
    Object mActivityThreadObj=sCurrentActivityThreadField.get(null);
    // 这一步就获取到了ActivityThread的对象
    
  2. 然后在获取对象里面的mH这个成员变量

    Field mHField=mActivityThreadClass.getDeclaredField("mH");
    mHField.setAccessible(true);
    // 然后我们通过get方法 拿ActivityThread对象身上的mh的对象
    Object mHandleObj=mHField.get(mActivityThreadObj);
    
  3. 现在我们拿到了ActivityThread里面的handler对象,我们就要对这个handler的handleMessage方法进行hook,这里的话Handler里面有提供接口,所以直接注入接口就可以了,不需要动态代理。

    handler 源码
    ...
      // 这个是从消息队列取出来之后 第一个处理的方法
    public void dispatchMessage(Message msg) {
           if (msg.callback != null) {
                handleCallback(msg);
            } else {
              // 如果callback 不为空,就去执行callback里面的handlemessage方法
               if (mCallback != null) {
                   if (mCallback.handleMessage(msg)) {
                        return;
                    }
               }
                handleMessage(msg);
             }
    }
    final Callback mCallback;
       /**
         * Callback interface you can use when instantiating a Handler to avoid
         * having to implement your own subclass of Handler.
         */
    public interface Callback {
       /**
        * @param msg A {@link android.os.Message Message} object
        * @return True if no further handling is desired
       */
        public boolean handleMessage(Message msg);
    }
    

    从上面handler的源码可以看出,但是这个里面没有提供setCallback这个方法,所以也只能通过反射,往里面注入一个callback

  4. 反射获取callback,注入一个callback

    Class<?> mHandlerClass=Class.forName("android.os.Handler");
    Field mHandleCallbackField=mHandleClass.getDeclaredField("mCallback");
    mHandleCallbackField.setAccessible(true);
    
    // 给callback注入
    HandlerCallback handlerCallback = new HandlerCallback((Handler) mHandlerObj);
    mHandleCallbackField.set(mHandleObj,);
    
    
    // 实现了Handle.Callback接口的类 用来注入用的
    class HandleCallback implements Handle.Callback{
      Objecrt mHandle;//持有真实的对象 最后要调用系统的handleMessage 不然activity都启动不了
      public HandlerCallback(Handler handler) {
          this.mHandle = handler;
      }
      
      @Override
      public boolean handleMessage(Message msg) {
         Log.e("handle", msg.what + "");
          // 继续让系统处理
          if (msg.what == 100) {
            // 系统发出的启动activity的消息的what就是100
               handleLauchActivity(msg);
           }
            handler.handleMessage(msg);
            return true;
      }
    }
    
    /**
    * 绕过AMS检查后,将我们真实的取出来 进行还原
    *
    * @param msg
    */
    private void handleLauchActivity(Message msg) {
         Object obj = msg.obj;
         try {
              Field mIntentField = obj.getClass().getDeclaredField("intent");
               mIntentField.setAccessible(true);
    
                // proxyActivity 取出来是这样的
                Intent realIntent = (Intent) mIntentField.get(obj);
    
                Intent oldIntent = realIntent.getParcelableExtra("oldIntent");
                if (oldIntent != null) {
                    // 然后在这里统一处理跳转activity
                    //判断有没有登陆成功
                    SharedPreferences sharedPreferences = context.getSharedPreferences("plugin", context.MODE_PRIVATE);
                    boolean isLogin = sharedPreferences.getBoolean("login", false);
                    if (isLogin) {
                        // 登陆了 按照原来的目标
                        realIntent.setComponent(oldIntent.getComponent());
                    } else {
                        // 没有登陆
                        ComponentName  newComponent=new ComponentName(context,LogActivity.class);
                        realIntent.setComponent(newComponent);
                     realIntent.putExtra("extraIntent",oldIntent.getComponent().getClassName());
                    }
                }
    
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    

    至此,大概流程就走完了,上面通过简单的分析了activity启动中一些重要的节点,然后进行hook,从而达到我们最终的目的,这样通过一个例子 熟悉了源码 也能在日常业务处理中用到。

    我在下面贴一下整个的hook的处理过程,就一个类

    public class HookUtil {
    
        /**
         * public abstract class Singleton<T> {
         * private T mInstance;
         * <p>
         * protected abstract T create();
         * <p>
         * public final T get() {
         * synchronized (this) {
         * if (mInstance == null) {
         * mInstance = create();
         * }
         * return mInstance;
         * }
         * }
         * }
         */
    
    
        private static final String TAG = "HooUtil";
        private Context context;
    
        /**
         * hook startActivity 让开启activity走自己的代理类
         */
        public void hookStartActivity(Context context) {
    
            this.context = context;
            // 基于28的源码
            try {
    
                Class<?> mActivityManagerClass = Class.forName("android.app.ActivityManager");
                //在拿到 IActivityManagerSingleton 这个静态属性
                Field mActivitySigletonField = mActivityManagerClass.getDeclaredField("IActivityManagerSingleton");
    
                //由于是私有的 要设置为可访问的
                mActivitySigletonField.setAccessible(true);
                // 由于是静态的 我直接获取 singleton这个对像
                Object mSingletonObj = mActivitySigletonField.get(null);
                //拿到了这个对象 但是我不需要这个对象,我是是需要它内部的mInstance这个对象
    
    
                Class<?> mSingletonClass = Class.forName("android.util.Singleton");
                Field mInstanceField = mSingletonClass.getDeclaredField("mInstance");
                mInstanceField.setAccessible(true);
                //然后获取这个mInstance
                Object mIactivityManagerObj = mInstanceField.get(mSingletonObj);
    
                // 现在获取到了我们要处理的这个对象 ,然后我们通过动态代理 来生成一个代理对象
                startActivityInvocation startActivityInvocation = new startActivityInvocation(mIactivityManagerObj);
    
                Object mProxyActivityManager = Proxy.newProxyInstance(getClass().getClassLoader(), mIactivityManagerObj.getClass().getInterfaces(), startActivityInvocation);
                mInstanceField.set(mSingletonObj, mProxyActivityManager);// 给将我们生成的动态代理的对象设置进去
    
    
                hookActivityThreadMH();
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "hook startAcgtivity 出现了异常" + e.getMessage());
            }
        }
    
    
        class startActivityInvocation implements InvocationHandler {
            private final Object obj;
    
            // 代理对象内部持有真实对象的引用
    
    
            public startActivityInvocation(Object mIActivityManagerObj) {
                this.obj = mIActivityManagerObj;
            }
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Intent oldIntent = null;
                if ("startActivity".equals(method.getName())) {
                    Log.e(TAG, "hook到了startActivity");
                    // hook startActivity 我们拿到要启动的Intent
                    int index = 0;
                    for (int i = 0; i < args.length; i++) {
                        if (args[i] instanceof Intent) {
                            index = i;
                            oldIntent = (Intent) args[i];
                        }
                    }
                    Intent newIntent = new Intent();
                    //为什么要用ProxyActivity 因为这一步是要绕过AMS的检查,清单文件注册了的
                    newIntent.setComponent(new ComponentName(context, ProxyActivity.class));
                    newIntent.putExtra("oldIntent", oldIntent);
    
                    //将真实的Intent隐藏起来创建新的 然后给系统
                    args[index] = newIntent;
                }
                return method.invoke(obj, args);
            }
        }
    
    
        /**
         * hook ActivityThread中的handler,处理我们的启动actiivty的消息,
         */
    
        private void hookActivityThreadMH() {
    
            //1 、我们要想办法代理处理那个HandleMessage()  所以就去找静态的,
            //  找handler,但是发现handler是new出来的,所以在找ActivityThread这个类,最后找到
            // 一个 sCurrentActivityThread 这个字段,是静态,而且是它持有了自己,那么直接获取class对象
            try {
                Class<?> mActivityThreadClass = Class.forName("android.app.ActivityThread");
    
                Field mSCurrntActivityThreadField = mActivityThreadClass.getDeclaredField("sCurrentActivityThread");
    
                //然后我们通过静态对象 可以获取到ActivityThread的对象,而且是系统创建的对象
                mSCurrntActivityThreadField.setAccessible(true);
                Object mActivityThreadObj = mSCurrntActivityThreadField.get(null);
                //在获取反射获取mh
    
                Field mHandlerField = mActivityThreadClass.getDeclaredField("mH");
                mHandlerField.setAccessible(true);
    
                //这一步我们就拿到了mH这个对象
                Object mHandlerObj = mHandlerField.get(mActivityThreadObj);
    
    
                // 下面我就要考虑是用动态代理还是用设置接口,来让handlerMessage先处理我们的,通过handler的dispatchMessage的方法 而且内部是提供接口,
                // 那就这里不用动态代理了,用提供的接口
    
                //接着还是用反射 ,给注入一个callback
    
                Class<?> mHandlerClass = Class.forName("android.os.Handler");
    
                Field mHandlerCallbackField = mHandlerClass.getDeclaredField("mCallback");
    
                mHandlerCallbackField.setAccessible(true);
    
    
                //给注入一个callback
                HandlerCallback handlerCallback = new HandlerCallback((Handler) mHandlerObj);
                mHandlerCallbackField.set(mHandlerObj, handlerCallback);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
    
        class HandlerCallback implements Handler.Callback {
    
    
            private final Handler handler;
    
            public HandlerCallback(Handler handler) {
                this.handler = handler;
            }
    
            @Override
            public boolean handleMessage(Message msg) {
                Log.e("handle", msg.what + "");
                // 继续让系统处理  9.0 的是159    9.0之下的是100
    
                if (msg.what == 100 || msg.what == 159) {
                    handleLauchActivity(msg);
                }
                handler.handleMessage(msg);
                return true;
            }
        }
    
        /**
         * 绕过AMS检查后,将我们真实的取出来 进行还原
         *
         * @param msg
         */
        private void handleLauchActivity(Message msg) {
            Object obj = msg.obj;
    
            try {
                Field mIntentField = obj.getClass().getDeclaredField("intent");
                mIntentField.setAccessible(true);
    
    
                // proxyActivity 取出来是这样的
                Intent realIntent = (Intent) mIntentField.get(obj);
    
                Intent oldIntent = realIntent.getParcelableExtra("oldIntent");
    
                if (oldIntent != null) {
                    // 然后在这里统一处理跳转activity
    
                    //判断有没有登陆成功
                    SharedPreferences sharedPreferences = context.getSharedPreferences("plugin", context.MODE_PRIVATE);
                    boolean isLogin = sharedPreferences.getBoolean("login", false);
                    if (isLogin) {
                        // 登陆了 按照原来的目标
                        realIntent.setComponent(oldIntent.getComponent());
                    } else {
                        // 没有登陆
                        ComponentName  newComponent=new ComponentName(context,LogActivity.class);
                        realIntent.setComponent(newComponent);
                        realIntent.putExtra("extraIntent",oldIntent.getComponent().getClassName());
    
                    }
                }
    
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        /**
         *
         * //  这个是handler内部的处理方法
         * //  我们发现它会 先看mCallback 不为空,去执行callback里面的
         * //  handleMessage 方法,返回为true 就直接返回了
         *
         *  public void dispatchMessage(Message msg) {
         *         if (msg.callback != null) {
         *             handleCallback(msg);
         *         } else {
         *             if (mCallback != null) {
         *                 if (mCallback.handleMessage(msg)) {
         *                     return;
         *                 }
         *             }
         *             handleMessage(msg);
         *         }
         *     }
         */
    
    }
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容