這篇文章主要講解如何利用動態(tài)代理技術(shù)Hook掉系統(tǒng)的AMS服務(wù),來實(shí)現(xiàn)攔截Activity的啟動流程,這種hook原理方式來自DroidPlugin。代碼量不是很多,為了更容易的理解,需要掌握J(rèn)AVA的反射,動態(tài)代理技術(shù),以及Activity的啟動流程。
1、尋找Hook點(diǎn)的原則
Android中主要是依靠分析系統(tǒng)源碼類來做到的,首先我們得找到被Hook的對象,我稱之為Hook點(diǎn);什么樣的對象比較好Hook呢?一般來說,靜態(tài)變量和單例變量是相對不容易改變,是一個比較好的hook點(diǎn),而普通的對象有易變的可能,每個版本都不一樣,處理難度比較大。我們根據(jù)這個原則找到所謂的Hook點(diǎn)。
2、尋找Hook點(diǎn)
通常點(diǎn)擊一個Button就開始Activity跳轉(zhuǎn)了,這中間發(fā)生了什么,我們?nèi)绾蜨ook,來實(shí)現(xiàn)Activity啟動的攔截呢?
public void start(View view) {
Intent intent = new Intent(this, OtherActivity.class);
startActivity(intent);
}
我們的目的是要攔截startActivity方法,跟蹤源碼,發(fā)現(xiàn)最后啟動Activity是由Instrumentation類的execStartActivity做到的。其實(shí)這個類相當(dāng)于啟動Activity的中間者,啟動Activity中間都是由它來操作的
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
....
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
//通過ActivityManagerNative.getDefault()獲取一個對象,開始啟動新的Activity
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
對于ActivityManagerNative這個東東,熟悉Activity/Service啟動過程的都不陌生
public abstract class ActivityManagerNative extends Binder implements IActivityManager
繼承了Binder,實(shí)現(xiàn)了一個IActivityManager接口,這就是為了遠(yuǎn)程服務(wù)通信做準(zhǔn)備的"Stub"類,一個完整的AID L有兩部分,一個是個跟服務(wù)端通信的Stub,一個是跟客戶端通信的Proxy。ActivityManagerNative就是Stub,閱讀源碼發(fā)現(xiàn)在ActivityManagerNative 文件中還有個ActivityManagerProxy,這里就多不扯了。
static public IActivityManager getDefault() {
return gDefault.get();
}
ActivityManagerNative.getDefault()獲取的是一個IActivityManager對象,由IActivityManager去啟動Activity,IActivityManager的實(shí)現(xiàn)類是ActivityManagerService,ActivityManagerService是在另外一個進(jìn)程之中,所有Activity 啟動是一個跨進(jìn)程的通信的過程,所以真正啟動Activity的是通過遠(yuǎn)端服務(wù)ActivityManagerService來啟動的。
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;
}
其實(shí)gDefalut借助Singleton實(shí)現(xiàn)的單例模式,而在內(nèi)部可以看到先從ServiceManager中獲取到AMS遠(yuǎn)端服務(wù)的Binder對象,然后使用asInterface方法轉(zhuǎn)化成本地化對象,我們目的是攔截startActivity,所以改變IActivityManager對象可以做到這個一點(diǎn),這里gDefault又是靜態(tài)的,根據(jù)Hook原則,這是一個比較好的Hook點(diǎn)。
3、Hook掉startActivity,輸出日志
我們先實(shí)現(xiàn)一個小需求,啟動Activity的時候打印一條日志,寫一個工具類HookUtil。
public class HookUtil {
private Class<?> proxyActivity;
private Context context;
public HookUtil(Class<?> proxyActivity, Context context) {
this.proxyActivity = proxyActivity;
this.context = context;
}
public void hookAms() {
//一路反射,直到拿到IActivityManager的對象
try {
Class<?> ActivityManagerNativeClss = Class.forName("android.app.ActivityManagerNative");
Field defaultFiled = ActivityManagerNativeClss.getDeclaredField("gDefault");
defaultFiled.setAccessible(true);
Object defaultValue = defaultFiled.get(null);
//反射SingleTon
Class<?> SingletonClass = Class.forName("android.util.Singleton");
Field mInstance = SingletonClass.getDeclaredField("mInstance");
mInstance.setAccessible(true);
//到這里已經(jīng)拿到ActivityManager對象
Object iActivityManagerObject = mInstance.get(defaultValue);
//開始動態(tài)代理,用代理對象替換掉真實(shí)的ActivityManager,瞞天過海
Class<?> IActivityManagerIntercept = Class.forName("android.app.IActivityManager");
AmsInvocationHandler handler = new AmsInvocationHandler(iActivityManagerObject);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{IActivityManagerIntercept}, handler);
//現(xiàn)在替換掉這個對象
mInstance.set(defaultValue, proxy);
} catch (Exception e) {
e.printStackTrace();
}
}
private class AmsInvocationHandler implements InvocationHandler {
private Object iActivityManagerObject;
private AmsInvocationHandler(Object iActivityManagerObject) {
this.iActivityManagerObject = iActivityManagerObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i("HookUtil", method.getName());
//我要在這里搞點(diǎn)事情
if ("startActivity".contains(method.getName())) {
Log.e("HookUtil","Activity已經(jīng)開始啟動");
Log.e("HookUtil","小弟到此一游!?。?);
}
return method.invoke(iActivityManagerObject, args);
}
}
}
結(jié)合注釋應(yīng)該很容易看懂,在Application中配置一下
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
HookUtil hookUtil=new HookUtil(SecondActivity.class, this);
hookUtil.hookAms();
}
}
看看執(zhí)行結(jié)果:

