一、啟動插件的四大組件
1.1、宿主啟動插件的Activity
Activity 是需要在清單文件中注冊的,顯然,插件的 Activity 沒有在宿主的清單文件中注冊,那我們如何來啟動它呢?
這里我們就需要使用 Hook 技術,來繞開系統的檢測。可能有些同學不知道 Hook 是什么,所以我們先簡單的介紹下 Hook 技術。
正常情況下對象A調用對象B,對象B處理后將數據返回給對象A,如下圖:
而加入Hook后的流程就變成了下圖形式:
Hook可以是一個方法或者一個對象,它就像一個鉤子一樣掛在對象B上面,當對象A調用對象B之前,Hook可以做一些處理,起到“欺上瞞下”的作用。而對象B就是我們常說的Hook點。為了保證Hook的穩定性,Hook點一般選擇容易找到并且不易變化的對象,如靜態變量和單例。
那么思路就來了,首先我們在宿主里面創建一個 ProxyActivity 繼承自 Activity,并且在清單中注冊。當啟動插件Activity 的時候,在系統檢測前,找到一個Hook點,然后通過 Hook 將插件 Activity 替換成 ProxyActivity,等到檢測完了后,再找一個Hook點,使用 Hook 將它們換回來,這樣就實現了插件 Activity 的啟動。思路是不是非常的簡單。
通過 動態代理
和 反射
實現 Hook。
查找Hook點的原則:
- 盡量靜態變量或者單例對象。
- 盡量 Hook public 的對象和方法。
從 HOOK 一直找到
靜態的變量
或者直接拿到類的對象
,才說明 HOOK 成功。
1.2、Activity啟動流程
首先我們來看一張 Activity 啟動流程的簡單示意圖,如下:
通過這張圖我們可以確定 Hook 點的大致位置。
- 1、在進入 AMS 之前,找到一個 Hook 點,用來將插件 Activity 替換為 ProxyActivity。
- 2、從 AMS 出來后,再找一個 Hook 點,用來將 ProxyActivity 替換為插件 Activity。
1.3、Activity啟動流程
詳情見:
Launch 啟動 Activity
Activity 啟動流程(一)
Activity 啟動流程(二)
二、HOOK 點的選擇
我們在項目中一般通過 startActivity(new Intent(this,PluginActivity.class));
啟動 PluginActivity,如果我想換成啟動 ProxyActivity,調用方法 startActivity(new Intent(this,ProxyActivity.class));
這樣就可以了。是不是已經知道答案了!!!沒錯,我們只要找到能夠修改 Intent
的地方,就可以作為 Hook 點,從這兒也可以看出 Hook 點并不是唯一的。
- 一般我們選擇
Intent
作為 HOOK 點。
2.1、進入 AMS 之前HOOK 點
API30 源碼
// android/app/Instrumentation.java
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// 這兒就是我們的 Hook 點,替換傳入 startActivity 方法中的 intent 參數
try {
intent.migrateExtraStreamToClipData(who);
intent.prepareToLeaveProcess(who);
int result = ActivityTaskManager.getService().startActivity(whoThread,
who.getBasePackageName(), who.getAttributionTag(), 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;
}
API 28 源碼
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManager.getService()
.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;
}
既然 Hook 點找到了,那我們怎么修改呢?
答案是動態代理,所以我們要生成一個代理對象,顯然,我們要代理的是 ActivityTaskManager.getService()
返回的對象。
API 30 源碼
// android/app/ActivityTaskManager.java
public static IActivityTaskManager getService() {
return IActivityTaskManagerSingleton.get();
}
@UnsupportedAppUsage(trackingBug = 129726065)
private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
new Singleton<IActivityTaskManager>() {
@Override
protected IActivityTaskManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
return IActivityTaskManager.Stub.asInterface(b);
}
};
API 28 源碼
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
桶過上面的代碼,我們知道 IActivityManager 是調用的 Singleton 里面的 get 方法,所以下面我們再看下Singleton 是怎么樣的。
// android/util/Singleton
public abstract class Singleton<T> {
@UnsupportedAppUsage
public Singleton() {
}
@UnsupportedAppUsage
private T mInstance;
protected abstract T create();
@UnsupportedAppUsage
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
可以看出,IActivityManagerSingleton.get() 返回的實際上就是 mInstance 對象。所以接下來我們要替換的就是這個對象。
2.2、從 AMS 出來后HOOK點
替換回去會調用 Handler 的 handleMessage方法,所以下面我們看下 Handler 的源碼。
public void handleMessage(Message msg) {
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
當 mCallback != null 時,首先會執行mCallback.handleMessage(msg),再執行 handleMessage(msg),所以我們可以將 mCallback 作為 Hook 點,創建它。ok,現在問題就只剩一個了,就是找到含有 intent 的對象,沒辦法,只能接著看源碼。
API 26 源碼
// android/app/ActivityThread.java
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
} break;
}
}
static final class ActivityClientRecord {
Intent intent;
}
可以看到,在 ActivityClientRecord 類中,剛好就有個 intent,而且這個類的對象,我們也可以獲取到,就是msg.obj。
API 30源碼
// android/app/ActivityThread.H.java
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
if (isSystem()) {
transaction.recycle();
}
break;
//android/app/servertransaction/ClientTransaction .java
public class ClientTransaction implements Parcelable, ObjectPoolItem {
/** A list of individual callbacks to a client. */
@UnsupportedAppUsage
private List<ClientTransactionItem> mActivityCallbacks;
}
//android/app/servertransaction/TransactionExecutor.java
public class TransactionExecutor {
public void execute(ClientTransaction transaction) {
executeCallbacks(transaction);
executeLifecycleState(transaction);
}
public void executeCallbacks(ClientTransaction transaction) {
final int size = callbacks.size();
for (int i = 0; i < size; ++i) {
final ClientTransactionItem item = callbacks.get(i);
if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
final int postExecutionState = item.getPostExecutionState();
final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
item.getPostExecutionState());
if (closestPreExecutionState != UNDEFINED) {
cycleToPath(r, closestPreExecutionState, transaction);
}
//HOOK 點
item.execute(mTransactionHandler, token, mPendingActions);
item.postExecute(mTransactionHandler, token, mPendingActions);
}
}
}
//android/app/servertransaction/LaunchActivityItem.java
public class LaunchActivityItem extends ClientTransactionItem {
@UnsupportedAppUsage
private Intent mIntent;
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
//HOOK 點
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mIsForward,
mProfilerInfo, client, mAssistToken, mFixedRotationAdjustments);
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
在LaunchActivityItem
中我們就找到Intent
了,可以在此利用 HOOK 技術進行替換。
創建 mCallback 替換系統的Intent,運用拿到 msg 從而可以拿到 msg.obj; 然后在替換 intent
--> ClientTransaction == msg.obj
--> private List<ClientTransactionItem> mActivityCallbacks;
--> ClientTransactionItem的子類
--> private Intent mIntent;
--> LaunchActivityItem 對象
--> private List<ClientTransactionItem> mActivityCallbacks;
--> ClientTransaction == msg.obj
三、源碼
- 核心代碼
public class HookUtil {
private static final String TARGET_INTENT = "target_intent";
public static void hookAMSAidl(){
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P){
hookIActivityTaskManager();
}else{
hookIActivityManager();
}
}
public static void hookIActivityTaskManager(){
try{
Field singletonField = null;
Class<?> actvityManager = Class.forName("android.app.ActivityTaskManager");
singletonField = actvityManager.getDeclaredField("IActivityTaskManagerSingleton");
singletonField.setAccessible(true);
Object singleton = singletonField.get(null);
//拿IActivityManager對象
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
//原始的IActivityTaskManager
final Object IActivityTaskManager = mInstanceField.get(singleton);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()
, new Class[]{Class.forName("android.app.IActivityTaskManager")}
, new InvocationHandler() {
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
// Log.i(TAG, "invoke: " + method.getName());
//偷梁換柱
//真正要啟動的activity目標
Intent raw = null;
int index = -1;
if ("startActivity".equals(method.getName())) {
for (int i = 0; i < args.length; i++) {
if(args[i] instanceof Intent){
raw = (Intent)args[i];
index = i;
}
}
//代替的Intent
Intent newIntent = new Intent();
newIntent.setComponent(new ComponentName("com.example.designdemo", StubActivity.class.getName()));
newIntent.putExtra(TARGET_INTENT,raw);
args[index] = newIntent;
}
return method.invoke(IActivityTaskManager, args);
}
});
// 7. IActivityManagerProxy 融入到framework
mInstanceField.set(singleton, proxy);
}catch (Exception e){
e.printStackTrace();
}
}
public static void hookIActivityManager() {
try {
// 獲取 singleton 對象
Field singletonField = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // 小于8.0
Class<?> clazz = Class.forName("android.app.ActivityManagerNative");
singletonField = clazz.getDeclaredField("gDefault");
} else {
Class<?> clazz = Class.forName("android.app.ActivityManager");
singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
}
singletonField.setAccessible(true);
Object singleton = singletonField.get(null);
// 獲取 系統的 IActivityManager 對象
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
final Object mInstance = mInstanceField.get(singleton);
Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
// 創建動態代理對象
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{iActivityManagerClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// do something
// Intent的修改 -- 過濾
/**
* IActivityManager類的方法
* startActivity(whoThread, who.getBasePackageName(), intent,
* intent.resolveTypeIfNeeded(who.getContentResolver()),
* token, target != null ? target.mEmbeddedID : null,
* requestCode, 0, null, options)
*/
// 過濾
if ("startActivity".equals(method.getName())) {
int index = -1;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
// 啟動插件的
Intent intent = (Intent) args[index];
Intent proxyIntent = new Intent();
proxyIntent.setComponent(new ComponentName("com.example.designdemo", StubActivity.class.getName()));
proxyIntent.putExtra(TARGET_INTENT, intent);
args[index] = proxyIntent;
}
// args method需要的參數 --- 不改變原有的執行流程
// mInstance 系統的 IActivityManager 對象
return method.invoke(mInstance, args);
}
});
// ActivityManager.getService() 替換成 proxyInstance
mInstanceField.set(singleton, proxyInstance);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void hookHandler() {
try {
// 獲取 ActivityThread 類的 Class 對象
Class<?> clazz = Class.forName("android.app.ActivityThread");
// 獲取 ActivityThread 對象
Field activityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
Object activityThread = activityThreadField.get(null);
// 獲取 mH 對象
Field mHField = clazz.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mH = (Handler) mHField.get(activityThread);
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
// 創建的 callback
Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
// 通過msg 可以拿到 Intent,可以換回執行插件的Intent
// 找到 Intent的方便替換的地方 --- 在這個類里面 ActivityClientRecord --- Intent intent 非靜態
switch (msg.what) {
case 100: //API 26 以下
try {
// msg.obj == ActivityClientRecord
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
// 啟動代理Intent
Intent proxyIntent = (Intent) intentField.get(msg.obj);
// 啟動插件的 Intent
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
if (intent != null) {
intentField.set(msg.obj, intent);
}
} catch (Exception e) {
e.printStackTrace();
}
break;
case 159: //API 30
try {
// msg.obj == ClientTransaction
// 獲取 mActivityCallbacks 對象
Field mActivityCallbacksField = msg.obj.getClass()
.getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
List mActivityCallbacks = (List) mActivityCallbacksField.get(msg.obj);
for (int i = 0; i < mActivityCallbacks.size(); i++) {
if (mActivityCallbacks.get(i).getClass().getName()
.equals("android.app.servertransaction.LaunchActivityItem")) {
Object launchActivityItem = mActivityCallbacks.get(i);
// 獲取啟動代理的 Intent
Field mIntentField = launchActivityItem.getClass()
.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
Intent proxyIntent = (Intent) mIntentField.get(launchActivityItem);
// 目標 intent 替換 proxyIntent
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
if (intent != null) {
mIntentField.set(launchActivityItem, intent);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
// 必須 return false
return false;
}
};
// 替換系統的 callBack
mCallbackField.set(mH, callback);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 初始化工具類
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LoadUtil.loadClass(this);
HookUtil.hookAMS();
HookUtil.hookHandler();
}
}
- 啟動Activity
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.enjoy.plugin",
"com.enjoy.plugin.MainActivity"));
startActivity(intent);