Android插件化——Hook技术

为君一笑 提交于 2019-11-26 23:54:18

1、Hook技术之动态代理

Hook技术的基础和必备技术是动态代理,关于动态代理的使用和原理参见Java动态代理

2、Binder Hook(Hook 系统服务)

2.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对象
    try {
51            IBinder service = sCache.get(name); //从缓存中获取
52            if (service != null) {
53                return service;
54            } else {
55                return getIServiceManager().getService(name);
56            }
57        } catch (RemoteException e) {
58            Log.e(TAG, "error in getService", e);
59        }
        return null;
61    }

在服务获取的过程中会调用ServiceFetcher的createService()方法,在create()中首先获取系统中保存的Binder对象,然后根据Binder对象查找服务对应的代理类;

  • IJobScheduler.Stub.asInterface():根据服务的Binder对象获取服务的代理类;
public static com.pomelos.music.MediaAidlInterface asInterface(android.os.IBinder obj){
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); //1、从本地查找是否存在复合的实例
if (((iin!=null)&&(iin instanceof com.pomelos.music.MediaAidlInterface))) {
return ((com.pomelos.music.MediaAidlInterface)iin);
}
return new com.pomelos.music.MediaAidlInterface.Stub.Proxy(obj); //2、创建远程代理
}
  • 总结一下服务的获取过程:
  1. 在系统开始时,系统会像SYSTEM_SERVICE_FETCHERS注册封装服务的ServiceFetcher实例
  2. 在程序调用获取服务时,根据服务名称从SYSTEM_SERVICE_FETCHERS查找并返回对应的ServiceFetcher实例
  3. 调用实例的get()获取服务时,首先从ServerManager中获取系统中保存服务的Binder
  4. 调用IxxInterface的asInterface()方法返回Binder的代理类
2.2、寻找Hook点
  1. 通过上面的分析知道,可以操作的地方就是obj.queryLocalInterface(),如果我们Hook了传入的Binder对象,修改他的queryLocalInterface就可以返回替代的对象的代理对象,就可实现代理
  2. 要想实现目标1就必须确保ServerManager的查找中返回我们指定的Binder,好在ServerManager中从系统Map缓存中获取,我们只要将代理的Binder放在缓存的Map,然后在查找时即可返回指定的Binder;
2.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. 在构造函数中获取并保存系统服务本身的代理类
  2. 拦截剪切方法,使系统一直认为剪切板上有内容
  • 创建Binder对象,此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);
    }
}

总结:

  1. 保存ServerManager中原来真正的Binder
  2. 获取两个类用于获取原来的Interface代理类
  3. Hook了queryLocalInterface方法
  4. 创建并返回代理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);

3、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);
  • 重写Instrumentation方法,用于替换代理Activity
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")) {
      intent.setClassName(who, ProxyActivity.class.getCanonicalName());
   }
   intent.putExtra(ACTIVITY_RAW, classname);
  
   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
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()创建真实的Activity对象

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

  • 创建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);
   }
}
  • 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);
  • 注册和创建代理服务
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点,才能实现真正的效果;

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!