Android Hook Activity 的几种姿势

这篇博客已 API 27 的源码为基础分析

前言

在上一篇文章 Android Hook 机制之简单实战 中,我们介绍了 Hook 的要点

  • Hook 的选择点:静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。

  • Hook 过程:

    • 寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。
    • 选择合适的代理方式,如果是接口可以用动态代理。
    • 偷梁换柱——用代理对象替换原始对象。
  • Android 的 API 版本比较多,方法和类可能不一样,所以要做好 API 的兼容工作。

今天这边文章主要讲解一下问题

  • hook activity 的三种方法
  • 怎样启动没有在 AndroidManifest 声明的 activity(完美兼容 Android O,AppCompactActivity)

hook activity 的几种方法

我们知道启动 Activity 主要有两种方式:

  • Activity startActivity
  • getApplicationContext startActivity

hook Activivity 的第一种方法

我们先来看一下 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(@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);
        }
    }
}

首先,我们先来看一下 startActivityForResult 方法,当 mParent 为 null 的时候,会调用到 mInstrumentation.execStartActivity 方法。当 mParent 不为 null 时,都会调用到 mParent.startActivityFromChild 方法。而 mParent 为 Activity 实例,接下来我们一起看一下 startActivityFromChild 方法。

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,不能使用动态代理的方式,因此,这里我们使用静态代理的方式。

扫描二维码关注公众号,回复: 2657815 查看本文章

下面让我们一起看一下 怎样 hook activity 的 mInstrumentation

  • 第一步:拿到当前 activity 的 mInstrumentation
  • 第二步:创建代理对象
  • 第三步:将我们的代理替换原 activity 的 mInstrumentation
public static void replaceInstrumentation(Activity activity) throws Exception {
    Class<?> k = Activity.class;
    //通过Activity.class 拿到 mInstrumentation字段
    Field field = k.getDeclaredField("mInstrumentation");
    field.setAccessible(true);
    //根据activity内mInstrumentation字段 获取Instrumentation对象
    Instrumentation instrumentation = (Instrumentation) field.get(activity);
    //创建代理对象
    Instrumentation instrumentationProxy = new ActivityProxyInstrumentation(instrumentation);
    //进行替换
    field.set(activity, instrumentationProxy);
}

public  class ActivityProxyInstrumentation extends Instrumentation {

    private static final String TAG = "ActivityProxyInstrumentation";

    // ActivityThread中原始的对象, 保存起来
    Instrumentation mBase;

    public ActivityProxyInstrumentation(Instrumentation base) {
        mBase = base;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        // Hook之前, 可以输出你想要的!
        Log.d(TAG,"xxxx: 执行了startActivity, 参数如下: " + "who = [" + who + "], " +
                "contextThread = [" + contextThread + "], token = [" + token + "], " +
                "target = [" + target + "], intent = [" + intent +
                "], requestCode = [" + requestCode + "], options = [" + options + "]");

        // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
        // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(mBase, who,
                    contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            // rom修改了 需要手动适配
            throw new RuntimeException("do not support!!! pls adapt it");
        }
    }


}

在 ActivityProxyInstrumentation 里面,我们打印相应的 log。

运行以下测试代码

try {
    HookHelper.replaceInstrumentation(this);
} catch (Exception e) {
    e.printStackTrace();
}
startActivity(new Intent(this,TestActivityStart.class));

将会看到输出以下 log

hook activity 的第二种方法

我们先来看一下 getApplicationContext startActivity 的调用关系

因此,这里我们要 hook 的是 ActivityThread 的 mInstrumentation

public static void attachContext() throws Exception {
    Log.i(TAG, "attachContext: ");
    // 先获取到当前的ActivityThread对象
    Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
    Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
    currentActivityThreadMethod.setAccessible(true);
    //currentActivityThread是一个static函数所以可以直接invoke,不需要带实例参数
    Object currentActivityThread = currentActivityThreadMethod.invoke(null);

    // 拿到原始的 mInstrumentation字段
    Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
    mInstrumentationField.setAccessible(true);
    Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
    // 创建代理对象
    Instrumentation evilInstrumentation = new ApplicationInstrumentation(mInstrumentation);
    // 偷梁换柱
    mInstrumentationField.set(currentActivityThread, evilInstrumentation);
}
public class ApplicationInstrumentation extends Instrumentation {

    private static final String TAG = "ApplicationInstrumentation";

