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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

Android Hook Activity 的几种姿势

發(fā)布時(shí)間:2023/12/29 Android 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android Hook Activity 的几种姿势 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

這篇博客已 API 27 的源碼為基礎(chǔ)分析

前言

在上一篇文章 Android Hook 機(jī)制之簡(jiǎn)單實(shí)戰(zhàn) 中,我們介紹了 Hook 的要點(diǎn)

  • Hook 的選擇點(diǎn):靜態(tài)變量和單例,因?yàn)橐坏﹦?chuàng)建對(duì)象,它們不容易變化,非常容易定位。

  • Hook 過(guò)程:

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

今天這邊文章主要講解一下問(wèn)題

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

hook activity 的幾種方法

我們知道啟動(dòng) Activity 主要有兩種方式:

  • Activity startActivity
  • getApplicationContext startActivity

hook Activivity 的第一種方法

我們先來(lái)看一下 Activity startActivity 方法的調(diào)用流程。

@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);}} }

首先,我們先來(lái)看一下 startActivityForResult 方法,當(dāng) mParent 為 null 的時(shí)候,會(huì)調(diào)用到 mInstrumentation.execStartActivity 方法。當(dāng) mParent 不為 null 時(shí),都會(huì)調(diào)用到 mParent.startActivityFromChild 方法。而 mParent 為 Activity 實(shí)例,接下來(lái)我們一起看一下 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 中也會(huì)調(diào)用 mInstrumentation.execStartActivity 方法。因此,即我們通過(guò) Activity startActivity 的方法啟動(dòng) activity,最終都會(huì)調(diào)用到 mInstrumentation.execStartActivity 方法。因此,如果我們想要攔截的話,可以 hook 住 mInstrumentation。

由于 mInstrumentation 是類,不是 interface,不能使用動(dòng)態(tài)代理的方式,因此,這里我們使用靜態(tài)代理的方式。

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

  • 第一步:拿到當(dāng)前 activity 的 mInstrumentation
  • 第二步:創(chuàng)建代理對(duì)象
  • 第三步:將我們的代理替換原 activity 的 mInstrumentation
