美文网首页
简单的代理Hook实现

简单的代理Hook实现

作者: jimjayce | 来源:发表于2018-08-16 15:31 被阅读0次

    代理Hook

    如果我们自己创建代理对象,然后把原始对象替换为我们的代理对象,那么就可以在这个代理对象为所欲为了;修改参数,替换返回值,我们称之为Hook。

    下面我们Hook掉startActivity这个方法,使得每次调用这个方法之前输出一条日志;(当然,这个输入日志有点点弱,只是为了展示原理;只要你想,你想可以替换参数,拦截这个startActivity过程,使得调用它导致启动某个别的Activity,指鹿为马!)

    首先我们得找到被Hook的对象,我称之为Hook点;什么样的对象比较好Hook呢?自然是容易找到的对象。什么样的对象容易找到?静态变量和单例;在一个进程之内,静态变量和单例变量是相对不容易发生变化的,因此非常容易定位,而普通的对象则要么无法标志,要么容易改变。我们根据这个原则找到所谓的Hook点。

    然后我们分析一下startActivity的调用链,找出合适的Hook点。我们知道对于Context.startActivity(Activity.startActivity的调用链与之不同),由于Context的实现实际上是ContextImpl;我们看ConetxtImpl类的startActivity方法:

    @Override
    public void startActivity(Intent intent, Bundle options) {
         ...
         mMainThread.getInstrumentation().execStartActivity(
             getOuterContext(), mMainThread.getApplicationThread(), null,(Activity)null, intent, -1, options);
    }
    

    这里,实际上使用了ActivityThread类的mInstrumentation成员的execStartActivity方法;注意到,ActivityThread 实际上是主线程,而主线程一个进程只有一个,因此这里是一个良好的Hook点。

    接下来就是想要Hook掉我们的主线程对象,也就是把这个主线程对象里面的mInstrumentation给替换成我们修改过的代理对象;要替换主线程对象里面的字段,首先我们得拿到主线程对象的引用,如何获取呢?ActivityThread类里面有一个静态方法currentActivityThread可以帮助我们拿到这个对象类;但是ActivityThread是一个隐藏类,我们需要用反射去获取,代码如下:

    // 先获取到当前的ActivityThread对象
    Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
    Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
    currentActivityThreadMethod.setAccessible(true);
    Object currentActivityThread = currentActivityThreadMethod.invoke(null);
    

    拿到这个currentActivityThread之后,我们需要修改它的mInstrumentation这个字段为我们的代理对象,我们先实现这个代理对象,由于JDK动态代理只支持接口,而这个Instrumentation是一个类,没办法,我们只有手动写静态代理类,覆盖掉原始的方法即可。(cglib可以做到基于类的动态代理,这里先不介绍)

    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) {
            // Hook之前, XXX到此一游!
            Log.e("sck", "执行了startActivity, 参数如下: " + "who = [" + who + "], " +
                    "contextThread = [" + contextThread + "], token = [" + token + "], " +
                    "target = [" + target + "], intent = [" + intent +
                    "], requestCode = [" + requestCode + "], options = [" + options + "]");
    
            // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
            // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
            try {
                Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                        "execStartActivity",
                        Context.class, IBinder.class, IBinder.class, Activity.class,
                        Intent.class, int.class, Bundle.class);
                execStartActivity.setAccessible(true);
                return (ActivityResult) execStartActivity.invoke(mBase, who,
                        contextThread, token, target, intent, requestCode, options);
            } catch (Exception e) {
                // 某该死的rom修改了  需要手动适配
                throw new RuntimeException("do not support!!! pls adapt it");
            }
        }
    }
    
    

    Ok,有了代理对象,我们要做的就是偷梁换柱!代码比较简单,采用反射直接修改:

    public static void attachContext() throws Exception {
            // 先获取到当前的ActivityThread对象
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
            currentActivityThreadMethod.setAccessible(true);
            //currentActivityThread是一个static函数所以可以直接invoke,不需要带实例参数
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);
    
            // 拿到原始的 mInstrumentation字段
            Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
            mInstrumentationField.setAccessible(true);
            Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
    
            // 创建代理对象
            Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
    
            // 偷梁换柱
            mInstrumentationField.set(currentActivityThread, evilInstrumentation);
        }
    

    Activity中的代码:

    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            // TODO: 16/1/28 支持Activity直接跳转请在这里Hook
            // 家庭作业,留给读者完成.
    
            Button tv = new Button(this);
            tv.setText("测试界面");
    
            setContentView(tv);
    
            tv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    intent.setData(Uri.parse("http://www.baidu.com"));
    
                    // 注意这里使用的ApplicationContext 启动的Activity
                    // 因为Activity对象的startActivity使用的并不是ContextImpl的mInstrumentation
                    // 而是自己的mInstrumentation, 如果你需要这样, 可以自己Hook
                    // 比较简单, 直接替换这个Activity的此字段即可.
                    getApplicationContext().startActivity(intent);
                }
            });
        }
    
        @Override
        protected void attachBaseContext(Context newBase) {
            super.attachBaseContext(newBase);
            Log.e("sck", "attachBaseContext");
            try {
                // 在这里进行Hook
                HookHelper.attachContext();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    整个Hook过程简要总结如下:

    1. 寻找Hook点,原则是静态变量或者单例对象,尽量Hook pulic的对象和方法,非public不保证每个版本都一样,需要适配。
    2. 选择合适的代理方式,如果是接口可以用动态代理;如果是类可以手动写代理也可以使用cglib。
    3. 偷梁换柱——用代理对象替换原始对象

    相关文章

      网友评论

          本文标题:简单的代理Hook实现

          本文链接:https://www.haomeiwen.com/subject/bdcobftx.html