Android 插件化Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )
Posted 韩曙亮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 插件化】Hook 插件化框架 ( Hook 实现思路 | Hook 按钮点击事件 ) 简要介绍了 Hook 实现思路 , 以及使用静态代理实现了 Hook 按钮点击事件 ;
在本博客中使用 Hook 技术进行 Hook 住 Activity 启动过程 ;
一、分析 Activity 启动源码
1、源码分析
在 " 宿主 " 模块中 , 启动 " 插件 " 模块 , 调用的是 startActivity 方法 ;
如果要 Hook Activity 的启动过程 , 必须熟悉启动 Activity 的源码 , 下面开始分析 调用 startActivity 方法的源码逻辑 ;
在 Activity 中启动另外一个 Activity , 调用 void startActivity(Intent intent) 方法 ,
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
在该 void startActivity(Intent intent) 方法中主要调用 void startActivity(Intent intent, @Nullable Bundle options) 重载方法 ;
@Override
public 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 activity
mIntent.removeExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);
mIntent.removeExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY);
// Put restore token
intent.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 ;
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);
}
}
}
在 void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) 方法中 , 调用了 Instrumentation mInstrumentation 成员的 execStartActivity 方法 , 启动 Activity ;
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
通过 Hook 该 execStartActivity 方法 , 使用该方法启动的 Activity 有完整的上下文环境 ;
2、涉及到的 Activity 相关代码
Activity 相关完整代码 :
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback,
AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
@Override
public 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 activity
mIntent.removeExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);
mIntent.removeExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY);
// Put restore token
intent.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 {
@UnsupportedAppUsage
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
}
}
Activity 中维护了 Instrumentation mInstrumentation 成员变量 ;
public class Activity extends ContextThemeWrapper
implements 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.
@UnsupportedAppUsage
private 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 字段的可访问性 , 只要是调用反射方法 , 或者访问反射的成员字段 , 第一件事就是设置可访问性 ;
正常可访问的方法或字段 , 绝对不会使用反射获取 , 既然使用了反射 , 那么设置可访问性是标配操作 ;
// 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;
以上是关于Android 插件化Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )的主要内容,如果未能解决你的问题,请参考以下文章
Android 插件化Hook 插件化框架 ( Hook Activity 启动流程 | Hook 点分析 )
Android 插件化Hook 插件化框架总结 ( 插件包管理 | Hook Activity 启动流程 | Hook 插件包资源加载 ) ★★★
Android 插件化Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )
Android 插件化Hook 插件化框架 ( Hook 技术 | 代理模式 | 静态代理 | 动态代理 )
Android 插件化Hook 插件化框架 ( Hook Activity 启动流程 | 反射获取 IActivityManager 对象 )
Android 插件化Hook 插件化框架 ( 使用 Hook 方式替换插件 Activity 的 mResources 成员变量 )