public static void replaceInstrumentation(Activity activity) throws Exception {Class<?> k = Activity.class;//通過(guò)Activity.class 拿到 mInstrumentation字段Field field = k.getDeclaredField("mInstrumentation");field.setAccessible(true);//根據(jù)activity內(nèi)mInstrumentation字段 獲取Instrumentation對(duì)象Instrumentation instrumentation = (Instrumentation) field.get(activity);//創(chuàng)建代理對(duì)象Instrumentation instrumentationProxy = new ActivityProxyInstrumentation(instrumentation);//進(jìn)行替換field.set(activity, instrumentationProxy); } public class ActivityProxyInstrumentation extends Instrumentation {private static final String TAG = "ActivityProxyInstrumentation";// ActivityThread中原始的對(duì)象, 保存起來(lái)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: 執(zhí)行了startActivity, 參數(shù)如下: " + "who = [" + who + "], " +"contextThread = [" + contextThread + "], token = [" + token + "], " +"target = [" + target + "], intent = [" + intent +"], requestCode = [" + requestCode + "], options = [" + options + "]");// 開始調(diào)用原始的方法, 調(diào)不調(diào)用隨你,但是不調(diào)用的話, 所有的startActivity都失效了.// 由于這個(gè)方法是隱藏的,因此需要使用反射調(diào)用;首先找到這個(gè)方法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修改了 需要手動(dòng)適配throw new RuntimeException("do not support!!! pls adapt it");}}}

在 ActivityProxyInstrumentation 里面,我們打印相應(yīng)的 log。

運(yùn)行以下測(cè)試代碼

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

將會(huì)看到輸出以下 log

hook activity 的第二種方法

我們先來(lái)看一下 getApplicationContext startActivity 的調(diào)用關(guān)系

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

public static void attachContext() throws Exception {Log.i(TAG, "attachContext: ");// 先獲取到當(dāng)前的ActivityThread對(duì)象Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");currentActivityThreadMethod.setAccessible(true);//currentActivityThread是一個(gè)static函數(shù)所以可以直接invoke,不需要帶實(shí)例參數(shù)Object currentActivityThread = currentActivityThreadMethod.invoke(null);// 拿到原始的 mInstrumentation字段Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");mInstrumentationField.setAccessible(true);Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);// 創(chuàng)建代理對(duì)象Instrumentation evilInstrumentation = new ApplicationInstrumentation(mInstrumentation);// 偷梁換柱mInstrumentationField.set(currentActivityThread, evilInstrumentation); } public class ApplicationInstrumentation extends Instrumentation {private static final String TAG = "ApplicationInstrumentation";// ActivityThread中原始的對(duì)象, 保存起來(lái)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: 執(zhí)行了startActivity, 參數(shù)如下: " + "who = [" + who + "], " + "contextThread = " +"" + "" + "[" + contextThread + "], token = [" + token + "], " + "target = [" + target + "], intent = [" + intent + "], requestCode = [" + requestCode + "], " +"options = " + "[" + options + "]");// 開始調(diào)用原始的方法, 調(diào)不調(diào)用隨你,但是不調(diào)用的話, 所有的startActivity都失效了.// 由于這個(gè)方法是隱藏的,因此需要使用反射調(diào)用;首先找到這個(gè)方法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修改了 需要手動(dòng)適配throw new RuntimeException("do not support!!! pls adapt it");}}}

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

運(yùn)行以下測(cè)試代碼

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 的兩種方法其實(shí)都有一定缺陷,比如,第一種方法,只能 hook 住通過(guò) Activity startActivity 的 activity。第二種方法,只能 hook 住通過(guò) getApplicationContext().startActivity 啟動(dòng)的 activity。那有沒(méi)有一種方法能 hook 上述兩種的,其實(shí)是有的,那就是 hook AMS。下面讓我們一起來(lái)看一下。

上面 hook startActivity 其實(shí)都是 hook 相應(yīng)的 mInstrumentation.execStartActivity 方法,因此,我們可以從這里下手,看 mInstrumentation.execStartActivity 里面有沒(méi)有一些共性的東西,可以 hook。

我們先來(lái) 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 這個(gè)方法

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 是一個(gè)單例對(duì)象,因此,我們可以 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);// 第二步:獲取我們的代理對(duì)象,這里因?yàn)?IActivityManager 是接口,我們使用動(dòng)態(tài)代理的方式Class<?> iActivity = Class.forName("android.app.IActivityManager");InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), newClass<?>[]{iActivity}, handler);// 第三步:偷梁換柱,將我們的 proxy 替換原來(lái)的對(duì)象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);} }

執(zhí)行以下測(cè)試代碼

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

將會(huì)看到以下 log

I/AMSInvocationHandler: ready to startActivity

接下來(lái)我們一起來(lái)看一下 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; }

可以看到這里啟動(dòng) activity 是調(diào)用 ActivityManagerNative.getDefault().startActivity 啟動(dòng)的。

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 是一個(gè)靜態(tài)變量,因此,我們可以嘗試 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);// 第二步:獲取我們的代理對(duì)象,這里因?yàn)?IActivityManager 是接口,我們使用動(dòng)態(tài)代理的方式Class<?> iActivity = Class.forName("android.app.IActivityManager");InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{iActivity}, handler);// 第三步:偷梁換柱,將我們的 proxy 替換原來(lái)的對(duì)象instanceField.set(defaultValue, proxy); }

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


啟動(dòng)一個(gè)沒(méi)有在 AndroidManifest 聲明的 Activity

我們知道,當(dāng)我們啟動(dòng)一個(gè)沒(méi)有在 AndroidManifest 中聲明的 activity,會(huì)拋出 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

從報(bào)錯(cuò)的堆棧中,我們非常定位到 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; }

