Android系统通过Binder机制给应用程序提供了一系列的系统服务,诸如ActivityManagerService
,ClipboardManager
, AudioManager
等;这些广泛存在系统服务给应用程序提供了诸如任务管理,音频,视频等异常强大的功能。
插件框架作为各个插件的管理者,为了使得插件能够无缝地使用这些系统服务,自然会对这些系统服务做出一定的改造(Hook),使得插件的开发和使用更加方便,从而大大降低插件的开发和维护成本。比如,Hook住ActivityManagerService
可以让插件无缝地使用startActivity
方法而不是使用特定的方式(比如that语法)来启动插件或者主程序的任意界面。
我们把这种Hook系统服务的机制称之为Binder Hook,因为本质上这些服务提供者都是存在于系统各个进程的Binder对象。因此,要理解接下来的内容必须了解Android的Binder机制,可以参考我之前的文章Binder学习指南
阅读本文之前,可以先clone一份 understand-plugin-framework,参考此项目的binder-hook
模块。另外,插件框架原理解析系列文章见索引。
系统服务的获取过程
我们知道系统的各个远程service对象都是以Binder的形式存在的,而这些Binder有一个管理者,那就是ServiceManager
;我们要Hook掉这些service,自然要从这个ServiceManager
下手,不然星罗棋布的Binder广泛存在于系统的各个角落,要一个个找出来还真是大海捞针。
回想一下我们使用系统服务的时候是怎么干的,想必这个大家一定再熟悉不过了:通过Context
对象的getSystemService
方法;比如要使用ActivityManager
1 |
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); |
可是这个貌似跟ServiceManager
没有什么关系啊?我们再查看getSystemService
方法;(Context的实现在ContextImpl
里面):
1 2 3 4 |
public Object getSystemService(String name) { ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name); return fetcher == null ? null : fetcher.getService(this); } |
很简单,所有的service对象都保存在一张map
里面,我们再看这个map是怎么初始化的:
1 2 3 4 5 6 |
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
这个包装对象:
1 2 3 4 |
registerService(ACTIVITY_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler()); }}); |
但是在ActivityManager
这个类内部,也使用了ServiceManager
;具体来说,因为ActivityManager里面所有的核心操作都是使用ActivityManagerNative.getDefault()
完成的。那么这个语句干了什么呢?
1 2 3 4 5 6 7 |
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() { protected IActivityManager create() { IBinder b = ServiceManager.getService("activity"); IActivityManager am = asInterface(b); return am; } }; |
因此,通过分析我们得知,系统Service的使用其实就分为两步:
1 2 |
IBinder b = ServiceManager.getService("service_name"); // 获取原始的IBinder对象 IXXInterface in = IXXInterface.Stub.asInterface(b); // 转换为Service接口 |
寻找Hook点
在Android 插件化原理解析(2):Hook 机制之动态代理里面我们说过,Hook分为三步,最关键的一步就是寻找Hook点。我们现在已经搞清楚了系统服务的使用过程,那么就需要找出在这个过程中,在哪个环节是最合适hook的。
由于系统服务的使用者都是对第二步获取到的IXXInterface
进行操作,因此如果我们要hook掉某个系统服务,只需要把第二步的asInterface
方法返回的对象修改为为我们Hook过的对象就可以了。
asInterface过程
接下来我们分析asInterface
方法,然后想办法把这个方法的返回值修改为我们Hook过的系统服务对象。这里我们以系统剪切版服务为例,源码位置为android.content.IClipboard
,IClipboard.Stub.asInterface
方法代码如下:
这个方法的意思就是:先查看本进程是否存在这个Binder对象,如果有那么直接就是本进程调用了;如果不存在那么创建一个代理对象,让代理对象委托驱动完成跨进程调用。
观察这个方法,前面的那个if语句判空返回肯定动不了手脚;最后一句调用构造函数然后直接返回我们也是无从下手,要修改asInterface
方法的返回值,我们唯一能做的就是从这一句下手:
1 |
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // Hook点 |
我们可以尝试修改这个obj
对象的queryLocalInterface
方法的返回值,并保证这个返回值符合接下来的if
条件检测,那么就达到了修改asInterface
方法返回值的目的。
而这个obj
对象刚好是我们第一步返回的IBinder
对象,接下来我们尝试对这个IBinder
对象的queryLocalInterface
方法进行hook。
getService过程
上文分析得知,我们想要修改IBinder
对象的queryLocalInterface
方法;获取IBinder
对象的过程如下:
1 |
IBinder b = ServiceManager.getService("service_name"); |
因此,我们希望能修改这个getService
方法的返回值,让这个方法返回一个我们伪造过的IBinder
对象;这样,我们可以在自己伪造的IBinder
对象的queryLocalInterface
方法作处理,进而使得asInterface
方法返回在queryLocalInterface
方法里面处理过的值,最终实现hook系统服务的目的。
在跟踪这个getService
方法之前我们思考一下,由于系统服务是一系列的远程Service,它们的本体,也就是Binder本地对象一般都存在于某个单独的进程,在这个进程之外的其他进程存在的都是这些Binder本地对象的代理。因此在我们的进程里面,存在的也只是这个Binder代理对象,我们也只能对这些Binder代理对象下手。(如果这一段看不懂,建议不要往下看了,先看Binder学习指南)
然后,这个getService
是一个静态方法,如果此方法什么都不做,拿到Binder代理对象之后直接返回;那么我们就无能为力了:我们没有办法拦截一个静态方法,也没有办法获取到这个静态方法里面的局部变量(即我们希望修改的那个Binder代理对象)。
接下来就可以看这个getService
的代码了:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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
的时候每次都会优先查找缓存,因此返回给使用者的都是被我们修改过的对象,从而达到瞒天过海的目的。
总结一下,要达到修改系统服务的目的,我们需要如下两步:
- 首先肯定需要伪造一个系统服务对象,接下来就要想办法让
asInterface
能够返回我们的这个伪造对象而不是原始的系统服务对象。 - 通过上文分析我们知道,只要让
getService
返回IBinder
对象的queryLocalInterface
方法直接返回我们伪造过的系统服务对象就能达到目的。所以,我们需要伪造一个IBinder对象,主要是修改它的queryLocalInterface
方法,让它返回我们伪造的系统服务对象;然后把这个伪造对象放置在ServiceManager
的缓存map
里面即可。
我们通过Binder机制的优先查找本地Binder对象的这个特性达到了Hook掉系统服务对象的目的。因此queryLocalInterface
也失去了它原本的意义(只查找本地Binder对象,没有本地对象返回null),这个方法只是一个傀儡,是我们实现hook系统对象的桥梁:我们通过这个“漏洞”让asInterface
永远都返回我们伪造过的对象。由于我们接管了asInterface
这个方法的全部,我们伪造过的这个系统服务对象不能是只拥有本地Binder对象(原始queryLocalInterface
方法返回的对象)的能力,还要有Binder代理对象操纵驱动的能力。
接下来我们就以Hook系统的剪切版服务为例,用实际代码来说明,如何Hook掉系统服务。
Hook系统剪切版服务
伪造剪切版服务对象
首先我们用代理的方式伪造一个剪切版服务对象,关于如何使用代理的方式进行hook以及其中的原理,可以查看Android 插件化原理解析(2):Hook 机制之动态代理。
具体代码如下,我们用动态代理的方式Hook掉了hasPrimaryClip()
,getPrimaryClip()
这两个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public class BinderHookHandler implements InvocationHandler { private static final String TAG = "BinderHookHandler"; // 原始的Service对象 (IInterface) Object base; public BinderHookHandler(IBinder base, Class<?> |