    // ActivityThread中原始的对象, 保存起来
    Instrumentation mBase;

    public ApplicationInstrumentation(Instrumentation base) {
        mBase = base;
    }

    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, 
                                            Activity target, Intent intent, int requestCode, 
                                            Bundle options) {

        // Hook之前, 可以输出你想要的!
        Log.d(TAG, "xxxx: 执行了startActivity, 参数如下: " + "who = [" + who + "], " + "contextThread = " +
                "" + "" + "[" + contextThread + "], token = [" + token + "], " + "target = [" + 
                target + "], intent = [" + intent + "], requestCode = [" + requestCode + "], " +
                "options = " + "[" + options + "]");

        // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
        // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod
                    ("execStartActivity", Context.class, IBinder.class, IBinder.class, Activity
                            .class, Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token, 
                    target, intent, requestCode, options);
        } catch (Exception e) {
            // rom修改了 需要手动适配
            throw new RuntimeException("do not support!!! pls adapt it");
        }
    }

}

可以看到在 ApplicationInstrumentation 里面,我们只是打印出 startActivity 中各个方法参数的值。

运行以下测试代码

try {
    HookHelper.attachContext();
} catch (Exception e) {
    e.printStackTrace();
}
Intent intent = new Intent(this, TestActivityStart.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);

将看到以下 log


Hook AMS

上面 hook activity 的两种方法其实都有一定缺陷,比如,第一种方法,只能 hook 住通过 Activity startActivity 的 activity。第二种方法,只能 hook 住通过 getApplicationContext().startActivity 启动的 activity。那有没有一种方法能 hook 上述两种的,其实是有的,那就是 hook AMS。下面让我们一起来看一下。

上面 hook startActivity 其实都是 hook 相应的 mInstrumentation.execStartActivity 方法,因此,我们可以从这里下手,看 mInstrumentation.execStartActivity 里面有没有一些共性的东西,可以 hook。

我们先来 mInstrumentation.execStartActivity 方法

public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    IApplicationThread whoThread = (IApplicationThread) contextThread;
    Uri referrer = target != null ? target.onProvideReferrer() : null;
    if (referrer != null) {
        intent.putExtra(Intent.EXTRA_REFERRER, referrer);
    }
    if (mActivityMonitors != null) {
        synchronized (mSync) {
            final int N = mActivityMonitors.size();
            for (int i=0; i<N; i++) {
                final ActivityMonitor am = mActivityMonitors.get(i);
                ActivityResult result = null;
                if (am.ignoreMatchingSpecificIntents()) {
                    result = am.onStartActivity(intent);
                }
                if (result != null) {
                    am.mHits++;
                    return result;
                } else if (am.match(who, null, intent)) {
                    am.mHits++;
                    if (am.isBlocking()) {
                        return requestCode >= 0 ? am.getResult() : null;
                    }
                    break;
                }
            }
        }
    }
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        int result = ActivityManager.getService()
            .startActivity(whoThread, who.getBasePackageName(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token, target != null ? target.mEmbeddedID : null,
                    requestCode, 0, null, options);
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}



这里我们留意 ActivityManager.getService().startActivity 这个方法

public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}

private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() {
            @Override
            protected IActivityManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                return am;
            }
        };

可以看到 IActivityManagerSingleton 是一个单例对象,因此,我们可以 hook 它。

public static void hookAMSAfter26() throws Exception {
    // 第一步:获取 IActivityManagerSingleton
    Class<?> aClass = Class.forName("android.app.ActivityManager");
    Field declaredField = aClass.getDeclaredField("IActivityManagerSingleton");
    declaredField.setAccessible(true);
    Object value = declaredField.get(null);

    Class<?> singletonClz = Class.forName("android.util.Singleton");
    Field instanceField = singletonClz.getDeclaredField("mInstance");
    instanceField.setAccessible(true);
    Object iActivityManagerObject = instanceField.get(value);

    // 第二步:获取我们的代理对象,这里因为 IActivityManager 是接口,我们使用动态代理的方式
    Class<?> iActivity = Class.forName("android.app.IActivityManager");
    InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);
    Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new
            Class<?>[]{iActivity}, handler);

    // 第三步:偷梁换柱,将我们的 proxy 替换原来的对象
    instanceField.set(value, proxy);

}

public class AMSInvocationHandler implements InvocationHandler {

    private static final String TAG = "AMSInvocationHandler";

