zoukankan      html  css  js  c++  java
  • startActivity进行Hook

    --摘自《android插件化开发指南》

    1.Activity的startActivity和Context的startActivity都是在app进程中通知AMS要启动哪个Activity,都是调用Instrumentation的execStartActivity。

    方案一:

    一般所有Activity都有一个基类BaseActivity,在BaseActivity中重写startActivityForResult

    方案二:

    Activity的mInstumentation进行Hook

    public class EvilInstrumentation extends Instrumentation {
    
        private static final String TAG = "EvilInstrumentation";
    
        // ActivityThread中原始的对象, 保存起来
        Instrumentation mBase;
    
        public EvilInstrumentation(Instrumentation base) {
            mBase = base;
        }
    
        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {
    
            Log.d(TAG, "XXX到此一游!");
    
            // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
            // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
            Class[] p1 = {Context.class, IBinder.class,
                    IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class};
            Object[] v1 = {who, contextThread, token, target,
                    intent, requestCode, options};
            return (ActivityResult) RefInvoke.invokeInstanceMethod(
                    mBase, "execStartActivity", p1, v1);
        }
    }
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            Instrumentation mInstrumentation = (Instrumentation) RefInvoke.getFieldObject(Activity.class, this, "mInstrumentation");
            Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
    
            RefInvoke.setFieldObject(Activity.class, this, "mInstrumentation", evilInstrumentation);
    
            Button tv = new Button(this);
            tv.setText("测试界面");
            setContentView(tv);
    
            tv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                    startActivity(intent);
                }
            });
        }
    }

    如果把这段Hook代码转移到BaseActivity中,那所有继承的Activity的mInstrumentation就都被Hook了

        

    方案三:

    对AMN的getDefault方法进行Hook

    class MockClass1 implements InvocationHandler {
    
        private static final String TAG = "MockClass1";
    
        Object mBase;
    
        public MockClass1(Object base) {
            mBase = base;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            if ("startActivity".equals(method.getName())) {
    
                Log.e("bao", method.getName());
    
                return method.invoke(mBase, args);
            }
    
            return method.invoke(mBase, args);
        }
    }
    public class AMSHookHelper {
        public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
    
        public static void hookAMN() throws ClassNotFoundException,
                NoSuchMethodException, InvocationTargetException,
                IllegalAccessException, NoSuchFieldException {
    
            //获取AMN的gDefault单例gDefault,gDefault是final静态的
            Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault");
    
            // gDefault是一个 android.util.Singleton<T>对象; 我们取出这个单例里面的mInstance字段
            Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", gDefault, "mInstance");
    
            // 创建一个这个对象的代理对象MockClass1, 然后替换这个字段, 让我们的代理对象帮忙干活
            Class<?> classB2Interface = Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(
                    Thread.currentThread().getContextClassLoader(),
                    new Class<?>[] { classB2Interface },
                    new MockClass1(mInstance));
    
            //把gDefault的mInstance字段,修改为proxy
            RefInvoke.setFieldObject("android.util.Singleton", gDefault, "mInstance", proxy);
        }
    }
    public class MainActivity extends Activity {
    
        @Override
        protected void attachBaseContext(Context newBase) {
            super.attachBaseContext(newBase);
    
            try {
                AMSHookHelper.hookAMN();
            } catch (Throwable throwable) {
                throw new RuntimeException("hook failed", throwable);
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Button button = new Button(this);
            button.setText("启动TargetActivity");
    
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(MainActivity.this, TargetActivity.class);
                    startActivity(intent);
                }
            });
            setContentView(button);
        }
    }

    如果在自定义Application的attachBaseContext方法中执行AMSHookHelper的hookAMN方法,将一劳永逸

    方案四:

    对H类的mCallback字段进行Hook

    public class MockClass2 implements Handler.Callback {
    
        Handler mBase;
    
        public MockClass2(Handler base) {
            mBase = base;
        }
    
        @Override
        public boolean handleMessage(Message msg) {
    
            switch (msg.what) {
                // ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100
                // 本来使用反射的方式获取最好, 这里为了简便直接使用硬编码
                case 100:
                    handleLaunchActivity(msg);
                    break;
            }
    
            mBase.handleMessage(msg);
            return true;
        }
    
        private void handleLaunchActivity(Message msg) {
            // 这里简单起见,直接取出TargetActivity;
    
            Object obj = msg.obj;
    
            Log.d("baobao", obj.toString());
        }
    }
    public class HookHelper {
    
        public static void attachBaseContext() throws Exception {
    
            // 先获取到当前的ActivityThread对象
            Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");
    
            // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
            Handler mH = (Handler) RefInvoke.getFieldObject(currentActivityThread, "mH");
    
            //把Handler的mCallback字段,替换为new MockClass2(mH)
            RefInvoke.setFieldObject(Handler.class, mH, "mCallback", new MockClass2(mH));
        }
    }

    问:为什么不直接Hook了ActivityThread的mH字段?

    因为mH是H类型的,H类不对外暴露,也不是接口类型。所以既没办法伪造,Proxy.newProxyInstance也派不上用场

    方案五:

    再对Instrumentation字段进行Hook

    public class EvilInstrumentation extends Instrumentation {
    
        private static final String TAG = "EvilInstrumentation";
    
        // ActivityThread中原始的对象, 保存起来
        Instrumentation mBase;
    
        public EvilInstrumentation(Instrumentation base) {
            mBase = base;
        }
    
        public Activity newActivity(ClassLoader cl, String className,
                                    Intent intent)
                throws InstantiationException, IllegalAccessException,
                ClassNotFoundException {
    
            Log.d(TAG, "包建强到此一游!");
    
            return mBase.newActivity(cl, className, intent);
        }
    
        public void callActivityOnCreate(Activity activity, Bundle bundle) {
    
            Log.d(TAG, "到此一游!");
    
            // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
            // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
            Class[] p1 = {Activity.class, Bundle.class};
            Object[] v1 = {activity, bundle};
            RefInvoke.invokeInstanceMethod(
                    mBase, "callActivityOnCreate", p1, v1);
        }
    }
    public class HookHelper {
    
        public static void attachContext() throws Exception{
            // 先获取到当前的ActivityThread对象
            Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread");
    
            // 拿到原始的 mInstrumentation字段
            Instrumentation mInstrumentation = (Instrumentation) RefInvoke.getFieldObject(currentActivityThread, "mInstrumentation");
    
            // 创建代理对象
            Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
    
            // 偷梁换柱
            RefInvoke.setFieldObject(currentActivityThread, "mInstrumentation", evilInstrumentation);
        }
    }

    方案六:

    对ActivityThread的mInstrumentation字段进行Hook

    public class EvilInstrumentation extends Instrumentation {
    
        private static final String TAG = "EvilInstrumentation";
    
        // ActivityThread中原始的对象, 保存起来
        Instrumentation mBase;
    
        public EvilInstrumentation(Instrumentation base) {
            mBase = base;
        }
    
        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {
    
            Log.d(TAG, "XXX到此一游!");
    
            // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
            // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
            Class[] p1 = {Context.class, IBinder.class,
                    IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class};
            Object[] v1 = {who, contextThread, token, target,
                    intent, requestCode, options};
            return (ActivityResult) RefInvoke.invokeInstanceMethod(
                    mBase, "execStartActivity", p1, v1);
        }
    }
    public class HookHelper {
    
        public static void attachContext() throws Exception{
            // 先获取到当前的ActivityThread对象
            Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread");
    
            // 拿到原始的 mInstrumentation字段
            Instrumentation mInstrumentation = (Instrumentation) RefInvoke.getFieldObject(currentActivityThread, "mInstrumentation");
    
            // 创建代理对象
            Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
    
            // 偷梁换柱
            RefInvoke.setFieldObject(currentActivityThread, "mInstrumentation", evilInstrumentation);
        }
    }

    只有方案三,能同时满足Activity的startActivity()和Context的startActivity()方法

    ***启动没有在AndroidManifest.xml中申明的Activity

    Hook1:

    将Activity替换为一个在AndroidManifest中申明的StubActivity,绕过AMS的检查(这里选用对AMN进行Hook)

    class MockClass1 implements InvocationHandler {
    
        private static final String TAG = "MockClass1";
    
        Object mBase;
    
        public MockClass1(Object base) {
            mBase = base;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            Log.e("bao", method.getName());
    
            if ("startActivity".equals(method.getName())) {
                // 只拦截这个方法
                // 替换参数, 任你所为;甚至替换原始Activity启动别的Activity偷梁换柱
    
                // 找到参数里面的第一个Intent 对象
                Intent raw;
                int index = 0;
    
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Intent) {
                        index = i;
                        break;
                    }
                }
                raw = (Intent) args[index];
    
                Intent newIntent = new Intent();
    
                // 替身Activity的包名, 也就是我们自己的包名
                String stubPackage = raw.getComponent().getPackageName();
    
                // 这里我们把启动的Activity临时替换为 StubActivity
                ComponentName componentName = new ComponentName(stubPackage, StubActivity.class.getName());
                newIntent.setComponent(componentName);
    
                // 把我们原始要启动的TargetActivity先存起来
                newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, raw);
    
                // 替换掉Intent, 达到欺骗AMS的目的
                args[index] = newIntent;
    
                Log.d(TAG, "hook success");
                return method.invoke(mBase, args);
    
            }
    
            return method.invoke(mBase, args);
        }
    }
    public class AMSHookHelper {
        public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
    
        /**
         * Hook AMS
         * 主要完成的操作是  "把真正要启动的Activity临时替换为在AndroidManifest.xml中声明的替身Activity",进而骗过AMS
         */
        public static void hookAMN() throws ClassNotFoundException,
                NoSuchMethodException, InvocationTargetException,
                IllegalAccessException, NoSuchFieldException {
    
            //获取AMN的gDefault单例gDefault,gDefault是final静态的
            Object gDefault = RefInvoke.getStaticFieldObject("android.app.ActivityManagerNative", "gDefault");
    
            // gDefault是一个 android.util.Singleton<T>对象; 我们取出这个单例里面的mInstance字段
            Object mInstance = RefInvoke.getFieldObject("android.util.Singleton", gDefault, "mInstance");
    
            // 创建一个这个对象的代理对象MockClass1, 然后替换这个字段, 让我们的代理对象帮忙干活
            Class<?> classB2Interface = Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(
                    Thread.currentThread().getContextClassLoader(),
                    new Class<?>[] { classB2Interface },
                    new MockClass1(mInstance));
    
            //把gDefault的mInstance字段,修改为proxy
            Class class1 = gDefault.getClass();
            RefInvoke.setFieldObject("android.util.Singleton", gDefault, "mInstance", proxy);
        }
    
        /**
         * 由于之前我们用替身欺骗了AMS; 现在我们要换回我们真正需要启动的Activity
         * 不然就真的启动替身了, 狸猫换太子...
         * 到最终要启动Activity的时候,会交给ActivityThread 的一个内部类叫做 H 来完成
         * H 会完成这个消息转发; 最终调用它的callback
         */
        public static void hookActivityThread() throws Exception {
    
            // 先获取到当前的ActivityThread对象
            Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");
    
            // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
            Handler mH = (Handler) RefInvoke.getFieldObject(currentActivityThread, "mH");
    
    
            //把Handler的mCallback字段,替换为new MockClass2(mH)
            RefInvoke.setFieldObject(Handler.class, mH, "mCallback", new MockClass2(mH));
        }
    }

    Hook2:

    在即将启动时,把StubActivity替换为原先的Activity(这里的方案是对H类的mCallback字段进行Hook或者对ActivityThread的mInstrumentation进行Hook)

    class MockClass2 implements Handler.Callback {
    
        Handler mBase;
    
        public MockClass2(Handler base) {
            mBase = base;
        }
    
        @Override
        public boolean handleMessage(Message msg) {
    
            switch (msg.what) {
                // ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100
                // 本来使用反射的方式获取最好, 这里为了简便直接使用硬编码
                case 100:
                    handleLaunchActivity(msg);
                    break;
    
            }
    
            mBase.handleMessage(msg);
            return true;
        }
    
        private void handleLaunchActivity(Message msg) {
            // 这里简单起见,直接取出TargetActivity;
            Object obj = msg.obj;
    
            // 把替身恢复成真身
            Intent intent = (Intent) RefInvoke.getFieldObject(obj, "intent");
    
            Intent targetIntent = intent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
            intent.setComponent(targetIntent.getComponent());
        }
    
    
    }

    然后第一种:对H类的mCallback字段进行Hook

    public class HookHelper {
    public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
    
        public static void attachBaseContext() throws Exception {
    
            // 先获取到当前的ActivityThread对象
            Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread");
    
            // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
            Handler mH = (Handler) RefInvoke.getFieldObject(currentActivityThread, "mH");
    
            //把Handler的mCallback字段,替换为new MockClass2(mH)
            RefInvoke.setFieldObject(Handler.class, mH, "mCallback", new MockClass2(mH));
        }
    }

    第二种:对ActivityThread的mInstrumentation进行Hook

    public class HookHelper {
    public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
    
        public static void attachContext() throws Exception{
            // 先获取到当前的ActivityThread对象
            Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread");
    
            // 拿到原始的 mInstrumentation字段
            Instrumentation mInstrumentation = (Instrumentation) RefInvoke.getFieldObject(currentActivityThread, "mInstrumentation");
    
            // 创建代理对象
            Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
    
            // 偷梁换柱
            RefInvoke.setFieldObject(currentActivityThread, "mInstrumentation", evilInstrumentation);
        }
    }
    public class EvilInstrumentation extends Instrumentation {
    
        private static final String TAG = "EvilInstrumentation";
    
        // 替身Activity的包名, 也就是我们自己的包名
        String packageName = "jianqiang.com.hook1";
    
        // ActivityThread中原始的对象, 保存起来
        Instrumentation mBase;
    
        public EvilInstrumentation(Instrumentation base) {
            mBase = base;
        }
    
        public Activity newActivity(ClassLoader cl, String className,
                                    Intent intent)
                throws InstantiationException, IllegalAccessException,
                ClassNotFoundException {
    
            // 把替身恢复成真身
            Intent rawIntent = intent.getParcelableExtra(HookHelper.EXTRA_TARGET_INTENT);
            if(rawIntent == null) {
                return mBase.newActivity(cl, className, intent);
            }
    
            String newClassName = rawIntent.getComponent().getClassName();
            return mBase.newActivity(cl, newClassName, rawIntent);
        }
    }

    以上可以欺骗AMS

    欢迎关注我的微信公众号:安卓圈

  • 相关阅读:
    Linux在线或者离线安装gitlab
    Linux如何安装rpm文件
    使用docker run启动并进入一个容器
    docker导入导出镜像
    Mycat学习笔记一
    Mysql源码安装过程中可能碰到的问题
    当idea的maven项目没有.iml文件导致打开失败时
    从Vue.js窥探前端行业
    CSS 的overflow:hidden 属性详细解释
    win7系统Myeclipse下切换SVN用户
  • 原文地址:https://www.cnblogs.com/anni-qianqian/p/10098370.html
Copyright © 2011-2022 走看看