可以看到,我們成功的Hook掉了startActivity,輸出了一條日志。有了上面的基礎(chǔ),現(xiàn)在我們開始來點(diǎn)有用的東西,Activity不用在清單文件中注冊,就可以啟動起來,這個怎么搞呢?
4、無需注冊,啟動Activity
如下,TargetActivity沒有在清單文件中注冊,怎么去啟動TargetActivity?
public void start(View view) {
Intent intent = new Intent(this, TargetActivity.class);
startActivity(intent);
}
這個思路可以是這樣,上面已經(jīng)攔截了啟動Activity流程,在invoke中我們可以得到啟動參數(shù)intent信息,那么就在這里,我們可以自己構(gòu)造一個假的Activity信息的intent,這個Intent啟動的Activity是在清單文件中注冊的,當(dāng)真正啟動的時候(ActivityManagerService校驗(yàn)清單文件之后),用真實(shí)的Intent把代理的Intent在調(diào)換過來,然后啟動即可。
首先獲取真實(shí)啟動參數(shù)intent信息
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".contains(method.getName())) {
//換掉
Intent intent = null;
int index = 0;
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof Intent) {
//說明找到了startActivity的Intent參數(shù)
intent = (Intent) args[i];
//這個意圖是不能被啟動的,因?yàn)锳citivity沒有在清單文件中注冊
index = i;
}
}
//偽造一個代理的Intent,代理Intent啟動的是proxyActivity
Intent proxyIntent = new Intent();
ComponentName componentName = new ComponentName(context, proxyActivity);
proxyIntent.setComponent(componentName);
proxyIntent.putExtra("oldIntent", intent);
args[index] = proxyIntent;
}
return method.invoke(iActivityManagerObject, args);
}
有了上面的兩個步驟,這個代理的Intent是可以通過ActivityManagerService檢驗(yàn)的,因?yàn)槲以谇鍐挝募凶赃^
<activity android:name=".ProxyActivity" />
為了不啟動ProxyActivity,現(xiàn)在我們需要找一個合適的時機(jī),把真實(shí)的Intent換過了來,啟動我們真正想啟動的Activity??催^Activity的啟動流程的朋友,我們都知道這個過程是由Handler發(fā)送消息來實(shí)現(xiàn)的,可是通過Handler處理消息的代碼來看,消息的分發(fā)處理是有順序的,下面是Handler處理消息的代碼:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
handler處理消息的時候,首先去檢查是否實(shí)現(xiàn)了callback接口,如果有實(shí)現(xiàn)的話,那么會直接執(zhí)行接口方法,然后才是handleMessage方法,最后才是執(zhí)行重寫的handleMessage方法,我們一般大部分時候都是重寫了handleMessage方法,而ActivityThread主線程用的正是重寫的方法,這種方法的優(yōu)先級是最低的,我們完全可以實(shí)現(xiàn)接口來替換掉系統(tǒng)Handler的處理過程。這里詳見Android源碼解析Handler系列第(一)篇 --- Message全局池
public void hookSystemHandler() {
try {
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
//獲取主線程對象
Object activityThread = currentActivityThreadMethod.invoke(null);
//獲取mH字段
Field mH = activityThreadClass.getDeclaredField("mH");
mH.setAccessible(true);
//獲取Handler
Handler handler = (Handler) mH.get(activityThread);
//獲取原始的mCallBack字段
Field mCallBack = Handler.class.getDeclaredField("mCallback");
mCallBack.setAccessible(true);
//這里設(shè)置了我們自己實(shí)現(xiàn)了接口的CallBack對象
mCallBack.set(handler, new ActivityThreadHandlerCallback(handler)) ;
} catch (Exception e) {
e.printStackTrace();
}
}
自定義Callback類
private class ActivityThreadHandlerCallback implements Handler.Callback {
private Handler handler;
private ActivityThreadHandlerCallback(Handler handler) {
this.handler = handler;
}
@Override
public boolean handleMessage(Message msg) {
Log.i("HookAmsUtil", "handleMessage");
//替換之前的Intent
if (msg.what ==100) {
Log.i("HookAmsUtil","lauchActivity");
handleLauchActivity(msg);
}
handler.handleMessage(msg);
return true;
}
private void handleLauchActivity(Message msg) {
Object obj = msg.obj;//ActivityClientRecord
try{
Field intentField = obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyInent = (Intent) intentField.get(obj);
Intent realIntent = proxyInent.getParcelableExtra("oldIntent");
if (realIntent != null) {
proxyInent.setComponent(realIntent.getComponent());
}
}catch (Exception e){
Log.i("HookAmsUtil","lauchActivity falied");
}
}
}
最后在application中注入
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//這個ProxyActivity在清單文件中注冊過,以后所有的Activitiy都可以用ProxyActivity無需聲明,繞過監(jiān)測
HookAmsUtil hookAmsUtil = new HookAmsUtil(ProxyActivity.class, this);
hookAmsUtil.hookSystemHandler();
hookAmsUtil.hookAms();
}
}
執(zhí)行,點(diǎn)擊MainActivity中的按鈕成功跳轉(zhuǎn)到了TargetActivity。這是插件化系列博客的第一篇。