在該方法中,調(diào)用 startActivityAsUser 方法通過(guò)傳入的 intent 獲取 result,再通過(guò) checkStartActivityResult 方法,判斷 result 是否合法。

而我們知道我們啟動(dòng)的 activity 信息都儲(chǔ)存在 intent 中,那么我們?nèi)粝胍?啟動(dòng)一個(gè)沒(méi)有在 AndroidManifest 聲明的 Activity,那我們只需要在 某個(gè)時(shí)機(jī),即調(diào)用 startActivity 方法之前欺騙 AMS 我們的 activity 已經(jīng)注冊(cè)(即替換 intent),這樣就不會(huì)拋出 ActivityNotFoundException 異常。

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

  • 第一步, API 26 以后,hook android.app.ActivityManager.IActivityManagerSingleton, API 25 以前,hook android.app.ActivityManagerNative.gDefault
  • 第二步,獲取我們的代理對(duì)象,這里因?yàn)槭墙涌?#xff0c;所以我們使用動(dòng)態(tài)代理的方式
  • 第三步:設(shè)置為我們的代理對(duì)象
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); //所有靜態(tài)對(duì)象的反射可以通過(guò)傳null獲取。如果是實(shí)列必須傳實(shí)例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);// 第二步,獲取我們的代理對(duì)象,這里因?yàn)槭墙涌?#xff0c;所以我們使用動(dòng)態(tài)代理的方式amsObj = Proxy.newProxyInstance(context.getClass().getClassLoader(), amsObj.getClass().getInterfaces(), new AMSHookInvocationHandler(amsObj, pmName, hostClzName));// 第三步:設(shè)置為我們的代理對(duì)象amsField.set(gDefaultObj, amsObj); }

接著,我們?cè)趧?dòng)態(tài)代理對(duì)象中,當(dāng)調(diào)用 startActivity 方法的時(shí)候,我們把 intent 信息替換,校驗(yàn)的時(shí)候就可以繞過(guò)系統(tǒng)對(duì) activity 的校驗(yàn),這樣就不會(huì)跑出 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 {// 對(duì) startActivity進(jìn)行Hookif (method.getName().equals("startActivity")) {int index = 0;// 找到我們啟動(dòng)時(shí)的intentfor (int i = 0; i < args.length; i++) {if (args[i] instanceof Intent) {index = i;break;}}// 取出在真實(shí)的IntentIntent originallyIntent = (Intent) args[index];Log.i("AMSHookUtil", "AMSHookInvocationHandler:" + originallyIntent.getComponent().getClassName());// 自己偽造一個(gè)配置文件已注冊(cè)過(guò)的Activity IntentIntent proxyIntent = new Intent();// 因?yàn)槲覀冋{(diào)用的Activity沒(méi)有注冊(cè),所以這里我們先偷偷換成已注冊(cè)。使用一個(gè)假的IntentComponentName componentName = new ComponentName(mPackageName, cls);proxyIntent.setComponent(componentName);// 在這里把未注冊(cè)的Intent先存起來(lái) 一會(huì)兒我們需要在Handle里取出來(lái)用proxyIntent.putExtra(ORIGINALLY_INTENT, originallyIntent);args[index] = proxyIntent;}return method.invoke(mAmsObj, args);} }

但是,如果僅僅這樣做,會(huì)存在一個(gè)問(wèn)題,因?yàn)?intent 信息在校驗(yàn)的時(shí)候被我們替換了,但是我們并沒(méi)有將其還原,這樣,啟動(dòng)的 activity 就不是我們想要的 activity。

那么,我們要在哪個(gè)實(shí)際將 intent 信息還原呢?

我們回過(guò)頭再來(lái)看一下 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);}} }

