Activity Hook

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点说明过程请看http://blog.csdn.net/u012439416/article/details/70666659

总之,需要把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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值