【Android 插件化】Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )
Android 插件化系列文章目錄
【Android 插件化】插件化簡介 ( 組件化與插件化 )
【Android 插件化】插件化原理 ( JVM 內存數據 | 類加載流程 )
【Android 插件化】插件化原理 ( 類加載器 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 原理與實現思路 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 類加載器創建 | 資源加載 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 注入上下文的使用 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 獲取插件入口 Activity 組件 | 加載插件 Resources 資源 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 運行應用 | 代碼整理 )
【Android 插件化】Hook 插件化框架 ( Hook 技術 | 代理模式 | 靜態代理 | 動態代理 )
【Android 插件化】Hook 插件化框架 ( Hook 實現思路 | Hook 按鈕點擊事件 )
【Android 插件化】Hook 插件化框架 ( Hook Activity 啟動過程 | 靜態代理 )
文章目錄
- Android 插件化系列文章目錄
- 前言
- 一、分析 Activity 啟動源碼
- 1、源碼分析
- 2、涉及到的 Activity 相關代碼
- 二、Hook Activity 啟動過程
- 1、分析相關 類 / 成員 / 方法 結構
- 2、反射獲取 Activity 中的 Instrumentation mInstrumentation 成員字段
- 3、獲取 Activity 中的 Instrumentation mInstrumentation 成員字段值
- 4、設置 Activity 中的 Instrumentation mInstrumentation 成員字段值
- 5、代理類開發
- 三、完整代碼示例
- 1、主界面代碼示例
- 2、代理類代碼示例
- 3、跳轉的界面
- 4、執行結果
- 四、博客資源
前言
上一篇博客 【Android 插件化】Hook 插件化框架 ( Hook 實現思路 | Hook 按鈕點擊事件 ) 簡要介紹了 Hook 實現思路 , 以及使用靜態代理實現了 Hook 按鈕點擊事件 ;
在本博客中使用 Hook 技術進行 Hook 住 Activity 啟動過程 ;
一、分析 Activity 啟動源碼
1、源碼分析
在 " 宿主 " 模塊中 , 啟動 " 插件 " 模塊 , 調用的是 startActivity 方法 ;
如果要 Hook Activity 的啟動過程 , 必須熟悉啟動 Activity 的源碼 , 下面開始分析 調用 startActivity 方法的源碼邏輯 ;
在 Activity 中啟動另外一個 Activity , 調用 void startActivity(Intent intent) 方法 ,
@Overridepublic void startActivity(Intent intent) {this.startActivity(intent, null);}在該 void startActivity(Intent intent) 方法中主要調用 void startActivity(Intent intent, @Nullable Bundle options) 重載方法 ;
@Overridepublic void startActivity(Intent intent, @Nullable Bundle options) {if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY)) {if (TextUtils.equals(getPackageName(),intent.resolveActivity(getPackageManager()).getPackageName())) {// Apply Autofill restore mechanism on the started activity by startActivity()final IBinder token =mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);// Remove restore ability from current activitymIntent.removeExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);mIntent.removeExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY);// Put restore tokenintent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);intent.putExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY, true);}}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);}}在 void startActivity(Intent intent, @Nullable Bundle options) 中 , 最終調用了 void startActivityForResult(@RequiresPermission Intent intent, int requestCode) 方法 啟動 Activity ;
void startActivityForResult(@RequiresPermission Intent intent, int requestCode) 方法最終也是調用 void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) 重載方法 , 最后一個參數設置為 null ;
在 void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) 方法中 , 調用了 Instrumentation mInstrumentation 成員的 execStartActivity 方法 , 啟動 Activity ;
通過 Hook 該 execStartActivity 方法 , 使用該方法啟動的 Activity 有完整的上下文環境 ;
2、涉及到的 Activity 相關代碼
Activity 相關完整代碼 :
public class Activity extends ContextThemeWrapperimplements LayoutInflater.Factory2,Window.Callback, KeyEvent.Callback,OnCreateContextMenuListener, ComponentCallbacks2,Window.OnWindowDismissedCallback,AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {@Overridepublic void startActivity(Intent intent) {this.startActivity(intent, null);}@Overridepublic void startActivity(Intent intent, @Nullable Bundle options) {if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY)) {if (TextUtils.equals(getPackageName(),intent.resolveActivity(getPackageManager()).getPackageName())) {// Apply Autofill restore mechanism on the started activity by startActivity()final IBinder token =mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);// Remove restore ability from current activitymIntent.removeExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);mIntent.removeExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY);// Put restore tokenintent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);intent.putExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY, true);}}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);}}} }二、Hook Activity 啟動過程
1、分析相關 類 / 成員 / 方法 結構
要 Hook 的方法是 Instrumentation 的 execStartActivity 方法 ;
public class Instrumentation {@UnsupportedAppUsagepublic ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {} }Activity 中維護了 Instrumentation mInstrumentation 成員變量 ;
public class Activity extends ContextThemeWrapperimplements LayoutInflater.Factory2,Window.Callback, KeyEvent.Callback,OnCreateContextMenuListener, ComponentCallbacks2,Window.OnWindowDismissedCallback,AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {// set by the thread after the constructor and before onCreate(Bundle savedInstanceState) is called.@UnsupportedAppUsageprivate Instrumentation mInstrumentation; }2、反射獲取 Activity 中的 Instrumentation mInstrumentation 成員字段
首先 , 任何反射操作 , 都要獲取其字節碼文件 , 作為反射的入口 , 這里先獲取 Activity 字節碼對象 , 直接通過 Activity.class 獲取即可 ;
// 1. 獲取 Activity 字節碼文件// 字節碼文件是所有反射操作的入口Class<?> clazz = Activity.class;然后 , 獲取 Activity 中的 Instrumentation mInstrumentation 成員 Field 字段 ;
// 2. 獲取 Activity 的 Instrumentation mInstrumentation 成員 Field 字段Field mInstrumentation_Field = null;try {mInstrumentation_Field = clazz.getDeclaredField("mInstrumentation");} catch (NoSuchFieldException e) {e.printStackTrace();}最后 , 設置 Field mInstrumentation 字段的可訪問性 , 只要是調用反射方法 , 或者訪問反射的成員字段 , 第一件事就是設置可訪問性 ;
正??稍L問的方法或字段 , 絕對不會使用反射獲取 , 既然使用了反射 , 那么設置可訪問性是標配操作 ;
// 3. 設置 Field mInstrumentation 字段的可訪問性mInstrumentation_Field.setAccessible(true);本步驟完整代碼示例 :
// 1. 獲取 Activity 字節碼文件// 字節碼文件是所有反射操作的入口Class<?> clazz = Activity.class;// 2. 獲取 Activity 的 Instrumentation mInstrumentation 成員 Field 字段Field mInstrumentation_Field = null;try {mInstrumentation_Field = clazz.getDeclaredField("mInstrumentation");} catch (NoSuchFieldException e) {e.printStackTrace();}// 3. 設置 Field mInstrumentation 字段的可訪問性mInstrumentation_Field.setAccessible(true);3、獲取 Activity 中的 Instrumentation mInstrumentation 成員字段值
獲取 Activity 的 Instrumentation mInstrumentation 成員對象值 , 該成員值就是需要被代理的目標對象 ;
代理者 需要 持有 被代理的目標對象 ;
獲取該成員的意義是 , 創建 Instrumentation 代理時, 需要將原始的 Instrumentation 傳入代理對象中 ;
// 4. 獲取 Activity 的 Instrumentation mInstrumentation 成員對象值// 獲取該成員的意義是 , 創建 Instrumentation 代理時, 需要將原始的 Instrumentation 傳入代理對象中Instrumentation mInstrumentation = null;try {mInstrumentation = (Instrumentation) mInstrumentation_Field.get(this);} catch (IllegalAccessException e) {e.printStackTrace();}4、設置 Activity 中的 Instrumentation mInstrumentation 成員字段值
設置 Activity 中的 Instrumentation mInstrumentation 成員字段值 , 將 Activity 的 Instrumentation mInstrumentation 成員變量 設置為自己定義的 Instrumentation 代理對象 ;
此處使用的是靜態代理 ;
// 5. 將 Activity 的 Instrumentation mInstrumentation 成員變量// 設置為自己定義的 Instrumentation 代理對象try {mInstrumentation_Field.set(this, new InstrumentationProxy(mInstrumentation));} catch (IllegalAccessException e) {e.printStackTrace();}5、代理類開發
被代理的目標對象是 Activity 中的 Instrumentation mInstrumentation 成員變量 ;
代理類中需要持有上述成員變量 , 通過反射獲取該成員 , 并設置給代理者 ;
在代理類中 , 繼承 Instrumentation 類 , 這是因為還需要通過反射 , 將代理類設置給 Activity 的 Instrumentation mInstrumentation 成員 , 以達到偷梁換柱的目的 , 檔 Activity 調用 Instrumentation mInstrumentation 成員時 , 其實調用的是開發者開發的代理類 ;
在 Android 界面跳轉時 , 會自動回調 Activity 中的 Instrumentation mInstrumentation 成員的 execStartActivity 方法 ;
實際上調用的是代理類的 execStartActivity 方法 ;
在代理類 execStartActivity 方法中 , 首先調用持有的 Activity 中原本的 Instrumentation mInstrumentation 成員的 execStartActivity 方法 , 然后在該方法的前面 , 后面 , 可以添加自己的業務邏輯 , 該方法的執行參數也可以進行修改 ;
這樣就成功將自己的業務邏輯注入到了 Activity 啟動過程中 ;
代碼示例 :
package com.example.plugin_hook;import android.app.Activity; import android.app.Instrumentation; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.util.Log;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;public class InstrumentationProxy extends Instrumentation {private static final String TAG = "InstrumentationProxy";/*** Activity 中原本的 Instrumentation mInstrumentation 成員* 從構造函數中進行初始化*/final Instrumentation orginalInstrumentation;public InstrumentationProxy(Instrumentation orginalInstrumentation) {this.orginalInstrumentation = orginalInstrumentation;}public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {Log.i(TAG, "注入的 Hook 前執行的業務邏輯");// 1. 反射執行 Instrumentation orginalInstrumentation 成員的 execStartActivity 方法Method execStartActivity_Method = null;try {execStartActivity_Method = Instrumentation.class.getDeclaredMethod("execStartActivity",Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);} catch (NoSuchMethodException e) {e.printStackTrace();}// 2. 設置方法可訪問性execStartActivity_Method.setAccessible(true);// 3. 執行 Instrumentation orginalInstrumentation 的 execStartActivity 方法// 使用 Object 類型對象接收反射方法執行結果ActivityResult activityResult = null;try {activityResult = (ActivityResult) execStartActivity_Method.invoke(orginalInstrumentation,who,contextThread,token,target,intent,requestCode,options);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}Log.i(TAG, "注入的 Hook 后執行的業務邏輯");return activityResult;} }三、完整代碼示例
1、主界面代碼示例
主界面代碼示例 :
package com.example.plugin_hook;import androidx.appcompat.app.AppCompatActivity;import android.app.Activity; import android.app.Instrumentation; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button;import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 獲取按鈕 , 并未按鈕組件設置點擊事件Button button = findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.i(TAG, "Button OnClickListener onClick");startActivity(new Intent(MainActivity.this, MainActivity2.class));}});hookOnClick(button);hookStartActivity();}@Overridepublic void startActivity(Intent intent) {super.startActivity(intent);}/*** hook Button 組件的 getListenerInfo 方法* @param view*/private void hookOnClick(View view){// 獲取 View 的 getListenerInfo 方法Method getListenerInfo = null;try {getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");} catch (NoSuchMethodException e) {e.printStackTrace();}// 執行所有的反射方法 , 設置成員變量 之前 , 都要設置可見性getListenerInfo.setAccessible(true);// 執行 View view 對象的 getListenerInfo 方法Object mListenerInfo = null;try {mListenerInfo = getListenerInfo.invoke(view);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}// 反射獲取 OnClickListener 成員// ① 先根據全類名獲取 ListenerInfo 字節碼Class<?> clazz = null;try {clazz = Class.forName("android.view.View$ListenerInfo");} catch (ClassNotFoundException e) {e.printStackTrace();}// ② 獲取 android.view.View.ListenerInfo 中的 mOnClickListener 成員Field field = null;try {field = clazz.getField("mOnClickListener");} catch (NoSuchFieldException e) {e.printStackTrace();}// ③ 設置該字段訪問性, 執行所有的反射方法 , 設置成員變量 之前 , 都要設置可見性field.setAccessible(true);// ④ 獲取 mOnClickListener 成員變量View.OnClickListener mOnClickListener = null;try {mOnClickListener = (View.OnClickListener) field.get(mListenerInfo);} catch (IllegalAccessException e) {e.printStackTrace();}// ⑤ 修改 View 的 ListenerInfo 成員的 mOnClickListener 成員// 其中 ListenerInfo 成員 是try {View.OnClickListener finalMOnClickListener = mOnClickListener;field.set(mListenerInfo, new View.OnClickListener(){@Overridepublic void onClick(View v) {Log.i(TAG, "Hook Before");finalMOnClickListener.onClick(view);Log.i(TAG, "Hook After");}});} catch (IllegalAccessException e) {e.printStackTrace();}}/*** Hook Activity 界面啟動過程* 鉤住 Instrumentation 的 execStartActivity 方法* 向該方法中注入自定義的業務邏輯*/private void hookStartActivity(){// 1. 獲取 Activity 字節碼文件// 字節碼文件是所有反射操作的入口Class<?> clazz = Activity.class;// 2. 獲取 Activity 的 Instrumentation mInstrumentation 成員 Field 字段Field mInstrumentation_Field = null;try {mInstrumentation_Field = clazz.getDeclaredField("mInstrumentation");} catch (NoSuchFieldException e) {e.printStackTrace();}// 3. 設置 Field mInstrumentation 字段的可訪問性mInstrumentation_Field.setAccessible(true);// 4. 獲取 Activity 的 Instrumentation mInstrumentation 成員對象值// 獲取該成員的意義是 , 創建 Instrumentation 代理時, 需要將原始的 Instrumentation 傳入代理對象中Instrumentation mInstrumentation = null;try {mInstrumentation = (Instrumentation) mInstrumentation_Field.get(this);} catch (IllegalAccessException e) {e.printStackTrace();}// 5. 將 Activity 的 Instrumentation mInstrumentation 成員變量// 設置為自己定義的 Instrumentation 代理對象try {mInstrumentation_Field.set(this, new InstrumentationProxy(mInstrumentation));} catch (IllegalAccessException e) {e.printStackTrace();}}}2、代理類代碼示例
代理類代碼示例 :
package com.example.plugin_hook;import android.app.Activity; import android.app.Instrumentation; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.util.Log;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;public class InstrumentationProxy extends Instrumentation {private static final String TAG = "InstrumentationProxy";/*** Activity 中原本的 Instrumentation mInstrumentation 成員* 從構造函數中進行初始化*/final Instrumentation orginalInstrumentation;public InstrumentationProxy(Instrumentation orginalInstrumentation) {this.orginalInstrumentation = orginalInstrumentation;}public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {Log.i(TAG, "注入的 Hook 前執行的業務邏輯");// 1. 反射執行 Instrumentation orginalInstrumentation 成員的 execStartActivity 方法Method execStartActivity_Method = null;try {execStartActivity_Method = Instrumentation.class.getDeclaredMethod("execStartActivity",Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);} catch (NoSuchMethodException e) {e.printStackTrace();}// 2. 設置方法可訪問性execStartActivity_Method.setAccessible(true);// 3. 執行 Instrumentation orginalInstrumentation 的 execStartActivity 方法// 使用 Object 類型對象接收反射方法執行結果ActivityResult activityResult = null;try {activityResult = (ActivityResult) execStartActivity_Method.invoke(orginalInstrumentation,who,contextThread,token,target,intent,requestCode,options);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}Log.i(TAG, "注入的 Hook 后執行的業務邏輯");return activityResult;} }3、跳轉的界面
跳轉的界面 :
package com.example.plugin_hook;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;public class MainActivity2 extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main2);} }4、執行結果
按鈕點擊事件 , 在調用按鈕事件的前后 , 注入的業務邏輯 , 分別打印 Hook Before 和 Hook After ;
Activity 啟動過程 , Hook 住了 Activity 的 Instrumentation mInstrumentation 成員的 execStartActivity 方法 , 在調用該方法的 前后 , 各注入的業務邏輯中打印 注入的 Hook 前執行的業務邏輯 和 注入的 Hook 后執行的業務邏輯 ;
I/MainActivity: Hook Before I/MainActivity: Button OnClickListener onClick I/InstrumentationProxy: 注入的 Hook 前執行的業務邏輯 I/InstrumentationProxy: 注入的 Hook 后執行的業務邏輯 I/MainActivity: Hook After四、博客資源
博客資源 :
- GitHub : https://github.com/han1202012/Plugin_Hook
總結
以上是生活随笔為你收集整理的【Android 插件化】Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 插件化】Hook 插件
- 下一篇: 【Android 插件化】Hook 插件