android 插件化机制之Binder

来源:互联网 发布:java两年经验工资多少 编辑:程序博客网 时间:2024/06/06 19:34

------本文转载自  Android插件化原理解析——Hook机制之Binder Hook  这一系列的文章实在是写的好!

1, 概述

Android系统通过Binder机制给应用程序提供了一系列的系统服务,诸如ActivityManagerService,

ClipboardManager, AudioManager等;这些广泛存在系统服务给应用程序提供了诸如任务管理,音频,视频等异常强大的功能。

 插件框架作为各个插件的管理者,为了使得插件能够无缝地使用这些系统服务,自然会对这些系统服务做出一定的改造(Hook),

使得插件的开发和使用更加方便,从而大大降低插件的开发和维护成本。比如,Hook住ActivityManagerService可以

让插件无缝地使用startActivity方法而不是使用特定的方式(比如that语法)来启动插件或者主程序的任意界面。

把这种Hook系统服务的机制称之为Binder Hook,因为本质上这些服务提供者都是存在于系统进程的Binder对象。

阅读本文之前,可以先clone一份understand-plugin-framework,参考此项目的binder-hook 模块。本编文章的源码基于android 6.0.

2, 系统服务的获取过程

系统的各个远程service对象都是以Binder的形式存在的,而这些Binder有一个管理者,那就是ServiceManager;

要Hook掉这些service,自然要从这个ServiceManager下手,不然星罗棋布的Binder广泛存在于系统的各个角落,要一个个找出来还真是大海捞针。

回想一下使用系统服务的时候是怎么干的,想必这个大家一定再熟悉不过了:通过Context对象的getSystemService方法;比如要使用ActivityManager:

ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

可是这个貌似跟ServiceManager没有什么关系啊?再查看getSystemService方法;(Context的实现在ContextImpl里面):

public Object getSystemService(String name) {    ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);    return fetcher == null ? null : fetcher.getService(this);}

很简单,所有的service对象都保存在一张map里面,再看这个map是怎么初始化的:

registerService(ACCOUNT_SERVICE, new ServiceFetcher() {   public Object createService(ContextImpl ctx) {      IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);            IAccountManager service = IAccountManager.Stub.asInterface(b);           return new AccountManager(ctx, service);}});

在ContextImpl的静态初始化块里面,有的Service是像上面这样初始化的;可以看到,

确实使用了ServiceManager;当然还有一些service并没有直接使用ServiceManager,

而是做了一层包装并返回了这个包装对象,比如的ActivityManager,它返回的是ActivityManager这个包装对象:

registerService(ACTIVITY_SERVICE, new ServiceFetcher() {   public Object createService(ContextImpl ctx) {        return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());  }});

但是在ActivityManager这个类内部,也使用了ServiceManager;具体来说,

因为ActivityManager里面所有的核心操作都是使用ActivityManagerNative.getDefault()完成的。

那么这个语句干了什么呢?

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {        protected IActivityManager create() {            IBinder b = ServiceManager.getService("activity");            IActivityManager am = asInterface(b);            return am;        }    };

因此,通过分析得知,系统Service的使用其实就分为两步:

IBinder b = ServiceManager.getService("service_name"); // 获取原始的IBinder对象IXXInterface in = IXXInterface.Stub.asInterface(b); // 转换为Service接口

3.寻找Hook点

由于系统服务的使用者都是对第二步获取到的IXXInterface进行操作,因此如果要hook掉某个系统服务,

只需要把第二步的asInterface方法返回的对象修改为为Hook过的对象就可以了。

3.1 asInterface过程

接下来分析asInterface方法,然后想办法把这个方法的返回值修改为Hook过的系统服务对象。这里以系统剪切版服务为例,

源码位置为android.content.IClipboard,IClipboard.Stub.asInterface方法代码如下:

