Hook Activity 的 startActivity 方法(Activity startActivity)
先定义一个 InstrumentationProxy 继承 Instrumentation ,然后完善里面的 execStartActivity 方法。
public class InstrumentationProxy extends Instrumentation {
private static final String TAG = "InstrumentationProxy";
Instrumentation mInstrumentation;
public InstrumentationProxy(Instrumentation instrumentation) {
mInstrumentation = instrumentation;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
Log.d(TAG, "Hook 成功" + " who :" + who);
// 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
// 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
Class[] pareTyples = {Context.class, IBinder.class, IBinder.class,
Activity.class, Intent.class, int.class, Bundle.class};
Object[] pareVaules = {who, contextThread, token, target,
intent, requestCode, options};
String methodName = "execStartActivity";
try {
Method execStartActivity = mInstrumentation.getClass().getDeclaredMethod(methodName, pareTyples);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(mInstrumentation, pareVaules);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class HookMainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hook_main);
Class clazz = Activity.class;
try {
Field field = clazz.getDeclaredField("mInstrumentation");//通过Activity.class 拿到 mInstrumentation字段
field.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) field.get(this); //根据activity内mInstrumentation字段 获取Instrumentation对象
Instrumentation instrumentationProxy = new InstrumentationProxy(mInstrumentation); ////创建代理对象
field.set(this, instrumentationProxy); //进行替换
} catch (Exception e) {
e.printStackTrace();
}
}
}
Hook Context 的 startActivity 方法 (getApplicationContext startActivity)
public class HookMainActivity extends AppCompatActivity {
@Override
protected void attachBaseContext(Context context) {
super.attachBaseContext(context);
try {
// 在这里进行Hook
String className = "android.app.ActivityThread";
String methodName = "currentActivityThread";
Class[] pareTyples = new Class[]{};
Object[] pareVaules = new Object[]{};
// 先获取到当前的ActivityThread对象
Class clazz = Class.forName(className);
Method method = clazz.getDeclaredMethod(methodName, pareTyples);
method.setAccessible(true);
Object currentActivityThread = method.invoke(null, pareVaules);// 先获取到当前的ActivityThread对象
String filedName = "mInstrumentation";
// 拿到原始的 mInstrumentation字段
Field field = clazz.getDeclaredField(filedName);
field.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) field.get(currentActivityThread); // 拿到原始的 mInstrumentation字段
// 创建代理对象
Instrumentation instrumentationProxy = new InstrumentationProxy(mInstrumentation);
// 偷梁换柱
field.set(currentActivityThread, instrumentationProxy);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hook_main);
}
}
Hook startActivity 总结
Hook Context 的 startActivity 方法和 Hook Activity 的 startActivity 方法最大的区别就是替换的 Instrumentation 不同 , 前者是 ActivityThread 中的 Instrumentation ,后者是Activity 中的 Instrumentation。另外有一点需要注意的是,在 MainActivity 的onCreate 方法中进行Instrumentation替换的,未必是最佳的替换时间点 ,所以在 Activity 的 attachBaseContext 方法中进行 Instrumentation 替换,因为这个方法要先于 Activity 的 onCreate 方法被调用。讲到这里,我们知道了如何使用代理来 Hook startActivity 方法,简单说就是找到 Hook 点,再用代理对象来替换 Hook 点 。
Hook 的要点
- Hook 的选择点:静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。
- Hook 的过程:
1.寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。
2.选择合适的代理方式,如果是接口可以用动态代理。
3.偷梁换柱——用代理对象替换原始对象。 - Android 的 API 版本比较多,方法和类可能不一样,所以要做好 API 的兼容工作。
我们来梳理下 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(Intent intent, int requestCode) {
startActivityForResult(intent, requestCode, null);
}
/**
* Launch an activity for which you would like a result when it finished.
* When this activity exits, your
* onActivityResult() method will be called with the given requestCode.
* Using a negative requestCode is the same as calling
* {@link #startActivity} (the activity is not launched as a sub-activity).
*
* <p>Note that this method should only be used with Intent protocols
* that are defined to return a result. In other protocols (such as
* {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
* not get the result when you expect. For example, if the activity you
* are launching uses {@link Intent#FLAG_ACTIVITY_NEW_TASK}, it will not
* run in your task and thus you will immediately receive a cancel result.
*
* <p>As a special case, if you call startActivityForResult() with a requestCode
* >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
* activity, then your window will not be displayed until a result is
* returned back from the started activity. This is to avoid visible
* flickering when redirecting to another activity.
*
* <p>This method throws {@link android.content.ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
*
* @param intent The intent to start.
* @param requestCode If >= 0, this code will be returned in
* onActivityResult() when the activity exits.
* @param options Additional options for how the Activity should be started.
* See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
* @throws android.content.ActivityNotFoundException
*
* @see #startActivity
*/
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 方法。
/**
* This is called when a child activity of this one calls its
* {@link #startActivity} or {@link #startActivityForResult} method.
*
* <p>This method throws {@link android.content.ActivityNotFoundException}
* if there was no Activity found to run the given Intent.
*
* @param child The activity making the call.
* @param intent The intent to start.
* @param requestCode Reply request code. < 0 if reply is not requested.
* @param options Additional options for how the Activity should be started.
* See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
* @throws android.content.ActivityNotFoundException
*
* @see #startActivity
* @see #startActivityForResult
*/
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,不能使用动态代理的方式,因此,这里我们使用静态代理的方式。
ok,这里我们重新理一下 Activity 大概的启动流程:
app 调用 startActivity 方法 -> Instrumentation 类通过 ActivityManagerNative 或者 ActivityManager( API 26以后)将启动请求发送给 AMS -> AMS 进行一系列检查并将此请求通过 Binder 派发给所属 app -> app 通过 Binder 收到这个启动请求 -> ActivityThread 中的实现将收到的请求进行封装后送入 Handler -> 从 Handler 中取出这个消息,开始 app 本地的 Activity 初始化和启动逻辑。
网友评论