日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android Hook Activity 的几种姿势

發布時間:2023/12/29 Android 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android Hook Activity 的几种姿势 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這篇博客已 API 27 的源碼為基礎分析

前言

在上一篇文章 Android Hook 機制之簡單實戰 中,我們介紹了 Hook 的要點

  • Hook 的選擇點:靜態變量和單例,因為一旦創建對象,它們不容易變化,非常容易定位。

  • Hook 過程:

    • 尋找 Hook 點,原則是靜態變量或者單例對象,盡量 Hook public 的對象和方法。
    • 選擇合適的代理方式,如果是接口可以用動態代理。
    • 偷梁換柱——用代理對象替換原始對象。
  • Android 的 API 版本比較多,方法和類可能不一樣,所以要做好 API 的兼容工作。

今天這邊文章主要講解一下問題

  • hook activity 的三種方法
  • 怎樣啟動沒有在 AndroidManifest 聲明的 activity(完美兼容 Android O,AppCompactActivity)

hook activity 的幾種方法

我們知道啟動 Activity 主要有兩種方式:

  • Activity startActivity
  • getApplicationContext startActivity

hook Activivity 的第一種方法

我們先來看一下 Activity startActivity 方法的調用流程。

@Override public void startActivity(Intent intent, @Nullable Bundle options) {if (options != null) {startActivityForResult(intent, -1, options);} else {// Note we want to go through this call for compatibility with// applications that may have overridden the method.startActivityForResult(intent, -1);} } public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {startActivityForResult(intent, requestCode, null); }public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {if (mParent == null) {options = transferSpringboardActivityOptions(options);Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);if (ar != null) {mMainThread.sendActivityResult(mToken, mEmbeddedID, requestCode, ar.getResultCode(),ar.getResultData());}if (requestCode >= 0) {// If this start is requesting a result, we can avoid making// the activity visible until the result is received. Setting// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the// activity hidden during this time, to avoid flickering.// This can only be done when a result is requested because// that guarantees we will get information back when the// activity is finished, no matter what happens to it.mStartedActivity = true;}cancelInputsAndStartExitTransition(options);// TODO Consider clearing/flushing other event sources and events for child windows.} else {if (options != null) {mParent.startActivityFromChild(this, intent, requestCode, options);} else {// Note we want to go through this method for compatibility with// existing applications that may have overridden it.mParent.startActivityFromChild(this, intent, requestCode);}} }

首先,我們先來看一下 startActivityForResult 方法,當 mParent 為 null 的時候,會調用到 mInstrumentation.execStartActivity 方法。當 mParent 不為 null 時,都會調用到 mParent.startActivityFromChild 方法。而 mParent 為 Activity 實例,接下來我們一起看一下 startActivityFromChild 方法。

public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,int requestCode, @Nullable Bundle options) {options = transferSpringboardActivityOptions(options);Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, child,intent, requestCode, options);if (ar != null) {mMainThread.sendActivityResult(mToken, child.mEmbeddedID, requestCode,ar.getResultCode(), ar.getResultData());}cancelInputsAndStartExitTransition(options); }

可以看到 startActivityFromChild 中也會調用 mInstrumentation.execStartActivity 方法。因此,即我們通過 Activity startActivity 的方法啟動 activity,最終都會調用到 mInstrumentation.execStartActivity 方法。因此,如果我們想要攔截的話,可以 hook 住 mInstrumentation。

由于 mInstrumentation 是類,不是 interface,不能使用動態代理的方式,因此,這里我們使用靜態代理的方式。

下面讓我們一起看一下 怎樣 hook activity 的 mInstrumentation

  • 第一步:拿到當前 activity 的 mInstrumentation
  • 第二步:創建代理對象
  • 第三步:將我們的代理替換原 activity 的 mInstrumentation