該方法主要分為兩個(gè)邏輯,當(dāng) mParent 為空的時(shí)候即不為空的時(shí)候

  • 第一種情況,mParent 不為空的時(shí)候,調(diào)用到 mInstrumentation.execStartActivity 方法之后,會(huì)調(diào)用 mMainThread.sendActivityResult 方法
  • 第二種情況,當(dāng) mParent 為空的時(shí)候,會(huì)調(diào)用 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 方法里面,又會(huì)調(diào)用到 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 的代碼發(fā)現(xiàn) sendActivityResult 方法會(huì)調(diào)用 scheduleSendResult 方法發(fā)送,而 scheduleSendResult 方法又會(huì)調(diào)用 sendMessage 方法,在 sendMessage 方法里面,會(huì)調(diào)用 mH 發(fā)送消息(即 Handler)

因此,我們只需要在回調(diào) 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 大概的啟動(dòng)流程:

app 調(diào)用 startActivity 方法 -> Instrumentation 類通過(guò) ActivityManagerNative 或者 ActivityManager( API 26以后)將啟動(dòng)請(qǐng)求發(fā)送給 AMS -> AMS 進(jìn)行一系列檢查并將此請(qǐng)求通過(guò) Binder 派發(fā)給所屬 app -> app 通過(guò) Binder 收到這個(gè)啟動(dòng)請(qǐng)求 -> ActivityThread 中的實(shí)現(xiàn)將收到的請(qǐng)求進(jìn)行封裝后送入 Handler -> 從 Handler 中取出這個(gè)消息,開始 app 本地的 Activity 初始化和啟動(dòng)邏輯。

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);//拿到之前真實(shí)要被啟動(dòng)的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,假如不加上該方法,當(dāng) activity instanceOf AppCompatActivity 時(shí),會(huì)拋出 PackageManager$NameNotFoundException 異常。hookPM(context);} catch (Exception e) {e.printStackTrace();} }

運(yùn)行以上代碼,當(dāng)你啟動(dòng)一個(gè)沒(méi)有在 AndroidManifest 注冊(cè)的 Activity,你會(huì)發(fā)現(xiàn)是可以正常啟動(dòng)的。但是,當(dāng)未注冊(cè)的 Activity 是 AppCompatActivity 的子類的時(shí)候,會(huì)拋出以下異常

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)

從上面的異常信息來(lái)看,主要是在 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; }

而該方法中,是調(diào)用 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); }

運(yùn)行以上代碼,可以看到我們可以正常啟動(dòng)沒(méi)有在 AndroidManifest 的 activity。已經(jīng)完美兼容 Android 8.0,AppCompactActivity。

小結(jié)

啟動(dòng)沒(méi)有在 AndroidManifest 注冊(cè)的 Activity 課改可以分為連個(gè)步驟

  • 在 AMS 通過(guò) intent 校驗(yàn) activity 是否注冊(cè)的時(shí)候,用已經(jīng)在 AndroidManifet 注冊(cè)的 Activity 欺騙 AMS,繞過(guò) 原有 activity 的校驗(yàn),并將原有的 intent 信息儲(chǔ)存起來(lái)
  • 在 AMS 校驗(yàn)完畢的時(shí)候,通過(guò) binder 告知我們的應(yīng)用啟動(dòng)相應(yīng) activity 的時(shí)候,我們將 intent 的信息取出來(lái),還原。

HookDemo

Android Hook 機(jī)制之簡(jiǎn)單實(shí)戰(zhàn)

Android Hook Activity 的幾種姿勢(shì)

推薦閱讀

Android 面試必備 - http 與 https 協(xié)議

Android 面試必備 - 計(jì)算機(jī)網(wǎng)絡(luò)基本知識(shí)(TCP,UDP,Http,https)

Android 面試必備 - 線程

賣一下廣告,歡迎大家關(guān)注我的微信公眾號(hào),掃一掃下方二維碼或搜索微信號(hào) stormjun94,即可關(guān)注。 目前專注于 Android 開發(fā),主要分享 Android開發(fā)相關(guān)知識(shí)和一些相關(guān)的優(yōu)秀文章,包括個(gè)人總結(jié),職場(chǎng)經(jīng)驗(yàn)等。

總結(jié)

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

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。