美文网首页Android技术知识
Android插件化——高手必备的Hook技术

Android插件化——高手必备的Hook技术

作者: 冬日毛毛雨 | 来源:发表于2020-12-17 17:36 被阅读0次

    1、Hook技术

    在程序开发中方法的调用和执行都是按顺序执行的,那如果我们想修改或接入程序的执行,就必须在执行的过程中插入自己的程序,但同时又不影响原来程序的执行,这就是Hook 技术,Hook中文意思是钩子,它是一个两头的钩子,切开原来的程序然后用Hook勾住两端,是程序仍然整体执行但数据都同过钩子传递,既然数据都要通过钩子,那我们就可以在这个过程中偷梁换柱了;

    • Hook分类
    1. 根据Hook的API语言划分,分为Hook Java 和 Hook Native
    2. 根据Hook进程划分,分为应用进程Hook 和 全局 Hook
    • 代理模式
    1. 代理模式是Hook模式的基础原型,代理模式指为某个类的操作提供代理执行
    2. 代理模式的意义在于无需修改原来的程序结构,增加或扩展程序的功能
    3. 代理类的实现:声明一个功能接口,代理类和真实类都实现这个功能接口,在代理类中保存真实类的对象,当调用代理类执行方法时,内部调用真实对象执行;
    interface Function { // 声明的方法接口
        fun function()
    }
    
    class RealFunction : Function { // 创建的真实实现类,需要执行具体的逻辑
        override fun function() {
            TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
        }
    }
    
    class ProxyFunction(val realFunction: Function) : Function { // 代理类,内部传入真实实例
        override fun function() {
            // doSomething.....
            realFunction.function()
        }
    }
    
    1. 代理模式的使用
    val realFunction = RealFunction()
    val proxyFunction = ProxyFunction(realFunction)
    proxyFunction.realFunction // 执行代理方法
    
    • 动态代理,关于动态代理的使用和原理见另一篇文章Java动态代理

    从上面的代理模式和动态代理可以看出,我们完全可以通过代理的代码去拦截和修改原来程序的逻辑,这里针对的是自己创建的类,那针对系统或框架的源码呢?是不是也可以实现呢?答案是肯定的,但从上面对Hook的定以中可以看出,我们入过要想稳定的Hook程序,那必须找到最合适且一定会执行的地方,也就是寻找Hook点,这也是重点和难点的地方,一般选择不太变换的对象最作为Hook点,如:单例、静态对象等;

    2、Hook系统中的Instrumentation

    通过上面的学习大概知道Hook的原理和使用,剩下部分已安卓中常见的Hook使用为例深入理解和使用Hook技术,我们现在利用Hook技术修改掉Activity的启动过程,由Android进阶知识树——Android四大组件启动过程知道,Activity在启动的开始和最后创建对象时都是通过Instrumentation类,而且Instrumentation在ActivityThread中的对象即进程中只有一个实例,正好符合Hook点,下面就一起实现Hook Instrumentation,具体步骤如下:

    • 创建Instrumentation的代理类,内部保存系统原来的Instrumentation
    public class FixInstrumentation extends Instrumentation {
       private Instrumentation instrumentation;
       private static final String ACTIVITY_RAW = "raw_activity";
       public FixInstrumentation(Instrumentation instrumentation) {
          this.instrumentation = instrumentation;
       }
    }
    
    • Hook系统的Instrumentation
    Class<?> classes = Class.forName("android.app.ActivityThread");
    Method activityThread = classes.getDeclaredMethod("currentActivityThread");
    activityThread.setAccessible(true);
    Object currentThread = activityThread.invoke(null);
    Field instrumentationField = classes.getDeclaredField("mInstrumentation");
    instrumentationField.setAccessible(true);
    Instrumentation instrumentationInfo = (Instrumentation) instrumentationField.get(currentThread);
    FixInstrumentation fixInstrumentation = new FixInstrumentation(instrumentationInfo);
    instrumentationField.set(currentThread, fixInstrumentation);
    
    1. 首先反射获取进程中ActivityThread的对象,然后已此对象反射获取系统中的mInstrumentation对象
    2. 创建FixInstrumentation对象,内部保存上一步获取的mInstrumentation的实例
    3. 反射将FixInstrumentation对象设置到ActivityThread中,此时进程中的mInstrumentation就是FixInstrumentation
    • 重写Instrumentation方法,用于替换代理Activity

    现在要在FixInstrumentation中实现启动Main3Activity的功能,但是Main3Activity为加载进来的插件活动,未在程序的注册清单中注册,正常启动的话一定会抛出异常,这里想利用Hook的Instrumentation对象将启动对象替换车成已注册的活动,从而逃避系统的检查,这也是Android插件化技术的方案,下面我们就在execStartActivity()方法中替换

    public ActivityResult execStartActivity(
          Context who, IBinder contextThread, IBinder token, Activity target,
          Intent intent, int requestCode, Bundle options) {
    
       ComponentName componentName = intent.getComponent();
       String packageName = componentName.getPackageName();
       String classname = componentName.getClassName();
       if (classname.equals("com.alex.kotlin.plugin.Main3Activity")) { //判断是否为Main3Activity
          intent.setClassName(who, ProxyActivity.class.getCanonicalName()); // 替换为注册的ProxyActivity启动
       }
       intent.putExtra(ACTIVITY_RAW, classname); // 同时保存原来的Main3Activity
       try {
          @SuppressLint("PrivateApi")
          Method method = instrumentation.getClass().getDeclaredMethod("execStartActivity",
                Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
          if (!Modifier.isPublic(method.getModifiers())) {
             method.setAccessible(true);
          }
          return (ActivityResult) method.invoke(instrumentation, who, contextThread, token, target, intent, requestCode, options);
    }
    
    • 创建真正启动的Activity

    上面的替换操作躲过了系统的注册检查,程序执行到newActivity()创建活动时,我们要该回真正启动的目标活动,否则一切都白忙活了,下面在newActivity()完成替换,在替换完成后执行原来的逻辑;

    public Activity newActivity(ClassLoader cl, String className,
                         Intent intent) throws InstantiationException,
          IllegalAccessException {
       String classnameIntent = intent.getStringExtra(ACTIVITY_RAW);
       String packageName = intent.getComponent().getPackageName(); // 获取Intent中保存的真正Activity包名、类名
       if (className.equals(ProxyActivity.class.getCanonicalName())) {
          ComponentName componentName = new ComponentName(packageName, classnameIntent); // 替换真实Activity的包名和类名
          intent.setComponent(componentName);
          className = classnameIntent;
       }
       Log.d("FixInstrumentation == ", "set activity is  original" + className);
       try {
          @SuppressLint("PrivateApi")
          Method method = instrumentation.getClass().getDeclaredMethod("newActivity",
                ClassLoader.class, String.class, Intent.class);
          if (!Modifier.isPublic(method.getModifiers())) {
             method.setAccessible(true);
          }
          return (Activity) method.invoke(instrumentation, cl, className, intent); // 执行原来的创建方法
       }
    }
    

    Hook Instrumentation实现Activity插件启动总结:

    1. Hook系统的Instrumentation对象,设置创建的代理类
    2. 在代理类中修改启动Activity的Intent,将启动的目标Activity替换为占位Activity,从而避免注册清单的检查
    3. 在代理类中重写newActivity()将启动的活动换回真实目标,然后继续执行原有逻辑

    3、Binder Hook(Hook 系统服务)

    上面通过Hook技术修改了活动的启动过程,属于应用程序的Hook,下面尝试Hook Android的系统服务,修改系统的功能,在Hook之前还是先了解一下系统服务的获取过程,并尝试寻找Hook点;

    3.1、系统获取服务的原理
    • ContextImpl.getSystemService(String name)
     @Override
    public Object getSystemService(String name) {
         return SystemServiceRegistry.getSystemService(this, name);
    }
    public static Object getSystemService(ContextImpl ctx, String name) {
         //1、从注册的SYSTEM_SERVICE_FETCHERS中根据名称获取ServiceFetcher
         ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); 
         return fetcher != null ? fetcher.getService(ctx) : null; //2、ServiceFetcher中创建服务
    }
    

    在使用系统服务时会直接调用Context的getSystemService(),最终调用ContextImpl中的方法,在ContextImpl中调用SystemServiceRegistry.getSystemService(),关于SystemServiceRegistry简单介绍一下,系统在启动时会向SystemServiceRegistry中注册一系列服务,在使用过程中直接根据服务名换获取服务;

    • SYSTEM_SERVICE_FETCHERS中注册服务(以JOB_SCHEDULER_SERVICE为例)
    //注册服务
    registerService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class, new StaticServiceFetcher<JobScheduler>() {
               @Override
              public JobScheduler createService() {
                 IBinder b = ServiceManager.getService(Context.JOB_SCHEDULER_SERVICE); //从ServiceManager中获取Binder
                 return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b)); //获取Binder的代理对象
             }});
    private static <T> void registerService(String serviceName, Class<T> serviceClass,ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); //以键值对的形式保存服务名称、StaticServiceFetcher实例
    }
    

    从上面的注册过程知道,系统首先将每个服务的创建过程封装在对应的ServiceFetcher对象中,然后将ServiceFetcher对象以服务名称注册在SYSTEM_SERVICE_FETCHERS中,这也就是为什么获取服务时传入服务名称;

    • ServiceManager.getService():获取系统中相应服务对应的Binder对象
      public JobScheduler createService() throws ServiceNotFoundException {
                   IBinder b = ServiceManager.getServiceOrThrow(Context.JOB_SCHEDULER_SERVICE);
                   return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));
               }
    

    在服务获取的过程中会调用ServiceFetcher的createService()方法,在create()中首先获取系统中保存的Binder对象,然后根据Binder对象调用asInterface()查找代理类,asInterface()会先检查本进程是否存在Binder对象,如果不存在则创建一个代理对象;

    • 总结一下服务的获取过程:
    1. 在系统开始时,系统会像SYSTEM_SERVICE_FETCHERS注册封装服务的ServiceFetcher实例
    2. 在程序调用获取服务时,根据服务名称从SYSTEM_SERVICE_FETCHERS查找并返回对应的ServiceFetcher实例
    3. 调用实例的get()获取服务时,首先从ServerManager中获取系统中保存服务的Binder
    4. 调用IxxInterface的asInterface()方法查找并返回Binder的代理类
    3.2、寻找Hook点
    1. 通过上面的分析知道,可以操作的地方就是obj.queryLocalInterface(),如果我们Hook了传入的Binder对象,修改他的queryLocalInterface就可以返回替代的对象的代理对象,就可实现代理;
    2. 要想实现目标1就必须确保ServerManager的查找中能返回我们指定的Binder,好在ServerManager中从系统Map缓存中获取,我们只要将代理的Binder放在缓存的Map,然后在查找时即可返回指定的Binder;
    3.3、实战——以剪切版服务为例
    • 创建服务的动态代理类
    public class FixBinder implements InvocationHandler {
        private static final String TAG = "BinderHookHandler";
        // 原来的Service对象 (IInterface)
        Object base;
        public FixBinder(IBinder base, Class<?> stubClass) {
            try {
             Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class);//获取原接口的asInterface
             this.base = asInterfaceMethod.invoke(null, base); //使用原来的Binder反射执行获取本来服务的代理类
            } catch (Exception e) {
                throw new RuntimeException("hooked failed!");
            }
        }
        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 欺骗系统,使之认为剪切版上一直有内容
            if ("hasPrimaryClip".equals(method.getName())) {
                return true;
            }
            return method.invoke(base, args); //其余方法使用原Binder代理反射执行
        }
    }
    
    1. 这里和前面直接保存系统对象不同,因为在查找服务时首先获得的是系统的Binder,只有自己利用Binder其查找才会返回代理类
    2. 在构造函数中传入系统中查找的Binder对象,然后反射调用asasInterface()获取并保存系统服务本身的服务的代理类
    3. 拦截剪切方法,拦截hasPrimaryClip()方法返回true,使系统一直认为剪切板上有内容
    • 创建Binder对象
    public class ProxyBinder implements InvocationHandler {
        IBinder base;
        Class<?> stub;
        Class<?> iinterface;
        public ProxyBinder(IBinder base) {
            this.base = base; //(1)
            try {
                this.stub = Class.forName("android.content.IClipboard$Stub”); //(2)
                this.iinterface = Class.forName("android.content.IClipboard");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("queryLocalInterface".equals(method.getName())) { //(3) 
                return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),//(4)
                        // asInterface 的时候会检测是否是特定类型的接口然后进行强制转换
                        // 因此这里的动态代理生成的类型信息的类型必须是正确的,即必须是以下3个接口实例
                        new Class[] { IBinder.class, IInterface.class, this.iinterface },
                        new FixBinder(base, stub));
            }
            return method.invoke(base, args);
        }
    }
    

    第一创建了系统服务的代理类,由前面分析知道了,代理类的使用是由Binder查询出来的,所以下一步要创建一个Binder类,并且内部拦截查询的queryLocalInterface()方法,让此方法返回第一步的代理类,创建步骤:

    1. 和普通代理一样,在代理内部保存ServerManager中原来真正的Binder
    2. 利用反射获取IClipboard$Stub类,用于查找代理类
    3. Hook了queryLocalInterface方法
    4. 在invoke()拦截到方法后,使用动态代理创建并返回IClipboard的代理Binder
    • Hook 替换ServerManager中的Binder
    final String CLIPBOARD_SERVICE = "clipboard";
    // 下面这一段的意思实际就是: ServiceManager.getService("clipboard");
    Class<?> serviceManager = Class.forName("android.os.ServiceManager");
    Method getService = serviceManager.getDeclaredMethod("getService", String.class);
    // (1)ServiceManager里面管理的原始的Clipboard Binder对象
    IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE);
    //(2) Hook 掉这个Binder代理对象的 queryLocalInterface 方法
    IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
            new Class<?>[] { IBinder.class },
            new BinderProxyHookHandler(rawBinder));
    // (3)把这个hook过的Binder代理对象放进ServiceManager的cache里面
    Field cacheField = serviceManager.getDeclaredField("sCache");
    cacheField.setAccessible(true);
    Map<String, IBinder> cache = (Map) cacheField.get(null);
    cache.put(CLIPBOARD_SERVICE, hookedBinder);
    

    通过前面两部已经将所有要创建的代理Binder实现了,剩下的就是要将ProxyBinder放入系统ServiceManager的缓存中,这样在查询时才会按我们的要求返回Binder,后面的套路才能执行下去,具体的Hook过程见代码注释;

    4、Hook 系统服务AMS(Android 9.0)

    上面两个例子已经将Hook的使用介绍清楚了,接下来再利用Hook技术拦截系统的AMS,改变系统的服务启动,也是插件化启动服务的原理,这里实现启动未注册的MyService,关于Service的启动过程点击上面的四大组件的链接查看,因为AMS也是通过Binder通信的所以Hook的第一步要实现Binder的动态代理

    • 创建AMS的代理,实现功能拦截服务的启动过程
    public class HookProxyBinder implements InvocationHandler {
       public static final String HookProxyBinder = "HookProxyBinder";
       Object binder;
       public HookProxyBinder(Object binder) {
          this.binder = binder;
       }
       @Override
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          Log.e("HookProxyBinder==", method.getName());
          if ("startService".equals(method.getName())) { //拦截启动服务
             int i = 0;
             Intent intent = null;
             for (int index = 0; index < args.length; index++) {
                if (args[index] instanceof Intent) {
                   i = index;
                   intent = (Intent) args[index];
                   break;
                }
             }
             String packageName = intent.getComponent().getPackageName();
             String className = intent.getComponent().getClassName();
             if (className.equals(MyService.class.getCanonicalName())) {
                intent.setClassName(packageName, ProxyService.class.getCanonicalName());
                intent.putExtra(HookProxyBinder, className);
             }
             args[i] = intent;
          }
          return method.invoke(binder, args);
       }
    }
    

    在invoke()方法中拦截startService(),整体实现和上面Activity的启动一样,通过在拦截到方法后替换为注册的占位服务,实现服务的启动

    • Hook系统AMS(以Android 9.0 为例)
    //1、反射获取ActivityManager类中的静态实例IActivityManagerSingleton
    Class<?> manager = Class.forName("android.app.ActivityManager");
    Field field = manager.getDeclaredField("IActivityManagerSingleton");
    field.setAccessible(true);
    Object object = field.get(null);
    //反射获取Singleton中的mInstance实例,mInstance就是调用create之后创建的对象,此处就是IActivityManager的代理实例
    Class<?> singlen = Class.forName("android.util.Singleton");
    Field field1 = singlen.getDeclaredField("mInstance");
    field1.setAccessible(true);
    Object binder = field1.get(object); //2、获取此IActivityManagerSingleton内部的mInstance
    Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
    //3、创建代理IActivityManager
    Object binder1 = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{iActivityManagerInterface}, new HookProxyBinder(binder));
    //4、将重写的代理IActivityManager设置给mInstance
    field1.set(object, binder1);
    

    上面的Hook过程也就是利用反射获取系统原来的代理类,然后将创建的代理类设置到系统中,只是这里牵扯到了Singleton类, 系统中的AMS是使用Singleton单例提供的,所以只能反射从Singleton中获取,详情见Android进阶知识树——Android四大组件启动过程

    • 注册和创建代理服务
    public class ProxyService extends Service {
       public ProxyService() {
       }
       @Override
       public IBinder onBind(Intent intent) {
          throw null;
       }
       @Override
       public int onStartCommand(Intent intent, int flags, int startId) {
          String service = intent.getStringExtra(HookProxyBinder.HookProxyBinder);
          Log.e("============++++", service);
          return super.onStartCommand(intent, flags, startId);
       }
    }
    
    • 启动服务
    Intent intentService = new Intent(MainActivity.this, MyService.class); //此处启动服务并未注册
    startService(intentService);
    

    插件启动Service过程:

    1. 通过Hook技术将系统的AMS替换为代理类,拦截启动服务的方法
    2. 在拦截方法中将启动的目标Service替换为占位Service,同时保存目标服务
    3. 在占位服务启动后获取目标服务并调用其相应方法,实现插件服务的启动

    本文从基础只是和实战的角度介绍下Hook技术,Hook技术也是插件化中必备的技术,Hook从技术角度上还是比较简单,但需要对目标原理和过程进行理解,确定合理的Hook点,才能实现真正的效果;

    大家如果还想了解更多Android 相关的更多知识点可以点进我的GitHub项目中,里面记录了许多的Android 知识点。

    相关文章

      网友评论

        本文标题:Android插件化——高手必备的Hook技术

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