public static void replaceInstrumentation(Activity activity) throws Exception {Class<?> k = Activity.class;//通過Activity.class 拿到 mInstrumentation字段Field field = k.getDeclaredField("mInstrumentation");field.setAccessible(true);//根據activity內mInstrumentation字段 獲取Instrumentation對象Instrumentation instrumentation = (Instrumentation) field.get(activity);//創建代理對象Instrumentation instrumentationProxy = new ActivityProxyInstrumentation(instrumentation);//進行替換field.set(activity, instrumentationProxy); } public class ActivityProxyInstrumentation extends Instrumentation {private static final String TAG = "ActivityProxyInstrumentation";// ActivityThread中原始的對象, 保存起來Instrumentation mBase;public ActivityProxyInstrumentation(Instrumentation base) {mBase = base;}public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {// Hook之前, 可以輸出你想要的!Log.d(TAG,"xxxx: 執行了startActivity, 參數如下: " + "who = [" + who + "], " +"contextThread = [" + contextThread + "], token = [" + token + "], " +"target = [" + target + "], intent = [" + intent +"], requestCode = [" + requestCode + "], options = [" + options + "]");// 開始調用原始的方法, 調不調用隨你,但是不調用的話, 所有的startActivity都失效了.// 由于這個方法是隱藏的,因此需要使用反射調用;首先找到這個方法try {Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActivity",Context.class, IBinder.class, IBinder.class, Activity.class,Intent.class, int.class, Bundle.class);execStartActivity.setAccessible(true);return (ActivityResult) execStartActivity.invoke(mBase, who,contextThread, token, target, intent, requestCode, options);} catch (Exception e) {// rom修改了 需要手動適配throw new RuntimeException("do not support!!! pls adapt it");}}}

在 ActivityProxyInstrumentation 里面,我們打印相應的 log。

運行以下測試代碼

try {HookHelper.replaceInstrumentation(this); } catch (Exception e) {e.printStackTrace(); } startActivity(new Intent(this,TestActivityStart.class));

將會看到輸出以下 log

hook activity 的第二種方法

我們先來看一下 getApplicationContext startActivity 的調用關系

因此,這里我們要 hook 的是 ActivityThread 的 mInstrumentation

public static void attachContext() throws Exception {Log.i(TAG, "attachContext: ");// 先獲取到當前的ActivityThread對象Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");currentActivityThreadMethod.setAccessible(true);//currentActivityThread是一個static函數所以可以直接invoke,不需要帶實例參數Object currentActivityThread = currentActivityThreadMethod.invoke(null);// 拿到原始的 mInstrumentation字段Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");mInstrumentationField.setAccessible(true);Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);// 創建代理對象Instrumentation evilInstrumentation = new ApplicationInstrumentation(mInstrumentation);// 偷梁換柱mInstrumentationField.set(currentActivityThread, evilInstrumentation); } public class ApplicationInstrumentation extends Instrumentation {private static final String TAG = "ApplicationInstrumentation";// ActivityThread中原始的對象, 保存起來Instrumentation mBase;public ApplicationInstrumentation(Instrumentation base) {mBase = base;}public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {// Hook之前, 可以輸出你想要的!Log.d(TAG, "xxxx: 執行了startActivity, 參數如下: " + "who = [" + who + "], " + "contextThread = " +"" + "" + "[" + contextThread + "], token = [" + token + "], " + "target = [" + target + "], intent = [" + intent + "], requestCode = [" + requestCode + "], " +"options = " + "[" + options + "]");// 開始調用原始的方法, 調不調用隨你,但是不調用的話, 所有的startActivity都失效了.// 由于這個方法是隱藏的,因此需要使用反射調用;首先找到這個方法try {Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);execStartActivity.setAccessible(true);return (ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token, target, intent, requestCode, options);} catch (Exception e) {// rom修改了 需要手動適配throw new RuntimeException("do not support!!! pls adapt it");}}}

可以看到在 ApplicationInstrumentation 里面,我們只是打印出 startActivity 中各個方法參數的值。

運行以下測試代碼

try {HookHelper.attachContext(); } catch (Exception e) {e.printStackTrace(); } Intent intent = new Intent(this, TestActivityStart.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getApplicationContext().startActivity(intent);

將看到以下 log


Hook AMS

上面 hook activity 的兩種方法其實都有一定缺陷,比如,第一種方法,只能 hook 住通過 Activity startActivity 的 activity。第二種方法,只能 hook 住通過 getApplicationContext().startActivity 啟動的 activity。那有沒有一種方法能 hook 上述兩種的,其實是有的,那就是 hook AMS。下面讓我們一起來看一下。

上面 hook startActivity 其實都是 hook 相應的 mInstrumentation.execStartActivity 方法,因此,我們可以從這里下手,看 mInstrumentation.execStartActivity 里面有沒有一些共性的東西,可以 hook。

我們先來 mInstrumentation.execStartActivity 方法

public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {IApplicationThread whoThread = (IApplicationThread) contextThread;Uri referrer = target != null ? target.onProvideReferrer() : null;if (referrer != null) {intent.putExtra(Intent.EXTRA_REFERRER, referrer);}if (mActivityMonitors != null) {synchronized (mSync) {final int N = mActivityMonitors.size();for (int i=0; i<N; i++) {final ActivityMonitor am = mActivityMonitors.get(i);ActivityResult result = null;if (am.ignoreMatchingSpecificIntents()) {result = am.onStartActivity(intent);}if (result != null) {am.mHits++;return result;} else if (am.match(who, null, intent)) {am.mHits++;if (am.isBlocking()) {return requestCode >= 0 ? am.getResult() : null;}break;}}}}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; }

這里我們留意 ActivityManager.getService().startActivity 這個方法

public static IActivityManager getService() {return IActivityManagerSingleton.get(); }private static final Singleton<IActivityManager> IActivityManagerSingleton =new Singleton<IActivityManager>() {@Overrideprotected IActivityManager create() {final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);final IActivityManager am = IActivityManager.Stub.asInterface(b);return am;}};

可以看到 IActivityManagerSingleton 是一個單例對象,因此,我們可以 hook 它。

public static void hookAMSAfter26() throws Exception {// 第一步:獲取 IActivityManagerSingletonClass<?> aClass = Class.forName("android.app.ActivityManager");Field declaredField = aClass.getDeclaredField("IActivityManagerSingleton");declaredField.setAccessible(true);Object value = declaredField.get(null);Class<?> singletonClz = Class.forName("android.util.Singleton");Field instanceField = singletonClz.getDeclaredField("mInstance");instanceField.setAccessible(true);Object iActivityManagerObject = instanceField.get(value);// 第二步:獲取我們的代理對象,這里因為 IActivityManager 是接口,我們使用動態代理的方式Class<?> iActivity = Class.forName("android.app.IActivityManager");InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), newClass<?>[]{iActivity}, handler);// 第三步:偷梁換柱,將我們的 proxy 替換原來的對象instanceField.set(value, proxy);} public class AMSInvocationHandler implements InvocationHandler {private static final String TAG = "AMSInvocationHandler";Object iamObject;public AMSInvocationHandler(Object iamObject) {this.iamObject = iamObject;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// Log.e(TAG, method.getName());if ("startActivity".equals(method.getName())) {Log.i(TAG, "ready to startActivity");for (Object object : args) {Log.d(TAG, "invoke: object=" + object);}}return method.invoke(iamObject, args);} }

執行以下測試代碼

try {HookHelper.hookAMS(); } catch (Exception e) {e.printStackTrace(); } startActivity(new Intent(this,TestActivityStart.class));

將會看到以下 log

I/AMSInvocationHandler: ready to startActivity

接下來我們一起來看一下 API 25 Instrumentation 的代碼(自 API 26 開始 ,Instrumentation execStartActivity 方法有所改變)

public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {IApplicationThread whoThread = (IApplicationThread) contextThread;Uri referrer = target != null ? target.onProvideReferrer() : null;if (referrer != null) {intent.putExtra(Intent.EXTRA_REFERRER, referrer);}if (mActivityMonitors != null) {synchronized (mSync) {final int N = mActivityMonitors.size();for (int i=0; i<N; i++) {final ActivityMonitor am = mActivityMonitors.get(i);if (am.match(who, null, intent)) {am.mHits++;if (am.isBlocking()) {return requestCode >= 0 ? am.getResult() : null;}break;}}}}try {intent.migrateExtraStreamToClipData();intent.prepareToLeaveProcess(who);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; }

可以看到這里啟動 activity 是調用 ActivityManagerNative.getDefault().startActivity 啟動的。

public abstract class ActivityManagerNative extends Binder implements IActivityManager {/*** Retrieve the system's default/global activity manager.*/static public IActivityManager getDefault() {return gDefault.get();}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;}};}

同理我們看到 ActivityManagerNative 的 gDefault 是一個靜態變量,因此,我們可以嘗試 hook gDefault.

public static void hookAmsBefore26() throws Exception {// 第一步:獲取 IActivityManagerSingletonClass<?> forName = Class.forName("android.app.ActivityManagerNative");Field defaultField = forName.getDeclaredField("gDefault");defaultField.setAccessible(true);Object defaultValue = defaultField.get(null);Class<?> forName2 = Class.forName("android.util.Singleton");Field instanceField = forName2.getDeclaredField("mInstance");instanceField.setAccessible(true);Object iActivityManagerObject = instanceField.get(defaultValue);// 第二步:獲取我們的代理對象,這里因為 IActivityManager 是接口,我們使用動態代理的方式Class<?> iActivity = Class.forName("android.app.IActivityManager");InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{iActivity}, handler);// 第三步:偷梁換柱,將我們的 proxy 替換原來的對象instanceField.set(defaultValue, proxy); }

到此,hook Activity 的三種方式已講解完畢


啟動一個沒有在 AndroidManifest 聲明的 Activity

我們知道,當我們啟動一個沒有在 AndroidManifest 中聲明的 activity,會拋出 ActivityNotFoundException 異常。

Caused by: android.content.ActivityNotFoundException: Unable to find explicit activity class {com.xj.hookdemo/com.xj.hookdemo.activityhook.TargetAppCompatActivity}; have you declared this activity in your AndroidManifest.xml?

at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2124) at android.app.Instrumentation.execStartActivity(Instrumentation.java:1802)at android.app.Activity.startActivityForResult(Activity.java:4514)at android.support.v4.app.BaseFragmentActivityApi16.startActivityForResult(BaseFragmentActivityApi16.java:54)at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:67)at android.app.Activity.startActivityForResult(Activity.java:4472)at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:720)at android.app.Activity.startActivity(Activity.java:4833)at android.app.Activity.startActivity(Activity.java:4801)at com.xj.hookdemo.activityhook.TestStartActivityNoRegister.onB

