這里主要講的Hook,是利用java上的動(dòng)態(tài)代理實(shí)現(xiàn)替換系統(tǒng)某個(gè)類,在方法調(diào)用過(guò)程中,利用反射,插入自己代碼邏輯的一種方式。
安卓插件化學(xué)習(xí) Hook系統(tǒng)服務(wù)分析
Hook技術(shù)主要用的是java的動(dòng)態(tài)代理,掌握類動(dòng)態(tài)代理,其實(shí)不難理解Hook原理,只不過(guò)是找到一個(gè)我們需要的Hook點(diǎn),然后動(dòng)態(tài)代理獲取到系統(tǒng)目標(biāo)類的代理對(duì)象,然后就可以在InvocationHandler中對(duì)想要修改的方法邏輯插入自己需求邏輯。
一、Hook的必要條件
根據(jù)上一篇中Java反射與代理機(jī)制 講的動(dòng)態(tài)代理機(jī)制,重要的是兩點(diǎn):
1、Hook對(duì)象需要實(shí)現(xiàn)一個(gè)接口,并且這個(gè)接口方法中有我們需要注入代碼的目標(biāo)方法。
滿足這兩點(diǎn),就可以利用newProxyInstance方法構(gòu)造出目標(biāo)對(duì)象的代理類,并且在代理對(duì)象反射調(diào)用的時(shí)候,可以調(diào)用到Handler里面的對(duì)應(yīng)方法invoke方法里面。
2、滿足1條件的同時(shí),獲取目標(biāo)Hook對(duì)象。
這點(diǎn)為整個(gè)Hook過(guò)程中的難點(diǎn),要想對(duì)某個(gè)對(duì)象的方法Hook,就要得到該類的對(duì)象,然后對(duì)于系統(tǒng)的類調(diào)用,很多情況是不容易獲取對(duì)象引用的。然后我們可以找一些靜態(tài)變量(不用獲取類實(shí)例)或者一些單例類(反正是單例子,肯定就一個(gè)),
來(lái)降低hook的難度及尋求技巧。
二、系統(tǒng)服務(wù)層架構(gòu)簡(jiǎn)單分析
想要hook系統(tǒng)服務(wù),就要熟悉系統(tǒng)服務(wù)的基本架構(gòu),主要是了解應(yīng)用與系統(tǒng)交互的Binder架構(gòu)方式,最好要先了解Binder的相關(guān)知識(shí)。
結(jié)合上節(jié)講的Binder,總的來(lái)說(shuō)可以理解系統(tǒng)服務(wù)的使用主要理解下面這幾點(diǎn):
- 1.調(diào)用getSystemService(serviceName)方法可獲取服務(wù)對(duì)象在本地進(jìn)程的一個(gè)業(yè)務(wù)邏輯管理類。
- 2.方法內(nèi)用到遠(yuǎn)端對(duì)象的,其實(shí)是調(diào)用了ServiceManager的getService方法,獲取Ixxx類或xxxManager(以下用Ixxx代替)的遠(yuǎn)端Binder實(shí)體的一個(gè)本地BinderProxy。
- 3.調(diào)用ServiceManager的getService方法獲取遠(yuǎn)端服務(wù)的IBinder對(duì)象,這個(gè)過(guò)程需要底層Binder驅(qū)動(dòng)完成IPC通信。
- 4.有了遠(yuǎn)端服務(wù)的IBinder對(duì)象之后,通過(guò)Stub類的asInterface方法進(jìn)行類型轉(zhuǎn)化,獲取目標(biāo)接口對(duì)象。
- 5.系統(tǒng)中的服務(wù)獲取都是肯定是跨進(jìn)程的,遠(yuǎn)端服務(wù)都是在system_server進(jìn)程中的,所以asInterface方法中返回的是Proxy代理對(duì)象,也就是本地端的中間者。
- 6.最后返回的對(duì)象其實(shí)就是這個(gè)Proxy對(duì)象,而這個(gè)對(duì)象內(nèi)部使用了靜態(tài)代理方式,內(nèi)部有一個(gè)來(lái)自遠(yuǎn)端的mRemote變量即IBinder對(duì)象。然后直接調(diào)用方法其實(shí)就是調(diào)用mRemote的transact方法進(jìn)行通信了。
三、Android系統(tǒng)中常用Hook點(diǎn)
安卓系統(tǒng)中有很多我們可以直接動(dòng)態(tài)代理的地方,要想了解和發(fā)現(xiàn)這些可hook的點(diǎn),需要我們熟練的通讀和理解源碼知識(shí)、比如應(yīng)用的啟動(dòng)過(guò)程、四大組件的啟動(dòng)過(guò)程、Handler源碼分析、View繪制流程等一系列基本知識(shí)體系。
通用的hook方案來(lái)接管系統(tǒng)各類ManagerService
我們想要獲取系統(tǒng)的一個(gè)服務(wù)都會(huì)用到這么一段代碼如下:
XXXManager manager=(XXXManager)getSystemService(XXX_SERVICE);
然后分析getSystemService方法的具體實(shí)現(xiàn),可以發(fā)現(xiàn)此類Manager自身系統(tǒng)服務(wù)相關(guān)方法在應(yīng)用本地提供的一個(gè)代理類,真正的實(shí)現(xiàn)方法會(huì)同行g(shù)etService()方法IPC到系統(tǒng)進(jìn)程。
那么我們分析下安卓源碼的實(shí)現(xiàn),SystemServiceRegistry提供了本地可類Manager的獲取接口,任何找個(gè)Manager,分析,比如進(jìn)入ActivityManager,發(fā)現(xiàn)每個(gè)方法進(jìn)步都會(huì)調(diào)用
ActivityManagerNative.getDefault()方法獲取遠(yuǎn)端proxy來(lái)IPC系統(tǒng)進(jìn)程的實(shí)現(xiàn)。
其他Manager類似,最終都會(huì)通過(guò)下面方法,來(lái)獲取遠(yuǎn)端對(duì)象的Proxy。
IBinder b = ServiceManager.getService("activity");
分析到這里,是不是就可以肯定只要我們Hook掉這個(gè)本地的代理,就可以騙掉系統(tǒng)遠(yuǎn)端實(shí)現(xiàn),并在這個(gè)代理類中注入我們需求邏輯,那接下來(lái)看看這個(gè)本地代理的獲取源碼,符合動(dòng)態(tài)代理要求嗎?
public final class ServiceManager {
private static final String TAG = "ServiceManager";
private static IServiceManager sServiceManager;
private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;
}
// Find the service manager
sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
return sServiceManager;
}
/**
* Returns a reference to a service with the given name.
*
* @param name the name of the service to get
* @return a reference to the service, or <code>null</code> if the service doesn't exist
*/
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;
}
/**
* Place a new @a service called @a name into the service
* manager.
*
* @param name the name of the new service
* @param service the service object
*/
public static void addService(String name, IBinder service) {
try {
getIServiceManager().addService(name, service, false);
} catch (RemoteException e) {
Log.e(TAG, "error in addService", e);
}
}
......省略
}
該類中,主要看有個(gè)getService方法,和一個(gè)sCache 緩存。
sCache中保存的是遠(yuǎn)端服務(wù)的一個(gè)Ibinder對(duì)象,很明顯他是實(shí)現(xiàn)Ibinder接口的, 并且這兩個(gè)東西都是靜態(tài)的,意味著我們可以反射調(diào)用getService,然后把sCache里的目標(biāo)Ibinder替換為我們的動(dòng)態(tài)代理對(duì)象。
Hook掉這個(gè)對(duì)象是不是就可以攔截系統(tǒng)方法了呢? 答案是否定的。
應(yīng)為這里hook掉的是一個(gè)IBinder接口,只是Binder驅(qū)動(dòng)給我們的一個(gè)BinderProxy,BinderProxy是Binder內(nèi)部final類型的類只是實(shí)現(xiàn)類IBinder接口,并沒(méi)有我們需要攔截的方法。
那怎么才能夠攔截我們的目標(biāo)方法呢? 當(dāng)然是找到有這些方法的接口類,比如:IActivityManager、IServiceManager、IClipboard等...
那么這些接口類是怎么通過(guò)Binder驅(qū)動(dòng)返回的BinderProxy對(duì)象來(lái)轉(zhuǎn)化的呢? 做過(guò)AIDL開(kāi)發(fā)的,應(yīng)該很熟悉下面代碼
//參數(shù)就是返回的BinderProxy
public static Ixxx asInterface(IBinder obj) {
if(obj == null) {
return null;
} else {
IInterface iin = obj.queryLocalInterface("android.app.Ixxx");
return (Ixxx)(iin != null && iin instanceof Ixxx?(Ixxx)iin:new Ixxx.Stub.Proxy(obj));
}
}
可以看出,只要obj.queryLocalInterface返回不為null,就會(huì)返回這個(gè)方法里的內(nèi)容給外界調(diào)用的地方(即Ixx類的接口賦值)。
而queryLocalInterface方法又正好是IBinder接口中的方法,那么我們已經(jīng)Hook掉的BinderProxy,再次hook掉BinderProxy的queryLocalInterface()方法,就可以完全替換系統(tǒng)層Ixx
類的遠(yuǎn)端服務(wù)的本地代理接口。
關(guān)鍵代碼如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("queryLocalInterface".equals(method.getName()){
return Proxy.newProxyInstance(getClassLoader(),
new Class[]{Ixxx},
new HookBinderInvocationHandler();
}
}
通過(guò)以上代碼,就可以在HookBinderInvocationHandler類中,的invoke方法中連接Ixx接口的所有系統(tǒng)方法,并且注入自己的代碼邏輯。
到這里大功告成,通過(guò)這種方法,基本上系統(tǒng)方法都可以hook掉。但是有沒(méi)有別的辦法呢?這種在辦法至少需要有兩個(gè)hook點(diǎn),是否有必要呢?
其實(shí)hook是一項(xiàng)技巧活,Hook的次數(shù)需要實(shí)際情況的而定,要想通過(guò)動(dòng)態(tài)代理實(shí)現(xiàn)hook,就需要從上面說(shuō)的兩點(diǎn)出發(fā),獲取可hook的對(duì)象及地點(diǎn),找這兩個(gè)點(diǎn)的難易程度決定了整個(gè)hook過(guò)程的次數(shù)及難易程度。
AMS服務(wù)的hook分析
在插件化實(shí)現(xiàn)過(guò)程中,Hook系統(tǒng)AMS是最基本也是最重要的學(xué)習(xí)內(nèi)容, 接管AMS才能定制化相關(guān)插件邏輯,為應(yīng)用層開(kāi)發(fā)解耦。
Hook的技術(shù)需要靈活應(yīng)用,比如AMS的Hook本來(lái)可以用上面的通用方法Hook掉,那具體問(wèn)題具體分析,有沒(méi)有更加簡(jiǎn)單的辦法呢? 有!
想要找到AMS精確的hook點(diǎn),需要對(duì)應(yīng)用的啟動(dòng)有一定了解,可以這篇文章分析AMS遠(yuǎn)端服務(wù)調(diào)用機(jī)制以及Activity的啟動(dòng)流程。
通用Hook方案中,由于第一次hook無(wú)法獲取Ixx類的接口對(duì)象,所以多了一次hook。然后是不是只要我們一次性獲取Ixx類的實(shí)例對(duì)象,就可以一次Hook完成接管系統(tǒng)服務(wù)。

看下應(yīng)用啟動(dòng)過(guò)程源碼及ActivityManager源碼,會(huì)發(fā)現(xiàn)遠(yuǎn)端代理的獲取并沒(méi)有每次都采用getService,而是采用單例形式保存在一個(gè)靜態(tài)變量里。
ActivityManagerNative.getDefault()
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
從上面代碼可以看出,這里獲取帶遠(yuǎn)端的BinderProxy后,通過(guò)asInterface()方法轉(zhuǎn)化成我們的hook目標(biāo)接口類,并且返回后保存在一個(gè)靜態(tài)變量里。
這樣就為我們反射和動(dòng)態(tài)代理這個(gè)點(diǎn)提供了方便。
1、使用反射獲取這個(gè)gDefault里保存的IActivityManager;
2、動(dòng)態(tài)代理產(chǎn)生一個(gè)代理類,替換掉這個(gè)IActivityManager;
代碼如下:
try{
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
// 獲取 gDefault 這個(gè)字段, 想辦法替換它
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null);
// 4.x以上的gDefault是一個(gè) android.util.Singleton對(duì)象; 我們?nèi)〕鲞@個(gè)單例里面的字段
Class<?> singleton = Class.forName("android.util.Singleton");
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// ActivityManagerNative 的gDefault對(duì)象里面原始的 IActivityManager對(duì)象
Object rawIActivityManager = mInstanceField.get(gDefault);
// 創(chuàng)建一個(gè)這個(gè)對(duì)象的代理對(duì)象, 然后替換這個(gè)字段, 讓我們的代理對(duì)象幫忙干活
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));
mInstanceField.set(gDefault, proxy);
}
四、總結(jié)
android平臺(tái)動(dòng)態(tài)代理方式hook系統(tǒng),首選需要對(duì)hook的整理邏輯很熟悉,并且能給靈活找到hook地方,核心規(guī)則就是:能獲取到要hook點(diǎn)的類對(duì)象,然后動(dòng)態(tài)代理替換掉。
通過(guò)閱讀源碼發(fā)現(xiàn),安卓平臺(tái)架構(gòu)中,很多地方都是類似的框架,所以我們可以用同樣的辦法取hook掉系統(tǒng)的其他服務(wù)。
——————
歡迎轉(zhuǎn)載,請(qǐng)標(biāo)明出處:常興E站 www.canking.win