public static android.content.IClipboard asInterface(android.os.IBinder obj) {    if ((obj == null)) {        return null;     }    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // Hook点    if (((iin != null) && (iin instanceof android.content.IClipboard))) {        return ((android.content.IClipboard) iin);    }    return new android.content.IClipboard.Stub.Proxy(obj);}

这个方法的意思就是:先查看本进程是否存在这个Binder对象,如果有那么直接就是本进程调用了;

如果不存在那么创建一个代理对象,让代理对象委托驱动完成跨进程调用。

 观察这个方法,前面的那个if语句判空返回肯定动不了手脚;最后一句调用构造函数然后直接返回也是无从下手,

要修改asInterface方法的返回值,唯一能做的就是从这一句下手:  

android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // Hook点

可以尝试修改这个obj对象的queryLocalInterface方法的返回值,并保证这个返回值符合接下来的if条件检测,那么就达到了修改asInterface方法返回值的目的。

而这个obj对象刚好是第一步返回的IBinder对象,接下来尝试对这个IBinder对象的queryLocalInterface方法进行hook。

3.2 getService过程

上文分析得知,想要修改IBinder对象的queryLocalInterface方法;获取IBinder对象的过程如下:

IBinder b = ServiceManager.getService("service_name");

因此,希望能修改这个getService方法的返回值,让这个方法返回一个伪造过的IBinder对象;

这样,可以在自己伪造的IBinder对象的queryLocalInterface方法作处理,进而使得asInterface方法返回

在queryLocalInterface方法里面处理过的值,最终实现hook系统服务的目的。

    在跟踪这个getService方法之前思考一下,由于系统服务是一系列的远程Service,它们的本体,

也就是 Binder本地对象一般都存在于某个单独的进程,在这个进程之外的其他进程存在的都是这些Binder本地对象的代理

。因此在的进程里面,存在的也 只是这个Binder代理对象,也只能对这些Binder代理对象下手。

然后,这个getService是一个静态方法,如果此方法什么都不做,拿到Binder代理对象之后直接返回;

那么就无能为力了:没有办法拦截一个静态方法,也没有办法获取到这个静态方法里面的局部变量(即希望修改的那个Binder代理对象)。

接下来就可以看这个getService的代码了:

public static IBinder getService(String name) {    try {        IBinder service = sCache.get(name);        if (service != null) {            return service;        } else {            return getIServiceManager().getService(name);        }    } catch (RemoteException e) {        Log.e(TAG, "error in getService", e);    }    return null;}

天无绝人之路!ServiceManager为了避免每次都进行跨进程通信,把这些Binder代理对象缓存在一张map里面。

可以替换这个map里面的内容为Hook过的IBinder对象,由于系统在getService的时候每次都会优先查找缓存,

因此返回给使用者的都是被修改过的对象,从而达到瞒天过海的目的。

总结一下,要达到修改系统服务的目的,需要如下两步:

1.首先肯定需要伪造一个系统服务对象,接下来就要想办法让asInterface能够返回的这个伪造对象而不是原始的系统服务对象。

2.通过上文分析知道,只要让getService返回IBinder对象的queryLocalInterface方法直接返回伪造过的系统服务对象就能达到目的。

所以,需要伪造一个IBinder对象,主要是修改它的queryLocalInterface方法,让它返回伪造的系统服务对象;

然后把这个伪造对象放置在ServiceManager的缓存map里面即可。

通过Binder机制的优先查找本地Binder对象的这个特性达到了Hook掉系统服务对象的目的。

因此queryLocalInterface也失去了它原本的意义(只查找本地Binder对象,没有本地对象返回null),

这个方法只是一个傀儡,是实现hook系统对象的桥梁:通过这个“漏洞”让asInterface永远都返回伪造过的对象。

由于接管了asInterface这个方法的全部,伪造过的这个系统服务对象不能是只拥有

本地Binder对象(原始queryLocalInterface方法返回的对象)的能力,还要有Binder代理对象操纵驱动的能力。

     接下来就以Hook系统的剪切版服务为例,用实际代码来说明,如何Hook掉系统服务。

4. Hook系统剪切版服务

4.1 伪造剪切版服务对象

首先用代理的方式伪造一个剪切版服务对象,具体代码如下,用动态代理的方式Hook掉了hasPrimaryClip(),getPrimaryClip()这两个方法:

public class BinderHookHandler implements InvocationHandler {    private static final String TAG = "BinderHookHandler";    // 原始的Service对象 (IInterface)    Object base;    public BinderHookHandler(IBinder base, Class<?> stubClass) {        try {            Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class);            // IClipboard.Stub.asInterface(base);            this.base = asInterfaceMethod.invoke(null, base);        } 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 {        // 把剪切版的内容替换为 "you are hooked"        if ("getPrimaryClip".equals(method.getName())) {            Log.d(TAG, "hook getPrimaryClip");            return ClipData.newPlainText(null, "you are hooked");        }        // 欺骗系统,使之认为剪切版上一直有内容        if ("hasPrimaryClip".equals(method.getName())) {            return true;        }        return method.invoke(base, args);    }}

注意,拿到原始的IBinder对象之后,如果希望使用被Hook之前的系统服务,并不能直接使用这个IBinder对象,

而是需要使用asInterface方法将它转换为IClipboard接口;

因为getService方法返回的IBinder实际上是一个裸Binder代理对象,它只有与驱动打交道的能力,

但是它并不能独立工作,需要人指挥它;asInterface方法返回的IClipboard.Stub.Proxy类的对象通过操纵

这个裸BinderProxy对象从而实现了具体的IClipboard接口定义的操作。

4.2伪造IBinder 对象

在上一步中,已经伪造好了系统服务对象,现在要做的就是想办法让asInterface方法返回伪造的对象了;伪造一个IBinder对象:

public class BinderProxyHookHandler implements InvocationHandler {    private static final String TAG = "BinderProxyHookHandler";    // 绝大部分情况下,这是一个BinderProxy对象    // 只有当Service和在同一个进程的时候才是Binder本地对象    // 这个基本不可能    IBinder base;    Class<?> stub;    Class<?> iinterface;    public BinderProxyHookHandler(IBinder base) {        this.base = base;        try {            this.stub = Class.forName("android.content.IClipboard$Stub");            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())) {            Log.d(TAG, "hook queryLocalInterface");            // 这里直接返回真正被Hook掉的Service接口            // 这里的 queryLocalInterface 就不是原本的意思了            // 肯定不会真的返回一个本地接口, 因为接管了 asInterface方法的作用           // 因此必须是一个完整的 asInterface 过的 IInterface对象, 既要处理本地对象,也要处理代理对象        // 这只是一个Hook点而已, 它原始的含义已经被重定义了; 因为会永远确保这个方法不返回null           // 让 IClipboard.Stub.asInterface 永远走到if语句的else分支里面            return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),                    // asInterface 的时候会检测是否是特定类型的接口然后进行强制转换                    // 因此这里的动态代理生成的类型信息的类型必须是正确的                    new Class[] { IBinder.class, IInterface.class, this.iinterface },                    new BinderHookHandler(base, stub));        }        Log.d(TAG, "method:" + method.getName());        return method.invoke(base, args);    }}

使用动态代理的方式伪造了一个跟原始IBinder一模一样的对象,然后在这个伪造的IBinder

对象的queryLocalInterface方法里面返回了第一步创建的伪造过的系统服务对象;注意看注释。

4.3替换ServiceManager的IBinder对象

现在就是万事具备,只欠东风了;使用反射的方式修改ServiceManager类里面缓存的Binder对象,

使得getService方法返回伪造的IBinder对象,进而asInterface方法使用伪造IBinder对象的queryLocalInterface方法返回了伪造的系统服务对象。

代码较简单,如下:

final String CLIPBOARD_SERVICE = "clipboard";// 下面这一段的意思实际就是: ServiceManager.getService("clipboard");// 只不过 ServiceManager这个类是@hide的Class<?> serviceManager = Class.forName("android.os.ServiceManager");Method getService = serviceManager.getDeclaredMethod("getService", String.class);// ServiceManager里面管理的原始的Clipboard Binder对象// 一般来说这是一个Binder代理对象IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE);// Hook 掉这个Binder代理对象的 queryLocalInterface 方法// 然后在 queryLocalInterface 返回一个IInterface对象, hook掉感兴趣的方法即可.IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),        new Class<?>[] { IBinder.class },        new BinderProxyHookHandler(rawBinder));// 把这个hook过的Binder代理对象放进ServiceManager的cache里面// 以后查询的时候 会优先查询缓存里面的Binder, 这样就会使用被修改过的Binder了Field cacheField = serviceManager.getDeclaredField("sCache");cacheField.setAccessible(true);Map<String, IBinder> cache = (Map) cacheField.get(null);cache.put(CLIPBOARD_SERVICE, hookedBinder);

接下来,在app里面使用剪切版,比如长按进行粘贴之后,剪切版的内容永远都是youare hooked了;

这样,Hook系统服务的目的宣告完成!

也许你会问,插件框架会这么hook吗?如果不是那么插件框架hook这些干什么?

插件框架当然不会做替换文本这么无聊的事情,DroidPlugin插件框架管理插件使得插件就像是主程序一样,

因此插件需要使用主程序的剪切版,插件之间也会共用剪切版;其他的一些系统服务也类似,

这样就可以达到插件和宿主程序之间的天衣服缝,水乳交融!另外,ActivityManager以及PackageManager

这两个系统服务虽然也可以通过这种方式hook,但是由于它们的重要性和特殊性,使用了另外一种方式。

0 0
原创粉丝点击