    Object iamObject;

    public AMSInvocationHandler(Object iamObject) {
        this.iamObject = iamObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //            Log.e(TAG, method.getName());
        if ("startActivity".equals(method.getName())) {
            Log.i(TAG, "ready to startActivity");
            for (Object object : args) {
                Log.d(TAG, "invoke: object=" + object);
            }
        }
        return method.invoke(iamObject, args);
    }
}

执行以下测试代码

try {
    HookHelper.hookAMS();
} catch (Exception e) {
    e.printStackTrace();
}
startActivity(new Intent(this,TestActivityStart.class));

将会看到以下 log

I/AMSInvocationHandler: ready to startActivity

接下来我们一起来看一下 API 25 Instrumentation 的代码(自 API 26 开始 ,Instrumentation execStartActivity 方法有所改变)

public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    IApplicationThread whoThread = (IApplicationThread) contextThread;
    Uri referrer = target != null ? target.onProvideReferrer() : null;
    if (referrer != null) {
        intent.putExtra(Intent.EXTRA_REFERRER, referrer);
    }
    if (mActivityMonitors != null) {
        synchronized (mSync) {
            final int N = mActivityMonitors.size();
            for (int i=0; i<N; i++) {
                final ActivityMonitor am = mActivityMonitors.get(i);
                if (am.match(who, null, intent)) {
                    am.mHits++;
                    if (am.isBlocking()) {
                        return requestCode >= 0 ? am.getResult() : null;
                    }
                    break;
                }
            }
        }
    }
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        int result = ActivityManagerNative.getDefault()
            .startActivity(whoThread, who.getBasePackageName(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token, target != null ? target.mEmbeddedID : null,
                    requestCode, 0, null, options);
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}


可以看到这里启动 activity 是调用 ActivityManagerNative.getDefault().startActivity 启动的。

public abstract class ActivityManagerNative extends Binder implements IActivityManager
{

    /**
     * Retrieve the system's default/global activity manager.
     */
    static public IActivityManager getDefault() {
        return gDefault.get();
    }

    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };

}

同理我们看到 ActivityManagerNative 的 gDefault 是一个静态变量,因此,我们可以尝试 hook gDefault.

public static void hookAmsBefore26() throws Exception {
    // 第一步:获取 IActivityManagerSingleton
    Class<?> forName = Class.forName("android.app.ActivityManagerNative");
    Field defaultField = forName.getDeclaredField("gDefault");
    defaultField.setAccessible(true);
    Object defaultValue = defaultField.get(null);

    Class<?> forName2 = Class.forName("android.util.Singleton");
    Field instanceField = forName2.getDeclaredField("mInstance");
    instanceField.setAccessible(true);
    Object iActivityManagerObject = instanceField.get(defaultValue);

    // 第二步:获取我们的代理对象,这里因为 IActivityManager 是接口,我们使用动态代理的方式
    Class<?> iActivity = Class.forName("android.app.IActivityManager");
    InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);
    Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{iActivity}, handler);

    // 第三步:偷梁换柱,将我们的 proxy 替换原来的对象
    instanceField.set(defaultValue, proxy);
}

到此,hook Activity 的三种方式已讲解完毕


启动一个没有在 AndroidManifest 声明的 Activity

我们知道,当我们启动一个没有在 AndroidManifest 中声明的 activity,会抛出 ActivityNotFoundException 异常。

Caused by: android.content.ActivityNotFoundException: Unable to find explicit activity class {com.xj.hookdemo/com.xj.hookdemo.activityhook.TargetAppCompatActivity}; have you declared this activity in your AndroidManifest.xml?

    at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2124)
    at android.app.Instrumentation.execStartActivity(Instrumentation.java:1802)
    at android.app.Activity.startActivityForResult(Activity.java:4514)
    at android.support.v4.app.BaseFragmentActivityApi16.startActivityForResult(BaseFragmentActivityApi16.java:54)
    at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:67)
    at android.app.Activity.startActivityForResult(Activity.java:4472)
    at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:720)
    at android.app.Activity.startActivity(Activity.java:4833)
    at android.app.Activity.startActivity(Activity.java:4801)
    at com.xj.hookdemo.activityhook.TestStartActivityNoRegister.onB

从报错的堆栈中,我们非常定位到 Instrumentation.execStartActivity 方法

