1, Activity Hook
Activity,Service等组件是有生命周期的,它们统一由系统服务AMS管理;
Activity的详细启动流程在此就不论述了。主要步骤如下,
1, 从App进程调用startActivity等一系列方法;
2, 通过IPC调用进入系统进程system_server,完成Activity管理以及一些校检工作;
3, 回到APP进程完成真正的Activioty对象创建。
Hook Activity有2个问题需要解决,
1,必须在AndroidManifest.xml中显式声明要启动的Activity;
2,Activity 生命周期的管理
问题2随着问题的解决也跟着解决了,所以主要是问题1.
问题1的解决方案如下:
首先在步骤1中假装启动一个已经在AndroidManifest.xml里面声明过的替身Activity,然后让这个Activity进入AMS
进程接受检验;最后在第三步的时候换成真正需要启动的Activity;这样就成功欺骗了AMS进程,达到Hook的目的。
这一过程简称为穿马甲和脱马甲。
1.1 注册
AndroidManifest.xml里面的确注册了大量的空的activity
<activity
android:name=".stub.ActivityStub$P00$Standard00"
android:allowTaskReparenting="true"
android:excludeFromRecents="true"
android:exported="false"
android:hardwareAccelerated="true"
android:label="@string/stub_name_activity"
android:launchMode="standard"
android:noHistory="true"
android:theme="@style/DroidPluginTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.morgoo.droidplugin.category.PROXY_STUB" />
</intent-filter>
<meta-data
android:name="com.morgoo.droidplugin.ACTIVITY_STUB_INDEX"
android:value="0" />
</activity>
•••
这些类在ActivityStub中实现如下,
public static class P00{
public static class SingleInstance00 extends SingleInstanceStub {
}
public static class SingleTask00 extends SingleTaskStub {
}
•••
完全就是一些空类。
1.2 使用替身Activity绕过AMS
在AMS章节论述过, 调用AMS的startActivity方法其实是调用startActivity的beforeInvoke方法。
IactivityManagerHookHandle的内部类startActivity的beforeInvoke方法如下,
RunningActivities.beforeStartActivity();
boolean bRet = true;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
bRet = doReplaceIntentForStartActivityAPILow(args);
} else {
bRet = doReplaceIntentForStartActivityAPIHigh(args);
•••
1,判断是否有可用的activity, 预先占坑的stub activity数量是有限的,如果没有的话得腾出一个来。
RunningActivities的beforeStartActivity如下,
public static void beforeStartActivity() {
synchronized (mRunningActivityList) {
for (RunningActivityRecord record : mRunningActivityList.values()) {
if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) {
continue;
}else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
doFinshIt(mRunningSingleTopActivityList);
}else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
doFinshIt(mRunningSingleTopActivityList);
}else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
doFinshIt(mRunningSingleTopActivityList);
}
}
}
}
可以看到,除了MULTIPLE启动模式的activity record,其余的都会调用doFinshIt()方法。
doFinshIt方法如下,
private static void doFinshIt(Map<Integer, RunningActivityRecord> runningActivityList) {
if (runningActivityList != null && runningActivityList.size() >= PluginManager.STUB_NO_ACTIVITY_MAX_NUM - 1) {
List<RunningActivityRecord> activitys = new ArrayList<>(runningActivityList.size());
activitys.addAll(runningActivityList.values());
Collections.sort(activitys, sRunningActivityRecordComparator);
RunningActivityRecord record = activitys.get(0);
if (record.activity != null && !record.activity.isFinishing()) {
record.activity.finish();
}
}
}
2,根据android版本调用不同的方法, doReplaceIntentForStartActivityAPIHigh方法主要逻辑如下,
A,从参数列表里获取intent参数
int intentOfArgIndex = findFirstIntentIndexInArgs(args);
B, 通过intent获取待启动的ActivityInfo
ActivityInfo activityInfo = resolveActivity(intent);
C, 调用selectProxyActivity()选出一个合适的stub activity,主要是根据launch mode还有theme进行判断。
ComponentName component = selectProxyActivity(intent);
D, 创建一个新的intent,其component指向选出来的stub activity,flags和原始intent相同,
同时将原始的intent作为一个EXTRA_TARGET_INTENT参数设置到这个新的intent里面.
Intent newIntent = new Intent();
try {
ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(component.getPackageName());
setIntentClassLoader(newIntent, pluginClassLoader);
} catch (Exception e) {
Log.w(TAG, "Set Class Loader to new Intent fail", e);
}
newIntent.setComponent(component);
newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);//这里是真是的intent
newIntent.setFlags(intent.getFlags());
E,最后,如果是宿主程序启动的插件,加上FLAG_ACTIVITY_NEW_TASK在一个新的task中启动插件
if (TextUtils.equals(mHostContext.getPackageName(), callingPackage)) {
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
•••
到此,一个假intent就包装完成了,这个假的intent是AndroidManifest.xml里面已经注册过的,因此可以通过AMS的检验。
1.3 恢复真身
在第三步的过程中, AMS进程转移到App进程也是通过Binder调用完成的,承载这个功能的Binder对象是IApplicationThread;
在App进程它是Server端,在Server端接受Binder远程调用的是Binder线程池,Binder线程池通过Handler将消息转发给App的主线程;
详细的hook点说明过程请看
总之,需要把ActivityThread里面处理消息的Handler类H的的mCallback 变量Hook为自定义callback类的对象。
1.3.1 callback Hook
Hook的步骤如下,
HookFactory的installHook方法中有关Hook callback的代码如下,
installHook(new PluginCallbackHook(context), classLoader);
PluginCallbackHook相对于前面的Hook方法有些不同。这是Hook一个callback对象。
PluginCallbackHook的createHookHandle返回的结果为null
protected BaseHookHandle createHookHandle() {
return null;
}
PluginCallbackHook的onInstall主要逻辑如下,
1,获取到当前的ActivityThread对象
Object target = ActivityThreadCompat.currentActivityThread();
Class ActivityThreadClass = ActivityThreadCompat.activityThreadClass();
2,获取ActivityThread对象的mH变量,也就是H对象(主线程,一个进程中仅有一个)
Field mHField = FieldUtils.getField(ActivityThreadClass, "mH");
Handler handler = (Handler) FieldUtils.readField(mHField, target);
3,获取H的mCallback变量,
Field mCallbackField = FieldUtils.getField(Handler.class, "mCallback");
//*这里读取出旧的callback并处理*/
Object mCallback = FieldUtils.readField(mCallbackField, handler);
4,构造PluginCallback对象,
PluginCallback value = mCallback != null ? new PluginCallback(mHostContext, handler, (Handler.Callback) mCallback) : new PluginCallback(mHostContext, handler, null);
value.setEnable(isEnable());
5,将H的mCallback变量设置为PluginCallback。
mCallbacks.add(value);//将PluginCallback对象添加到mCallbacks ArrayList中
FieldUtils.writeField(mCallbackField, handler, value);
1.3.2 获取intent
PluginCallback的定义如下,
public class PluginCallback implements Handler.Callback {
handleMessage方法有关LAUNCH_ACTIVITY消息处理如下,
if (msg.what == LAUNCH_ACTIVITY) {
return handleLaunchActivity(msg);
}
这样,当AMS检查完启动Activity时,会调用PluginCallback的handleMessage方法,在此就可以把替身恢复成真身。
handleLaunchActivity方法的主要逻辑如下,
1, 从Message的obj字段里取出intent,注意这里是AMS返回过来的之前创建的那个假intent,然后从其
EXTRA_TARGET_INTENT字段里取出原始intent
Object obj = msg.obj;
Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent");
//ActivityInfo activityInfo = (ActivityInfo) FieldUtils.readField(obj, "activityInfo", true);
stubIntent.setExtrasClassLoader(mHostContext.getClassLoader());
Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
2, 根据原始intent解析出ComponentName和ActivityInfo
ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager());
ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0);
if (targetActivityInfo != null) {
if (targetComponentName != null && targetComponentName.getClassName().startsWith(".")) {
targetIntent.setClassName(targetComponentName.getPackageName(), targetComponentName.getPackageName() + targetComponentName.getClassName());
}
3, 把这个原始intent以及解析出来的ActivityInfo重新写回Message中,这样后续的处理中就会用这个原始的intent了。
Intent newTargetIntent = new Intent();
newTargetIntent.setComponent(targetIntent.getComponent());
newTargetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo);
if (stubActivityInfo != null) {
newTargetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo);
}
FieldUtils.writeDeclaredField(msg.obj, "intent", newTargetIntent);
这样,最后真正启动的是未在AndroidManifest.xml中显式声明要启动的Activity。