美文网首页
Android Hook技术分析

Android Hook技术分析

作者: 雷涛赛文 | 来源:发表于2020-11-25 15:25 被阅读0次

    一.简介

          Hook技术是一种用于改变API执行结果的技术,Android系统中有一套自己的事件分发机制,所有的代码调用和回调都是按照一定顺序执行的,Hook技术存在的意义就在于,Hook可以帮助我们在Android中在SDK源代码逻辑执行过程中,通过代码手动拦截执行该逻辑,加入自己的代码逻辑。
          为了保证hook的稳定性,一般拦截的点都会选择比较容易找到并且不易发生变化的对象,比如静态变量和单例。
          提到Hook,就不得不说一下Java的反射机制。

    二.反射

          Java反射机制主要提供了以下功能:
                在运行时判断任意一个对象所属的类
                在运行时构造任意一个类的对象
                在运行时判断任意一个类所具有的成员变量和方法
                在运行时调用任意一个对象的方法
                生成动态代理
          在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。

    反射.png

    三.Hook使用案例分析

    1.实现启动未注册的activity

          比如我们想启动一个activity,如果未在AndroidManifest.xml里面注册的话,调用Context.startActivity()时会出现以下异常:

    1-25 10:44:54.811 E/AndroidRuntime(25309): Caused by: android.content.ActivityNotFoundException: Unable to 
    find explicit activity class {com.hly.learn/com.hly.learn.HookActivity}; have you declared this activity in 
    your AndroidManifest.xml?
    

          下面就一起来实现启动一个未在AndroidManifest.xml注册的activity。

          a.寻找hook点

          对于Context.startActivity,由于Context的实现类为ContextImpl,因此直接分析ContextImpl类的startActivity()的方法:

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

          从上面可以看到,最终会调用mMainThread.getInstrumentation().execStartActivity(),mMainThread是ActivityThread实例,getInstrumentation()返回的是Instrumentation实例,实际上使用了ActivityThread类的mInstrumentation成员的execStartActivity方法;而ActivityThread 实际上是主线程,因为主线程一个进程只有一个,所以这里是一个良好的Hook点,即Hook主线程对象。

          b.选择合适代理方式

          要将这个主线程对象里面的mInstrumentation替换成修改过的代理对象;要替换主线程对象里面的字段,得先拿到主线程对象的引用,如何获取呢?
          ActivityThread类里面有一个静态方法currentActivityThread,通过它可以拿到这个对象类;但ActivityThread是一个隐藏类,需用反射去获取拿到currentActivityThread后,要修改它的mInstrumentation字段为修改后的代理对象。实现如下:

        public static void hookInstrumentation(Context context) {
            try {
                //step1:先获取到当前的ActivityThread对象, 该对象是mInstrumentation的持有者
                Class<?> activityThreadClz = Class.forName("android.app.ActivityThread");
                Method currentActivityThreadMethod = activityThreadClz.getDeclaredMethod("currentActivityThread");
                currentActivityThreadMethod.setAccessible(true);
                Object currentActivityThread = currentActivityThreadMethod.invoke(null);
    
                //step2:从ActivityThread里面拿到原始的mInstrumentation
                Field instrumentation = activityThreadClz.getDeclaredField("mInstrumentation");
                instrumentation.setAccessible(true);
                Instrumentation mInstrumentation = (Instrumentation) instrumentation.get(currentActivityThread);
    
                //step3:创建Instrumentation的代理对象[接下来会讲到]
                Instrumentation proxyInstrumentation = new ProxyInstrumentation(mInstrumentation, context.getPackageManager());
    
                //step4:将持有的Instrumentation原始对象替换成代理对象[将currentActivityThread里面的instrumentation变量替换为proxyInstrumentation]
                instrumentation.set(currentActivityThread, proxyInstrumentation);
            } catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException | NoSuchMethodException | InvocationTargetException e) {
                e.printStackTrace();
            }
    
        }
    

          接下来实现这个代理对象,由于JDK动态代理只支持接口,而这个Instrumentation是一个类,因此只能手写一个静态代理类,用来覆盖掉原始的方法,实现如下:

    public class ProxyInstrumentation extends Instrumentation {
    
        private Instrumentation mBase;
        private PackageManager mPm;
    
        public ProxyInstrumentation(Instrumentation base, PackageManager pm) {
            mBase = base;
            mPm = pm;
        }
    
        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {
            Log.d("Seven", "------Hook打印execStartActivity------");
            List<ResolveInfo> resolveInfos = mPm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
    
            //检查要跳转的activity是否在Manifest.xml里面注册
            if (resolveInfos == null || resolveInfos.size() == 0) {
                //把要跳转的activity记录下来,在接下来newActivity还原的时候要拿来还原
                intent.putExtra("intent_name", intent.getComponent().getClassName());
                //把要跳转的activity改成已经在Manifest.xml里面注册过的MainActivity
                intent.setClassName(who, "com.hly.learn.MainActivity");
            }
    
            // 开始调用Instrumentation原始的方法,由于这个方法是隐藏的,因此需要使用反射调用;
            try {
                //找到这个方法
                Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                        "execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class,
                        Intent.class, int.class, Bundle.class);
                execStartActivity.setAccessible(true);
                //通过反射调用Instrumentation的execStartActivity方法
                return (ActivityResult) execStartActivity.invoke(mBase, who,
                        contextThread, token, target, intent, requestCode, options);
            } catch (Exception e) {
                throw new RuntimeException("do not support!");
            }
        }
    
        @Override
        public Activity newActivity(ClassLoader cl, String className, Intent intent)
                throws InstantiationException, IllegalAccessException, ClassNotFoundException {
            //取出上步记录下来的要跳转的activity并进行替换,来启动未注册的activity
            String intentName = intent.getStringExtra("intent_name");
            Log.d("Seven", "------newActivity内进行替换------");
            if (!TextUtils.isEmpty(intentName)) {
                return super.newActivity(cl, intentName, intent);
            }
            return super.newActivity(cl, className, intent);
        }
    }
    
          c.启动运行
    HookUtils.hookInstrumentation(mContext);
    private void hookActivity() {
        Intent i = new Intent();
        i.setClass(mContext, HookActivity.class);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mContext.getApplicationContext().startActivity(i);
    }
    
    1-25 11:28:37.627 D/Seven   ( 1852): ------Hook打印execStartActivity------
    1-25 11:28:37.665 D/Seven   ( 1852): ------newActivity内进行替换------
    

          至此,一个未在AndroidManifest.xml里面注册的activity通过hook技术方式就可以启动了。

    2.拦截点击事件统计点击次数
          a.寻找hook点

          对一个view设置点击事件的调用流程如下:

        hkBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    
                }
            });
    
        public void setOnClickListener(@Nullable OnClickListener l) {
            if (!isClickable()) {
                setClickable(true);
            }
            getListenerInfo().mOnClickListener = l;
        }
    
        ListenerInfo getListenerInfo() {
            if (mListenerInfo != null) {
                return mListenerInfo;
            }
            mListenerInfo = new ListenerInfo();
            return mListenerInfo;
        }
    
        static class ListenerInfo {
            ......
            /**
             * Listener used to dispatch click events.
             * This field should be made private, so it is hidden from the SDK.
             * {@hide}
             */
            public OnClickListener mOnClickListener;
            ......
    }
    

          Hook操作onClickListener实例,需要首先获取到onClickListener的拥有者,即:ListenerInfo,最后通过ListenerInfo获取到原始的mOnClickListener。

          b.选择合适的代理方式

          从上面的流程可以发现,要将ListenerInfo类里面的mOnClickListener替换成修改过的代理对象;要替换ListenerInfo里面的字段,得先拿到ListenerInfo,View类中有一个方法getListenerInfo() ,通过它可以拿到这个ListenerInfo,实现如下:

        public static void hookOnClickListener(View view) {
            //step1:反射执行View类的getListenerInfo()方法,拿到view的mListenerInfo对象,这个对象是点击事件mOnClickListener的持有者
            try {
                Class<?> viewClz = Class.forName("android.view.View");
                Method method = viewClz.getDeclaredMethod("getListenerInfo");
                //由于getListenerInfo()方法并不是public的,所以要加这个代码来保证访问权限
                method.setAccessible(true);
                //拿到mListenerInfo,也就是点击事件的持有者
                Object listenerInfo = method.invoke(view);
    
                //step2:找到mListenerInfo持有的点击事件对象mOnClickListener
                //内部类的表示方法:android.view.View$ListenerInfo
                Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
                Field onClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
                final View.OnClickListener onClickListenerInstance = (View.OnClickListener) onClickListener.get(
                        listenerInfo);
    
                //step3:创建自己点击事件的OnClickListener代理类
                 动态或静态创建OnClickListener的代理类proxyOnClickListener
    
                //step4:将持有者拥有的点击事件替换成代理对象[将listenerInfo里面的onClickListener变量替换为proxyOnClickListener]
                onClickListener.set(listenerInfo, proxyOnClickListener);
            } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException | NoSuchFieldException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        }
    

          接下来要创建OnClickListener的代理对象,由于OnClickListener是一个接口,因此可以使用JDK动态代理方式Java动态代理,也可以用静态代理类实现,实现方式如下:

        //方式1:自己实现代理类,将原始的View.OnClickListener对象onClickListenerInstance作为参数传入
        ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
        static class ProxyOnClickListener implements View.OnClickListener{
    
            private View.OnClickListener listener;
            private int clickCount = 0;
            ProxyOnClickListener(View.OnClickListener listener){
                this.listener = listener;
            }
    
            @Override
            public void onClick(View v) {
                clickCount++;
                Log.d("Seven", "Hook OnClickListener 2 click count " + clickCount);
                if(this.listener != null){
                    this.listener.onClick(v);
                }
        }
                
        //方式2:由于View.OnClickListener是一个接口,所以可以直接用动态代理模式
        //参数:类的加载器,要代理实现的接口(用Class数组表示,支持多接口),代理类的实际逻辑封装在new出来的InvocationHandler内
        Object proxyOnClickListener = Proxy.newProxyInstance(View.OnClickListener.class.getClassLoader(),
                        new Class[]{View.OnClickListener.class}, new InvocationHandler() {
                    private int clickCount = 0;
                    @Override
                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                             clickCount++;
                             Log.d("Seven", "Hook OnClickListener 1 click count " + clickCount);
                             return method.invoke(onClickListenerInstance, args);
                    }
        });
    
          c.启动运行
    hkBtn.setOnClickListener(this);
    HookUtils.hookonClickListener(hkBtn);
    

          运行结果如下:

    1-25 10:51:12.190 D/Seven   (26125): Hook OnClickListener 1 click count 1
    1-25 10:51:21.428 D/Seven   (26125): Hook OnClickListener 1 click count 2
    1-25 10:51:21.607 D/Seven   (26125): Hook OnClickListener 1 click count 3
    1-25 10:51:21.770 D/Seven   (26125): Hook OnClickListener 1 click count 4
    1-25 10:51:21.918 D/Seven   (26125): Hook OnClickListener 1 click count 5
    1-25 10:51:22.068 D/Seven   (26125): Hook OnClickListener 1 click count 6
    1-25 10:51:22.217 D/Seven   (26125): Hook OnClickListener 1 click count 7
    1-25 10:51:22.394 D/Seven   (26125): Hook OnClickListener 1 click count 8
    1-25 10:51:22.543 D/Seven   (26125): Hook OnClickListener 1 click count 9
    1-25 10:51:22.722 D/Seven   (26125): Hook OnClickListener 1 click count 10
    1-25 10:51:22.854 D/Seven   (26125): Hook OnClickListener 1 click count 11
    1-25 10:51:23.009 D/Seven   (26125): Hook OnClickListener 1 click count 12
    1-25 10:51:23.158 D/Seven   (26125): Hook OnClickListener 1 click count 13
    

          至此已经实现了对点击事件的hook。

    3.总结

          针对以上两个案例分析,我们可以看到在进行hook时主要分为以下几步:
          a.寻找合适的hook点;
          b.找到Hook点对应的class;
          c.直接通过class或class内部的method找到对应的filed;
          d.替换对象是interface的话,直接用动态代理方式创建对象;否则,直接写个类继承要替换的类;
          e.调用filed.set(obj, proxy)来对变量进行替换;
          f.针对hook点进行相应的处理;

          hook技术涉及到的知识点主要有反射、代理及android源码的熟练程度。

    相关文章

      网友评论

          本文标题:Android Hook技术分析

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