public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, String resultWho,
        Intent intent, int requestCode, Bundle options, UserHandle user) {
       -----  // 省略若干代码



    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        int result = ActivityManager.getService()
            .startActivityAsUser(whoThread, who.getBasePackageName(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token, resultWho,
                    requestCode, 0, null, options, user.getIdentifier());
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}

在该方法中,调用 startActivityAsUser 方法通过传入的 intent 获取 result,再通过 checkStartActivityResult 方法,判断 result 是否合法。

而我们知道我们启动的 activity 信息都储存在 intent 中,那么我们若想要 启动一个没有在 AndroidManifest 声明的 Activity,那我们只需要在 某个时机,即调用 startActivity 方法之前欺骗 AMS 我们的 activity 已经注册(即替换 intent),这样就不会抛出 ActivityNotFoundException 异常。

在前面的时候,我们已经讲解到如何 hook ams,这里我们不再具体讲述,主要步骤如下

  • 第一步, API 26 以后,hook android.app.ActivityManager.IActivityManagerSingleton, API 25 以前,hook android.app.ActivityManagerNative.gDefault
  • 第二步,获取我们的代理对象,这里因为是接口,所以我们使用动态代理的方式
  • 第三步:设置为我们的代理对象
private static void hookAMS(Context context) throws ClassNotFoundException,
        NoSuchFieldException, IllegalAccessException {
    // 第一步,  API 26 以后,hook android.app.ActivityManager.IActivityManagerSingleton,
    //  API 25 以前,hook android.app.ActivityManagerNative.gDefault
    Field gDefaultField = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Class<?> activityManager = Class.forName("android.app.ActivityManager");
        gDefaultField = activityManager.getDeclaredField("IActivityManagerSingleton");
    } else {
        Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
        gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
    }
    gDefaultField.setAccessible(true);
    Object gDefaultObj = gDefaultField.get(null); //所有静态对象的反射可以通过传null获取。如果是实列必须传实例
    Class<?> singletonClazz = Class.forName("android.util.Singleton");
    Field amsField = singletonClazz.getDeclaredField("mInstance");
    amsField.setAccessible(true);
    Object amsObj = amsField.get(gDefaultObj);

    //
    String pmName = getPMName(context);
    String hostClzName = getHostClzName(context, pmName);

    // 第二步,获取我们的代理对象,这里因为是接口,所以我们使用动态代理的方式
    amsObj = Proxy.newProxyInstance(context.getClass().getClassLoader(), amsObj.getClass()
            .getInterfaces(), new AMSHookInvocationHandler(amsObj, pmName, hostClzName));

    // 第三步:设置为我们的代理对象
    amsField.set(gDefaultObj, amsObj);
}

接着,我们在动态代理对象中,当调用 startActivity 方法的时候,我们把 intent 信息替换,校验的时候就可以绕过系统对 activity 的校验,这样就不会跑出 ActivityNotFoundException 异常。

public class AMSHookInvocationHandler implements InvocationHandler {

    public static final String ORIGINALLY_INTENT = "originallyIntent";
    private Object mAmsObj;
    private String mPackageName;
    private String cls;

    public AMSHookInvocationHandler(Object amsObj, String packageName, String cls) {
        this.mAmsObj = amsObj;
        this.mPackageName = packageName;
        this.cls = cls;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //  对 startActivity进行Hook
        if (method.getName().equals("startActivity")) {
            int index = 0;
            //  找到我们启动时的intent
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }

            // 取出在真实的Intent
            Intent originallyIntent = (Intent) args[index];
            Log.i("AMSHookUtil", "AMSHookInvocationHandler:" + originallyIntent.getComponent()
                    .getClassName());
            // 自己伪造一个配置文件已注册过的Activity Intent
            Intent proxyIntent = new Intent();
            //  因为我们调用的Activity没有注册,所以这里我们先偷偷换成已注册。使用一个假的Intent
            ComponentName componentName = new ComponentName(mPackageName, cls);
            proxyIntent.setComponent(componentName);
            // 在这里把未注册的Intent先存起来 一会儿我们需要在Handle里取出来用
            proxyIntent.putExtra(ORIGINALLY_INTENT, originallyIntent);
            args[index] = proxyIntent;
        }
        return method.invoke(mAmsObj, args);
    }
}

但是,如果仅仅这样做,会存在一个问题,因为 intent 信息在校验的时候被我们替换了,但是我们并没有将其还原,这样,启动的 activity 就不是我们想要的 activity。

