Android系統(tǒng)通過(guò)Binder機(jī)制給應(yīng)用程序提供了一系列的系統(tǒng)服務(wù),諸如ActivityManagerService,ClipboardManager, AudioManager等;這些廣泛存在系統(tǒng)服務(wù)給應(yīng)用程序提供了諸如任務(wù)管理,音頻,視頻等異常強(qiáng)大的功能。
插件框架作為各個(gè)插件的管理者,為了使得插件能夠無(wú)縫地使用這些系統(tǒng)服務(wù),自然會(huì)對(duì)這些系統(tǒng)服務(wù)做出一定的改造(Hook),使得插件的開發(fā)和使用更加方便,從而大大降低插件的開發(fā)和維護(hù)成本。比如,Hook住ActivityManagerService可以讓插件無(wú)縫地使用startActivity方法而不是使用特定的方式(比如that語(yǔ)法)來(lái)啟動(dòng)插件或者主程序的任意界面。
我們把這種Hook系統(tǒng)服務(wù)的機(jī)制稱之為Binder Hook,因?yàn)楸举|(zhì)上這些服務(wù)提供者都是存在于系統(tǒng)各個(gè)進(jìn)程的Binder對(duì)象。因此,要理解接下來(lái)的內(nèi)容必須了解Android的Binder機(jī)制,可以參考我之前的文章Binder學(xué)習(xí)指南
閱讀本文之前,可以先clone一份 understand-plugin-framework,參考此項(xiàng)目的binder-hook 模塊。另外,插件框架原理解析系列文章見(jiàn)索引。
系統(tǒng)服務(wù)的獲取過(guò)程
我們知道系統(tǒng)的各個(gè)遠(yuǎn)程service對(duì)象都是以Binder的形式存在的,而這些Binder有一個(gè)管理者,那就是ServiceManager;我們要Hook掉這些service,自然要從這個(gè)ServiceManager下手,不然星羅棋布的Binder廣泛存在于系統(tǒng)的各個(gè)角落,要一個(gè)個(gè)找出來(lái)還真是大海撈針。
回想一下我們使用系統(tǒng)服務(wù)的時(shí)候是怎么干的,想必這個(gè)大家一定再熟悉不過(guò)了:通過(guò)Context對(duì)象的getSystemService方法;比如要使用ActivityManager:
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
可是這個(gè)貌似跟ServiceManager沒(méi)有什么關(guān)系???我們?cè)俨榭?code>getSystemService方法;(Context的實(shí)現(xiàn)在ContextImpl里面):
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
很簡(jiǎn)單,所有的service對(duì)象都保存在一張map里面,我們?cè)倏催@個(gè)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的靜態(tài)初始化塊里面,有的Service是像上面這樣初始化的;可以看到,確實(shí)使用了ServiceManager;當(dāng)然還有一些service并沒(méi)有直接使用ServiceManager,而是做了一層包裝并返回了這個(gè)包裝對(duì)象,比如我們的ActivityManager,它返回的是ActivityManager這個(gè)包裝對(duì)象:
registerService(ACTIVITY_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
}});
但是在ActivityManager這個(gè)類內(nèi)部,也使用了ServiceManager;具體來(lái)說(shuō),因?yàn)锳ctivityManager里面所有的核心操作都是使用ActivityManagerNative.getDefault()完成的。那么這個(gè)語(yǔ)句干了什么呢?
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
IActivityManager am = asInterface(b);
return am;
}
};
因此,通過(guò)分析我們得知,系統(tǒng)Service的使用其實(shí)就分為兩步:
IBinder b = ServiceManager.getService("service_name"); // 獲取原始的IBinder對(duì)象
IXXInterface in = IXXInterface.Stub.asInterface(b); // 轉(zhuǎn)換為Service接口
尋找Hook點(diǎn)
在插件框架原理解析——Hook機(jī)制之動(dòng)態(tài)代理里面我們說(shuō)過(guò),Hook分為三步,最關(guān)鍵的一步就是尋找Hook點(diǎn)。我們現(xiàn)在已經(jīng)搞清楚了系統(tǒng)服務(wù)的使用過(guò)程,那么就需要找出在這個(gè)過(guò)程中,在哪個(gè)環(huán)節(jié)是最合適hook的。
由于系統(tǒng)服務(wù)的使用者都是對(duì)第二步獲取到的IXXInterface進(jìn)行操作,因此如果我們要hook掉某個(gè)系統(tǒng)服務(wù),只需要把第二步的asInterface方法返回的對(duì)象修改為為我們Hook過(guò)的對(duì)象就可以了。
asInterface過(guò)程
接下來(lái)我們分析asInterface方法,然后想辦法把這個(gè)方法的返回值修改為我們Hook過(guò)的系統(tǒng)服務(wù)對(duì)象。這里我們以系統(tǒng)剪切版服務(wù)為例,源碼位置為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點(diǎn)
if (((iin != null) && (iin instanceof android.content.IClipboard))) {
return ((android.content.IClipboard) iin);
}
return new android.content.IClipboard.Stub.Proxy(obj);
}
這個(gè)方法的意思就是:先查看本進(jìn)程是否存在這個(gè)Binder對(duì)象,如果有那么直接就是本進(jìn)程調(diào)用了;如果不存在那么創(chuàng)建一個(gè)代理對(duì)象,讓代理對(duì)象委托驅(qū)動(dòng)完成跨進(jìn)程調(diào)用。
觀察這個(gè)方法,前面的那個(gè)if語(yǔ)句判空返回肯定動(dòng)不了手腳;最后一句調(diào)用構(gòu)造函數(shù)然后直接返回我們也是無(wú)從下手,要修改asInterface方法的返回值,我們唯一能做的就是從這一句下手:
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // Hook點(diǎn)
我們可以嘗試修改這個(gè)obj對(duì)象的queryLocalInterface方法的返回值,并保證這個(gè)返回值符合接下來(lái)的if條件檢測(cè),那么就達(dá)到了修改asInterface方法返回值的目的。
而這個(gè)obj對(duì)象剛好是我們第一步返回的IBinder對(duì)象,接下來(lái)我們嘗試對(duì)這個(gè)IBinder對(duì)象的queryLocalInterface方法進(jìn)行hook。
getService過(guò)程
上文分析得知,我們想要修改IBinder對(duì)象的queryLocalInterface方法;獲取IBinder對(duì)象的過(guò)程如下:
IBinder b = ServiceManager.getService("service_name");
因此,我們希望能修改這個(gè)getService方法的返回值,讓這個(gè)方法返回一個(gè)我們偽造過(guò)的IBinder對(duì)象;這樣,我們可以在自己偽造的IBinder對(duì)象的queryLocalInterface方法作處理,進(jìn)而使得asInterface方法返回在queryLocalInterface方法里面處理過(guò)的值,最終實(shí)現(xiàn)hook系統(tǒng)服務(wù)的目的。
在跟蹤這個(gè)getService方法之前我們思考一下,由于系統(tǒng)服務(wù)是一系列的遠(yuǎn)程Service,它們的本體,也就是Binder本地對(duì)象一般都存在于某個(gè)單獨(dú)的進(jìn)程,在這個(gè)進(jìn)程之外的其他進(jìn)程存在的都是這些Binder本地對(duì)象的代理。因此在我們的進(jìn)程里面,存在的也只是這個(gè)Binder代理對(duì)象,我們也只能對(duì)這些Binder代理對(duì)象下手。(如果這一段看不懂,建議不要往下看了,先看Binder學(xué)習(xí)指南)
然后,這個(gè)getService是一個(gè)靜態(tài)方法,如果此方法什么都不做,拿到Binder代理對(duì)象之后直接返回;那么我們就無(wú)能為力了:我們沒(méi)有辦法攔截一個(gè)靜態(tài)方法,也沒(méi)有辦法獲取到這個(gè)靜態(tài)方法里面的局部變量(即我們希望修改的那個(gè)Binder代理對(duì)象)。
接下來(lái)就可以看這個(gè)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;
}
天無(wú)絕人之路!ServiceManager為了避免每次都進(jìn)行跨進(jìn)程通信,把這些Binder代理對(duì)象緩存在一張map里面。
我們可以替換這個(gè)map里面的內(nèi)容為Hook過(guò)的IBinder對(duì)象,由于系統(tǒng)在getService的時(shí)候每次都會(huì)優(yōu)先查找緩存,因此返回給使用者的都是被我們修改過(guò)的對(duì)象,從而達(dá)到瞞天過(guò)海的目的。
總結(jié)一下,要達(dá)到修改系統(tǒng)服務(wù)的目的,我們需要如下兩步:
- 首先肯定需要偽造一個(gè)系統(tǒng)服務(wù)對(duì)象,接下來(lái)就要想辦法讓
asInterface能夠返回我們的這個(gè)偽造對(duì)象而不是原始的系統(tǒng)服務(wù)對(duì)象。 - 通過(guò)上文分析我們知道,只要讓
getService返回IBinder對(duì)象的queryLocalInterface方法直接返回我們偽造過(guò)的系統(tǒng)服務(wù)對(duì)象就能達(dá)到目的。所以,我們需要偽造一個(gè)IBinder對(duì)象,主要是修改它的queryLocalInterface方法,讓它返回我們偽造的系統(tǒng)服務(wù)對(duì)象;然后把這個(gè)偽造對(duì)象放置在ServiceManager的緩存map里面即可。
我們通過(guò)Binder機(jī)制的優(yōu)先查找本地Binder對(duì)象的這個(gè)特性達(dá)到了Hook掉系統(tǒng)服務(wù)對(duì)象的目的。因此queryLocalInterface也失去了它原本的意義(只查找本地Binder對(duì)象,沒(méi)有本地對(duì)象返回null),這個(gè)方法只是一個(gè)傀儡,是我們實(shí)現(xiàn)hook系統(tǒng)對(duì)象的橋梁:我們通過(guò)這個(gè)“漏洞”讓asInterface永遠(yuǎn)都返回我們偽造過(guò)的對(duì)象。由于我們接管了asInterface這個(gè)方法的全部,我們偽造過(guò)的這個(gè)系統(tǒng)服務(wù)對(duì)象不能是只擁有本地Binder對(duì)象(原始queryLocalInterface方法返回的對(duì)象)的能力,還要有Binder代理對(duì)象操縱驅(qū)動(dòng)的能力。
接下來(lái)我們就以Hook系統(tǒng)的剪切版服務(wù)為例,用實(shí)際代碼來(lái)說(shuō)明,如何Hook掉系統(tǒng)服務(wù)。
Hook系統(tǒng)剪切版服務(wù)
偽造剪切版服務(wù)對(duì)象
首先我們用代理的方式偽造一個(gè)剪切版服務(wù)對(duì)象,關(guān)于如何使用代理的方式進(jìn)行hook以及其中的原理,可以查看插件框架原理解析——Hook機(jī)制之動(dòng)態(tài)代理。
具體代碼如下,我們用動(dòng)態(tài)代理的方式Hook掉了hasPrimaryClip(),getPrimaryClip()這兩個(gè)方法:
public class BinderHookHandler implements InvocationHandler {
private static final String TAG = "BinderHookHandler";
// 原始的Service對(duì)象 (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 {
// 把剪切版的內(nèi)容替換為 "you are hooked"
if ("getPrimaryClip".equals(method.getName())) {
Log.d(TAG, "hook getPrimaryClip");
return ClipData.newPlainText(null, "you are hooked");
}
// 欺騙系統(tǒng),使之認(rèn)為剪切版上一直有內(nèi)容
if ("hasPrimaryClip".equals(method.getName())) {
return true;
}
return method.invoke(base, args);
}
}
注意,我們拿到原始的IBinder對(duì)象之后,如果我們希望使用被Hook之前的系統(tǒng)服務(wù),并不能直接使用這個(gè)IBinder對(duì)象,而是需要使用asInterface方法將它轉(zhuǎn)換為IClipboard接口;因?yàn)?code>getService方法返回的IBinder實(shí)際上是一個(gè)裸Binder代理對(duì)象,它只有與驅(qū)動(dòng)打交道的能力,但是它并不能獨(dú)立工作,需要人指揮它;asInterface方法返回的IClipboard.Stub.Proxy類的對(duì)象通過(guò)操縱這個(gè)裸BinderProxy對(duì)象從而實(shí)現(xiàn)了具體的IClipboard接口定義的操作。
偽造IBinder 對(duì)象
在上一步中,我們已經(jīng)偽造好了系統(tǒng)服務(wù)對(duì)象,現(xiàn)在要做的就是想辦法讓asInterface方法返回我們偽造的對(duì)象了;我們偽造一個(gè)IBinder對(duì)象:
public class BinderProxyHookHandler implements InvocationHandler {
private static final String TAG = "BinderProxyHookHandler";
// 絕大部分情況下,這是一個(gè)BinderProxy對(duì)象
// 只有當(dāng)Service和我們?cè)谕粋€(gè)進(jìn)程的時(shí)候才是Binder本地對(duì)象
// 這個(gè)基本不可能
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 就不是原本的意思了
// 我們肯定不會(huì)真的返回一個(gè)本地接口, 因?yàn)槲覀兘庸芰?asInterface方法的作用
// 因此必須是一個(gè)完整的 asInterface 過(guò)的 IInterface對(duì)象, 既要處理本地對(duì)象,也要處理代理對(duì)象
// 這只是一個(gè)Hook點(diǎn)而已, 它原始的含義已經(jīng)被我們重定義了; 因?yàn)槲覀儠?huì)永遠(yuǎn)確保這個(gè)方法不返回null
// 讓 IClipboard.Stub.asInterface 永遠(yuǎn)走到if語(yǔ)句的else分支里面
return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
// asInterface 的時(shí)候會(huì)檢測(cè)是否是特定類型的接口然后進(jìn)行強(qiáng)制轉(zhuǎn)換
// 因此這里的動(dòng)態(tài)代理生成的類型信息的類型必須是正確的
new Class[] { IBinder.class, IInterface.class, this.iinterface },
new BinderHookHandler(base, stub));
}
Log.d(TAG, "method:" + method.getName());
return method.invoke(base, args);
}
}
我們使用動(dòng)態(tài)代理的方式偽造了一個(gè)跟原始IBinder一模一樣的對(duì)象,然后在這個(gè)偽造的IBinder對(duì)象的queryLocalInterface方法里面返回了我們第一步創(chuàng)建的偽造過(guò)的系統(tǒng)服務(wù)對(duì)象;注意看注釋,詳細(xì)解釋可以看代碼
替換ServiceManager的IBinder對(duì)象
現(xiàn)在就是萬(wàn)事具備,只欠東風(fēng)了;我們使用反射的方式修改ServiceManager類里面緩存的Binder對(duì)象,使得getService方法返回我們偽造的IBinder對(duì)象,進(jìn)而asInterface方法使用偽造IBinder對(duì)象的queryLocalInterface方法返回了我們偽造的系統(tǒng)服務(wù)對(duì)象。代碼較簡(jiǎn)單,如下:
final String CLIPBOARD_SERVICE = "clipboard";
// 下面這一段的意思實(shí)際就是: ServiceManager.getService("clipboard");
// 只不過(guò) ServiceManager這個(gè)類是@hide的
Class<?> serviceManager = Class.forName("android.os.ServiceManager");
Method getService = serviceManager.getDeclaredMethod("getService", String.class);
// ServiceManager里面管理的原始的Clipboard Binder對(duì)象
// 一般來(lái)說(shuō)這是一個(gè)Binder代理對(duì)象
IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE);
// Hook 掉這個(gè)Binder代理對(duì)象的 queryLocalInterface 方法
// 然后在 queryLocalInterface 返回一個(gè)IInterface對(duì)象, hook掉我們感興趣的方法即可.
IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
new Class<?>[] { IBinder.class },
new BinderProxyHookHandler(rawBinder));
// 把這個(gè)hook過(guò)的Binder代理對(duì)象放進(jìn)ServiceManager的cache里面
// 以后查詢的時(shí)候 會(huì)優(yōu)先查詢緩存里面的Binder, 這樣就會(huì)使用被我們修改過(guò)的Binder了
Field cacheField = serviceManager.getDeclaredField("sCache");
cacheField.setAccessible(true);
Map<String, IBinder> cache = (Map) cacheField.get(null);
cache.put(CLIPBOARD_SERVICE, hookedBinder);
接下來(lái),在app里面使用剪切版,比如長(zhǎng)按進(jìn)行粘貼之后,剪切版的內(nèi)容永遠(yuǎn)都是you are hooked了;這樣,我們Hook系統(tǒng)服務(wù)的目的宣告完成!詳細(xì)的代碼參見(jiàn) github。
也許你會(huì)問(wèn),插件框架會(huì)這么hook嗎?如果不是那么插件框架hook這些干什么?插件框架當(dāng)然不會(huì)做替換文本這么無(wú)聊的事情,DroidPlugin插件框架管理插件使得插件就像是主程序一樣,因此插件需要使用主程序的剪切版,插件之間也會(huì)共用剪切版;其他的一些系統(tǒng)服務(wù)也類似,這樣就可以達(dá)到插件和宿主程序之間的天衣服縫,水乳交融!另外,ActivityManager以及PackageManager這兩個(gè)系統(tǒng)服務(wù)雖然也可以通過(guò)這種方式hook,但是由于它們的重要性和特殊性,DroidPlugin使用了另外一種方式,我們會(huì)單獨(dú)講解。
喜歡就點(diǎn)個(gè)贊吧~持續(xù)更新,請(qǐng)關(guān)注github項(xiàng)目 understand-plugin-framework 和我的 博客!