從報錯的堆棧中,我們非常定位到 Instrumentation.execStartActivity 方法

public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String resultWho,Intent intent, int requestCode, Bundle options, UserHandle user) {----- // 省略若干代碼try {intent.migrateExtraStreamToClipData();intent.prepareToLeaveProcess(who);int result = ActivityManager.getService().startActivityAsUser(whoThread, who.getBasePackageName(), intent,intent.resolveTypeIfNeeded(who.getContentResolver()),token, resultWho,requestCode, 0, null, options, user.getIdentifier());checkStartActivityResult(result, intent);} catch (RemoteException e) {throw new RuntimeException("Failure from system", e);}return null; }

在該方法中,調用 startActivityAsUser 方法通過傳入的 intent 獲取 result,再通過 checkStartActivityResult 方法,判斷 result 是否合法。

而我們知道我們啟動的 activity 信息都儲存在 intent 中,那么我們若想要 啟動一個沒有在 AndroidManifest 聲明的 Activity,那我們只需要在 某個時機,即調用 startActivity 方法之前欺騙 AMS 我們的 activity 已經注冊(即替換 intent),這樣就不會拋出 ActivityNotFoundException 異常。

在前面的時候,我們已經講解到如何 hook ams,這里我們不再具體講述,主要步驟如下

  • 第一步, API 26 以后,hook android.app.ActivityManager.IActivityManagerSingleton, API 25 以前,hook android.app.ActivityManagerNative.gDefault
  • 第二步,獲取我們的代理對象,這里因為是接口,所以我們使用動態代理的方式
  • 第三步:設置為我們的代理對象
private static void hookAMS(Context context) throws ClassNotFoundException,NoSuchFieldException, IllegalAccessException {// 第一步, API 26 以后,hook android.app.ActivityManager.IActivityManagerSingleton,// API 25 以前,hook android.app.ActivityManagerNative.gDefaultField gDefaultField = null;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {Class<?> activityManager = Class.forName("android.app.ActivityManager");gDefaultField = activityManager.getDeclaredField("IActivityManagerSingleton");} else {Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");}gDefaultField.setAccessible(true);Object gDefaultObj = gDefaultField.get(null); //所有靜態對象的反射可以通過傳null獲取。如果是實列必須傳實例Class<?> singletonClazz = Class.forName("android.util.Singleton");Field amsField = singletonClazz.getDeclaredField("mInstance");amsField.setAccessible(true);Object amsObj = amsField.get(gDefaultObj);//String pmName = getPMName(context);String hostClzName = getHostClzName(context, pmName);// 第二步,獲取我們的代理對象,這里因為是接口,所以我們使用動態代理的方式amsObj = Proxy.newProxyInstance(context.getClass().getClassLoader(), amsObj.getClass().getInterfaces(), new AMSHookInvocationHandler(amsObj, pmName, hostClzName));// 第三步:設置為我們的代理對象amsField.set(gDefaultObj, amsObj); }

接著,我們在動態代理對象中,當調用 startActivity 方法的時候,我們把 intent 信息替換,校驗的時候就可以繞過系統對 activity 的校驗,這樣就不會跑出 ActivityNotFoundException 異常。

public class AMSHookInvocationHandler implements InvocationHandler {public static final String ORIGINALLY_INTENT = "originallyIntent";private Object mAmsObj;private String mPackageName;private String cls;public AMSHookInvocationHandler(Object amsObj, String packageName, String cls) {this.mAmsObj = amsObj;this.mPackageName = packageName;this.cls = cls;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 對 startActivity進行Hookif (method.getName().equals("startActivity")) {int index = 0;// 找到我們啟動時的intentfor (int i = 0; i < args.length; i++) {if (args[i] instanceof Intent) {index = i;break;}}// 取出在真實的IntentIntent originallyIntent = (Intent) args[index];Log.i("AMSHookUtil", "AMSHookInvocationHandler:" + originallyIntent.getComponent().getClassName());// 自己偽造一個配置文件已注冊過的Activity IntentIntent proxyIntent = new Intent();// 因為我們調用的Activity沒有注冊,所以這里我們先偷偷換成已注冊。使用一個假的IntentComponentName componentName = new ComponentName(mPackageName, cls);proxyIntent.setComponent(componentName);// 在這里把未注冊的Intent先存起來 一會兒我們需要在Handle里取出來用proxyIntent.putExtra(ORIGINALLY_INTENT, originallyIntent);args[index] = proxyIntent;}return method.invoke(mAmsObj, args);} }

但是,如果僅僅這樣做,會存在一個問題,因為 intent 信息在校驗的時候被我們替換了,但是我們并沒有將其還原,這樣,啟動的 activity 就不是我們想要的 activity。

那么,我們要在哪個實際將 intent 信息還原呢?

我們回過頭再來看一下 Activity 的 startActivityForResult 方法

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {if (mParent == null) {options = transferSpringboardActivityOptions(options);Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);if (ar != null) {mMainThread.sendActivityResult(mToken, mEmbeddedID, requestCode, ar.getResultCode(),ar.getResultData());}if (requestCode >= 0) {// If this start is requesting a result, we can avoid making// the activity visible until the result is received. Setting// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the// activity hidden during this time, to avoid flickering.// This can only be done when a result is requested because// that guarantees we will get information back when the// activity is finished, no matter what happens to it.mStartedActivity = true;}cancelInputsAndStartExitTransition(options);// TODO Consider clearing/flushing other event sources and events for child windows.} else {if (options != null) {mParent.startActivityFromChild(this, intent, requestCode, options);} else {// Note we want to go through this method for compatibility with// existing applications that may have overridden it.mParent.startActivityFromChild(this, intent, requestCode);}} }

該方法主要分為兩個邏輯,當 mParent 為空的時候即不為空的時候

  • 第一種情況,mParent 不為空的時候,調用到 mInstrumentation.execStartActivity 方法之后,會調用 mMainThread.sendActivityResult 方法
  • 第二種情況,當 mParent 為空的時候,會調用 mParent.startActivityFromChild
public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,int requestCode, @Nullable Bundle options) {options = transferSpringboardActivityOptions(options);Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, child,intent, requestCode, options);if (ar != null) {mMainThread.sendActivityResult(mToken, child.mEmbeddedID, requestCode,ar.getResultCode(), ar.getResultData());}cancelInputsAndStartExitTransition(options); }

在 startActivityFromChild 方法里面,又會調用到 mMainThread.sendActivityResult 方法。因此,我們只需看一下該方法是怎樣 send ActivityResult 的。

public final class ActivityThread {---public final void sendActivityResult(IBinder token, String id, int requestCode,int resultCode, Intent data) {if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id+ " req=" + requestCode + " res=" + resultCode + " data=" + data);ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();list.add(new ResultInfo(id, requestCode, resultCode, data));mAppThread.scheduleSendResult(token, list);}public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {ResultData res = new ResultData();res.token = token;res.results = results;sendMessage(H.SEND_RESULT, res);}private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {if (DEBUG_MESSAGES) Slog.v(TAG, "SCHEDULE " + what + " " + mH.codeToString(what)+ ": " + arg1 + " / " + obj);Message msg = Message.obtain();msg.what = what;msg.obj = obj;msg.arg1 = arg1;msg.arg2 = arg2;if (async) {msg.setAsynchronous(true);}mH.sendMessage(msg);}final H mH = new H(); }

跟蹤 ActivityThread 的代碼發現 sendActivityResult 方法會調用 scheduleSendResult 方法發送,而 scheduleSendResult 方法又會調用 sendMessage 方法,在 sendMessage 方法里面,會調用 mH 發送消息(即 Handler)

因此,我們只需要在回調 H 的 handleMessage 消息之前還原我們的 intent 信息即可。

private class H extends Handler {public void handleMessage(Message msg) {if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));switch (msg.what) {case LAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");final ActivityClientRecord r = (ActivityClientRecord) msg.obj;r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;}

ok,這里我們重新理一下 Activity 大概的啟動流程:

app 調用 startActivity 方法 -> Instrumentation 類通過 ActivityManagerNative 或者 ActivityManager( API 26以后)將啟動請求發送給 AMS -> AMS 進行一系列檢查并將此請求通過 Binder 派發給所屬 app -> app 通過 Binder 收到這個啟動請求 -> ActivityThread 中的實現將收到的請求進行封裝后送入 Handler -> 從 Handler 中取出這個消息,開始 app 本地的 Activity 初始化和啟動邏輯。

hook ActivityThread 的 mH

/**** @param context* @param isAppCompatActivity 表示是否是 AppCompatActivity* @throws Exception*/ private static void hookLaunchActivity(Context context, boolean isAppCompatActivity) throwsException {Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");sCurrentActivityThreadField.setAccessible(true);Object sCurrentActivityThreadObj = sCurrentActivityThreadField.get(null);Field mHField = activityThreadClazz.getDeclaredField("mH");mHField.setAccessible(true);Handler mH = (Handler) mHField.get(sCurrentActivityThreadObj);Field callBackField = Handler.class.getDeclaredField("mCallback");callBackField.setAccessible(true);callBackField.set(mH, new ActivityThreadHandlerCallBack(context, isAppCompatActivity)); }public static class ActivityThreadHandlerCallBack implements Handler.Callback {private final boolean mIsAppCompatActivity;private final Context mContext;public ActivityThreadHandlerCallBack(Context context, boolean isAppCompatActivity) {mIsAppCompatActivity = isAppCompatActivity;mContext = context;}@Overridepublic boolean handleMessage(Message msg) {int LAUNCH_ACTIVITY = 0;try {Class<?> clazz = Class.forName("android.app.ActivityThread$H");Field field = clazz.getField("LAUNCH_ACTIVITY");LAUNCH_ACTIVITY = field.getInt(null);} catch (Exception e) {}if (msg.what == LAUNCH_ACTIVITY) {handleLaunchActivity(mContext, msg, mIsAppCompatActivity);}return false;} }private static void handleLaunchActivity(Context context, Message msg, booleanisAppCompatActivity) {try {Object obj = msg.obj;Field intentField = obj.getClass().getDeclaredField("intent");intentField.setAccessible(true);Intent proxyIntent = (Intent) intentField.get(obj);//拿到之前真實要被啟動的Intent 然后把Intent換掉Intent originallyIntent = proxyIntent.getParcelableExtra(ORIGINALLY_INTENT);if (originallyIntent == null) {return;}proxyIntent.setComponent(originallyIntent.getComponent());Log.e(TAG, "handleLaunchActivity:" + originallyIntent.getComponent().getClassName());// 如果不需要兼容 AppCompatActivityif (!isAppCompatActivity) {return;}//兼容AppCompatActivity,假如不加上該方法,當 activity instanceOf AppCompatActivity 時,會拋出 PackageManager$NameNotFoundException 異常。hookPM(context);} catch (Exception e) {e.printStackTrace();} }

運行以上代碼,當你啟動一個沒有在 AndroidManifest 注冊的 Activity,你會發現是可以正常啟動的。但是,當未注冊的 Activity 是 AppCompatActivity 的子類的時候,會拋出以下異常

Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{com.xj.hookdemo/com.xj.hookdemo.activityhook.TargetAppCompatActivity}
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:215)
at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155)
at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:59)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
at com.xj.hookdemo.activityhook.TargetAppCompatActivity.onCreate(TargetAppCompatActivity.java:12)
at android.app.Activity.performCreate(Activity.java:7026)
at android.app.Activity.performCreate(Activity.java:7017)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1231)

從上面的異常信息來看,主要是在 NavUtils.getParentActivityName 方法中拋出異常。

@Nullable public static String getParentActivityName(Context context, ComponentName componentName)throws NameNotFoundException {PackageManager pm = context.getPackageManager();ActivityInfo info = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);if (Build.VERSION.SDK_INT >= 16) {String result = info.parentActivityName;if (result != null) {return result;}}if (info.metaData == null) {return null;}String parentActivity = info.metaData.getString(PARENT_ACTIVITY);if (parentActivity == null) {return null;}if (parentActivity.charAt(0) == '.') {parentActivity = context.getPackageName() + parentActivity;}return parentActivity; }

而該方法中,是調用 PackageManager getActivityInfo 中去查詢。因此,我們只需要 hook PackageManager

private static void hookPM(Context context) throws ClassNotFoundException,NoSuchFieldException, IllegalAccessException, NoSuchMethodException,InvocationTargetException {String pmName = getPMName(context);String hostClzName = getHostClzName(context, pmName);Class<?> forName = Class.forName("android.app.ActivityThread");Field field = forName.getDeclaredField("sCurrentActivityThread");field.setAccessible(true);Object activityThread = field.get(null);Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager");Object iPackageManager = getPackageManager.invoke(activityThread);PackageManagerHandler handler = new PackageManagerHandler(iPackageManager, pmName, hostClzName);Class<?> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), newClass<?>[]{iPackageManagerIntercept}, handler);// 獲取 sPackageManager 屬性Field iPackageManagerField = activityThread.getClass().getDeclaredField("sPackageManager");iPackageManagerField.setAccessible(true);iPackageManagerField.set(activityThread, proxy); }

運行以上代碼,可以看到我們可以正常啟動沒有在 AndroidManifest 的 activity。已經完美兼容 Android 8.0,AppCompactActivity。

小結

啟動沒有在 AndroidManifest 注冊的 Activity 課改可以分為連個步驟

  • 在 AMS 通過 intent 校驗 activity 是否注冊的時候,用已經在 AndroidManifet 注冊的 Activity 欺騙 AMS,繞過 原有 activity 的校驗,并將原有的 intent 信息儲存起來
  • 在 AMS 校驗完畢的時候,通過 binder 告知我們的應用啟動相應 activity 的時候,我們將 intent 的信息取出來,還原。

HookDemo

Android Hook 機制之簡單實戰

Android Hook Activity 的幾種姿勢

推薦閱讀

Android 面試必備 - http 與 https 協議

Android 面試必備 - 計算機網絡基本知識(TCP,UDP,Http,https)

Android 面試必備 - 線程

賣一下廣告,歡迎大家關注我的微信公眾號,掃一掃下方二維碼或搜索微信號 stormjun94,即可關注。 目前專注于 Android 開發,主要分享 Android開發相關知識和一些相關的優秀文章,包括個人總結,職場經驗等。

總結

以上是生活随笔為你收集整理的Android Hook Activity 的几种姿势的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。