那么,我们要在哪个实际将 intent 信息还原呢?

我们回过头再来看一下 Activity 的 startActivityForResult 方法

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);
        }
    }
}

该方法主要分为两个逻辑,当 mParent 为空的时候即不为空的时候

  • 第一种情况,mParent 不为空的时候,调用到 mInstrumentation.execStartActivity 方法之后,会调用 mMainThread.sendActivityResult 方法
  • 第二种情况,当 mParent 为空的时候,会调用 mParent.startActivityFromChild
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 方法里面,又会调用到 mMainThread.sendActivityResult 方法。因此,我们只需看一下该方法是怎样 send ActivityResult 的。

public final class ActivityThread {

    ---

    public final void sendActivityResult(
            IBinder token, String id, int requestCode,
            int resultCode, Intent data) {
        if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id
                + " req=" + requestCode + " res=" + resultCode + " data=" + data);
        ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
        list.add(new ResultInfo(id, requestCode, resultCode, data));
        mAppThread.scheduleSendResult(token, list);
    }
    public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
        ResultData res = new ResultData();
        res.token = token;
        res.results = results;
        sendMessage(H.SEND_RESULT, res);
    }
    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        if (DEBUG_MESSAGES) Slog.v(
            TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
            + ": " + arg1 + " / " + obj);
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }

    final H mH = new H();
}

跟踪 ActivityThread 的代码发现 sendActivityResult 方法会调用 scheduleSendResult 方法发送,而 scheduleSendResult 方法又会调用 sendMessage 方法,在 sendMessage 方法里面,会调用 mH 发送消息(即 Handler)

因此,我们只需要在回调 H 的 handleMessage 消息之前还原我们的 intent 信息即可。

private class H extends Handler {

    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case LAUNCH_ACTIVITY: {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                r.packageInfo = getPackageInfoNoCheck(
                        r.activityInfo.applicationInfo, r.compatInfo);
                handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;

}

ok,这里我们重新理一下 Activity 大概的启动流程:

app 调用 startActivity 方法 -> Instrumentation 类通过 ActivityManagerNative 或者 ActivityManager( API 26以后)将启动请求发送给 AMS -> AMS 进行一系列检查并将此请求通过 Binder 派发给所属 app -> app 通过 Binder 收到这个启动请求 -> ActivityThread 中的实现将收到的请求进行封装后送入 Handler -> 从 Handler 中取出这个消息,开始 app 本地的 Activity 初始化和启动逻辑。

hook ActivityThread 的 mH

/**
 *
 * @param context
 * @param isAppCompatActivity 表示是否是 AppCompatActivity
 * @throws Exception
 */
private static void hookLaunchActivity(Context context, boolean isAppCompatActivity) throws
        Exception {
    Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
    Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
    sCurrentActivityThreadField.setAccessible(true);
    Object sCurrentActivityThreadObj = sCurrentActivityThreadField.get(null);

    Field mHField = activityThreadClazz.getDeclaredField("mH");
    mHField.setAccessible(true);
    Handler mH = (Handler) mHField.get(sCurrentActivityThreadObj);
    Field callBackField = Handler.class.getDeclaredField("mCallback");
    callBackField.setAccessible(true);
    callBackField.set(mH, new ActivityThreadHandlerCallBack(context, isAppCompatActivity));
}

public static class ActivityThreadHandlerCallBack implements Handler.Callback {

    private final boolean mIsAppCompatActivity;
    private final Context mContext;

    public ActivityThreadHandlerCallBack(Context context, boolean isAppCompatActivity) {
        mIsAppCompatActivity = isAppCompatActivity;
        mContext = context;
    }

    @Override
    public boolean handleMessage(Message msg) {
        int LAUNCH_ACTIVITY = 0;
        try {
            Class<?> clazz = Class.forName("android.app.ActivityThread$H");
            Field field = clazz.getField("LAUNCH_ACTIVITY");
            LAUNCH_ACTIVITY = field.getInt(null);
        } catch (Exception e) {
        }
        if (msg.what == LAUNCH_ACTIVITY) {
            handleLaunchActivity(mContext, msg, mIsAppCompatActivity);
        }
        return false;
    }
}

private static void handleLaunchActivity(Context context, Message msg, boolean
        isAppCompatActivity) {
    try {
        Object obj = msg.obj;
        Field intentField = obj.getClass().getDeclaredField("intent");
        intentField.setAccessible(true);
        Intent proxyIntent = (Intent) intentField.get(obj);
        //拿到之前真实要被启动的Intent 然后把Intent换掉
        Intent originallyIntent = proxyIntent.getParcelableExtra(ORIGINALLY_INTENT);
        if (originallyIntent == null) {
            return;
        }
        proxyIntent.setComponent(originallyIntent.getComponent());

        Log.e(TAG, "handleLaunchActivity:" + originallyIntent.getComponent().getClassName());

        // 如果不需要兼容 AppCompatActivity
        if (!isAppCompatActivity) {
            return;
        }

        //兼容AppCompatActivity,假如不加上该方法,当 activity instanceOf AppCompatActivity 时,会抛出  PackageManager$NameNotFoundException 异常。
        hookPM(context);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

运行以上代码,当你启动一个没有在 AndroidManifest 注册的 Activity,你会发现是可以正常启动的。但是,当未注册的 Activity 是 AppCompatActivity 的子类的时候,会抛出以下异常

Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{com.xj.hookdemo/com.xj.hookdemo.activityhook.TargetAppCompatActivity}
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:215)
at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155)
at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:59)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
at com.xj.hookdemo.activityhook.TargetAppCompatActivity.onCreate(TargetAppCompatActivity.java:12)
at android.app.Activity.performCreate(Activity.java:7026)
at android.app.Activity.performCreate(Activity.java:7017)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1231)

从上面的异常信息来看,主要是在 NavUtils.getParentActivityName 方法中抛出异常。

@Nullable
public static String getParentActivityName(Context context, ComponentName componentName)
        throws NameNotFoundException {
    PackageManager pm = context.getPackageManager();
    ActivityInfo info = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);
    if (Build.VERSION.SDK_INT >= 16) {
        String result = info.parentActivityName;
        if (result != null) {
            return result;
        }
    }
    if (info.metaData == null) {
        return null;
    }
    String parentActivity = info.metaData.getString(PARENT_ACTIVITY);
    if (parentActivity == null) {
        return null;
    }
    if (parentActivity.charAt(0) == '.') {
        parentActivity = context.getPackageName() + parentActivity;
    }
    return parentActivity;
}

而该方法中,是调用 PackageManager getActivityInfo 中去查询。因此,我们只需要 hook PackageManager


private static void hookPM(Context context) throws ClassNotFoundException,
        NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
        InvocationTargetException {
    String pmName = getPMName(context);
    String hostClzName = getHostClzName(context, pmName);

    Class<?> forName = Class.forName("android.app.ActivityThread");
    Field field = forName.getDeclaredField("sCurrentActivityThread");
    field.setAccessible(true);
    Object activityThread = field.get(null);
    Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager");
    Object iPackageManager = getPackageManager.invoke(activityThread);
    PackageManagerHandler handler = new PackageManagerHandler(iPackageManager, pmName, hostClzName);
    Class<?> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");
    Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new
            Class<?>[]{iPackageManagerIntercept}, handler);
    // 获取 sPackageManager 属性
    Field iPackageManagerField = activityThread.getClass().getDeclaredField("sPackageManager");
    iPackageManagerField.setAccessible(true);
    iPackageManagerField.set(activityThread, proxy);
}

运行以上代码,可以看到我们可以正常启动没有在 AndroidManifest 的 activity。已经完美兼容 Android 8.0,AppCompactActivity。

小结

启动没有在 AndroidManifest 注册的 Activity 课改可以分为连个步骤

  • 在 AMS 通过 intent 校验 activity 是否注册的时候,用已经在 AndroidManifet 注册的 Activity 欺骗 AMS,绕过 原有 activity 的校验,并将原有的 intent 信息储存起来
  • 在 AMS 校验完毕的时候,通过 binder 告知我们的应用启动相应 activity 的时候,我们将 intent 的信息取出来,还原。

HookDemo

Android Hook 机制之简单实战

Android Hook Activity 的几种姿势

最后的最后

卖一下广告,欢迎大家关注我的微信公众号,扫一扫下方二维码或搜索微信号 stormjun,即可关注。 目前专注于 Android 开发,主要分享 Android开发相关知识和一些相关的优秀文章,包括个人总结,职场经验等。

猜你喜欢

转载自blog.csdn.net/gdutxiaoxu/article/details/81459910