前言
本文主要是记录Hook activity的知识点,涉及的内容比较多,读完本文读者将会了解,activity的启动,动态代理,合并Dex文件,动态加载资源等,本文的目的是手写一个简易插件化框架,实现宿主app 启动一个插件中的Activity(没有在Manifest文件中注册的Activity)
activity的启动
9.0 activity的启动和早期的版本稍有不同,不过大体流程都是一样的主要是这样几步骤
- Launcher进程请求AMS
- AMS发送创建应用进程请求
- Zygote进程接受请求并孵化应用进程
- 应用进程启动ActivityThread
目的
启动一个插件中的Activity,那么我们面临两个问题
- 插件中的apk是没有安装的
- 要启动的Activity是没有在Manifest中注册的
分析
当启动一个Activity时候 AMS会检查是否在Manifest中注册,如果没有注册就会抛出异常
跟进源码 startActivity ->startActivityForResult
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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()); } ...省略 } |
从startActivityForResult中可以看出 activity的启动是交给了Instrumentation 的execStartActivity方法,那么进入到对应的类中的这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { ..... 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); //检查Activity } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } ...... } |
上面代码中通过ActivityManager.getService()得到一个result 然后丢给了checkStartActivityResult方法 ,那么我们看一下这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public static void checkStartActivityResult(int res, Object intent) { if (!ActivityManager.isStartResultFatalError(res)) { return; } switch (res) { case ActivityManager.START_INTENT_NOT_RESOLVED: case ActivityManager.START_CLASS_NOT_FOUND: if (intent instanceof Intent && ((Intent)intent).getComponent() != null) throw new ActivityNotFoundException( "Unable to find explicit activity class " + ((Intent)intent).getComponent().toShortString() + "; have you declared this activity in your AndroidManifest.xml?"); throw new ActivityNotFoundException( "No Activity found to handle " + intent); } |
我们发现了 传进来的res就是上面的result,并且通过判断,会抛出我们熟悉的异常
";have you declared this activity in your AndroidManifest.xml?",假如我们启动没有注册的activity那么返回的这个result 走到这里肯定会抛出异常,于是我们的hook点就来了,当代码走到这里的时候我们需要替换成可以通过检查的activity,那么如何替换呢,我们继续分析这个result是如何产生的,回到execStartActivity 这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { ..... 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); //检查Activity } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } ...... } |
我们发现activity的启动这个时候交给了ActivityManager,点进去看ActivityManger.getService()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** * @hide */ 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; } }; |
这里我在贴一下这个singleton这个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /** * Singleton helper class for lazily initialization. * * Modeled after frameworks/base/include/utils/Singleton.h * * @hide */ public abstract class Singleton<T> { private T mInstance; protected abstract T create(); public final T get() { synchronized (this) { if (mInstance == null) { mInstance = create(); } return mInstance; } } } |
我们发现singleton的get方法其实就是调用了 create()方法,也就是说,ActivityManager.getService 返回的是IActivityManager.Stub.asInterface(b); 看到这个应该会很熟悉,这就是AIDL,目的是获得AMS的binder对象,然后就可以发起AMS的远程调用了,也就是说Activity的启动交给了AMS。
熟悉AIDL的盆友们很容易就知道,getService返回的IActivityManager,就是一个接口
1 2 3 4 5 | ActivityManager.getService() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options) |
这里就是拿到接口的对象,然后调用接口的startActivity方法,之后代码会进入AMS的startActivity方法中
1 2 3 4 5 6 7 8 9 | //这里是ActivityManagerService中的方法 @Override public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) { return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions, UserHandle.getCallingUserId()); } |
后续的我们先不跟进(有兴趣的同学继续跟着源码往下走就可以了),这里看一下这个方法的第三个参数 intent ,我们发现启动activity的时候需要一个intent,那么当我们启动一个没有注册的activity的时候,我们是不是可以把这个intent替换掉,替换成已经注册的Intent,那么怎么替换呢?
替换
我们现在已经知道代理走到了 IACtivityManager.startActivity(参数1,参数2,参数3是intent...),IACtivityManager是一个接口,于是我们想到用动态代理去,去拦截这个接口做hook。要代理这个接口,就需要这个接口的对象,我们发现ActivityManger.getService()刚好返回的就是IActivityManager的对象于是我们就有了如下代码
1 2 | val mActivityManagerClass = Class.forName("android.app.ActivityManager") val mIActivityManager = mActivityManagerClass.getMethod("getService").invoke(null) |
这样就拿到了IActivityManager接口的对象,接下来使用动态代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | //这个mIActivityManagerProxy本质就是IActivityManager val mIActivityManagerProxy = Proxy.newProxyInstance( HookApplication::class.java.classLoader, arrayOf(mIActivityManagerClass) // 要监听的接口 ) { proxy, method, args -> // IActivityManager 接口的回调方法 // public int startActivity(IApplicationThread caller, String callingPackage, // Intent intent, String resolvedType, IBinder resultTo, String resultWho, // int requestCode, int flags, String profileFile, // ParcelFileDescriptor profileFd, Bundle options) throws RemoteException; if ("startActivity" == method.name) {//接口中有很多方法,但是我们只处理startActivity // 这里偷梁换柱,换了startActivity的方法中的第三个参数 // 换成 可以 通过 AMS检查的 ProxyActivity val intent = Intent(this, ProxyActivity::class.java) //这里ProxyActivity是注册过得 intent.putExtra("actionIntent", args[2] as Intent) args[2] = intent } Log.d("hook", "拦截到了IActivityManager里面的方法" + method.name) // 让系统继续正常往下执行 method.invoke(mIActivityManager, *args) } |
这里我们把接口的方法中的参数intent给换掉了,然后得到了一个mIActivityManagerProxy对象,然后我们需要把这个对象,重新赋值给singleton中的instance对象,(这里解释一下,由于原来的代码就是通过getService来获取IActivityManager接口的对象,而这个getService获取的就是singleton中的mInstance字段,所以我们把我们拦截后得到的IActivityManager对象,重新设置给mInstance字段,就达到了替换的效果)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * 为了拿到 Singleton的对象 * 通过 ActivityManager 拿到 IActivityManagerSingleton 变量(对象) */ val mSingletonField = mActivityManagerClass.getDeclaredField("IActivityManagerSingleton") mSingletonField.isAccessible = true val singletonObj = mSingletonField.get(null)//拿到singleton对象 // 替换点 val mSingletonClass = Class.forName("android.util.Singleton") // 获取此字段 mInstance val mInstanceField = mSingletonClass.getDeclaredField("mInstance") mInstanceField.isAccessible = true // 让虚拟机不要检测 权限修饰符 // 替换 mInstanceField.set(singletonObj, mIActivityManagerProxy) // 替换 |
这里稍微说一下反射注意的点,反射的关键点就是需要静态变量(看客姥爷们如果看不懂上面的,就复习一下反射)。
下面贴一下完整的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | /** * 要在执行 AMS之前,替换可用的 Activity,替换在AndroidManifest里面配置的Activity */ @Throws(Exception::class) private fun hookAmsAction() { // 动态代理 val mIActivityManagerClass = Class.forName("android.app.IActivityManager") // 我们要拿到IActivityManager对象,才能让动态代理里面的 invoke 正常执行下 val mActivityManagerClass = Class.forName("android.app.ActivityManager") val mIActivityManager = mActivityManagerClass.getMethod("getService").invoke(null) // 本质是IActivityManager val mIActivityManagerProxy = Proxy.newProxyInstance( HookApplication::class.java.classLoader, arrayOf(mIActivityManagerClass) // 要监听的接口 ) { proxy, method, args -> // IActivityManager 接口的回调方法 // public int startActivity(IApplicationThread caller, String callingPackage, // Intent intent, String resolvedType, IBinder resultTo, String resultWho, // int requestCode, int flags, String profileFile, // ParcelFileDescriptor profileFd, Bundle options) throws RemoteException; if ("startActivity" == method.name) { // 做自己的业务逻辑 // 换成 可以 通过 AMS检查的 ProxyActivity val intent = Intent(this, ProxyActivity::class.java) intent.putExtra("actionIntent", args[2] as Intent) args[2] = intent } Log.d("hook", "拦截到了IActivityManager里面的方法" + method.name) // 让系统继续正常往下执行 method.invoke(mIActivityManager, *args) } /** * 为了拿到 Singleton的对象 * 通过 ActivityManager 拿到 IActivityManagerSingleton 变量(对象) */ val mSingletonField = mActivityManagerClass.getDeclaredField("IActivityManagerSingleton") mSingletonField.isAccessible = true val singletonObj = mSingletonField.get(null) // 替换点 val mSingletonClass = Class.forName("android.util.Singleton") // 获取此字段 mInstance val mInstanceField = mSingletonClass.getDeclaredField("mInstance") mInstanceField.isAccessible = true // 让虚拟机不要检测 权限修饰符 // 替换 mInstanceField.set(singletonObj, mIActivityManagerProxy) // 替换是需要gDefault } |
把这个方法放到Application的oncreate方法中,我们就可以启动一个没有注册的Activity了
1 2 | val intent = Intent(this, TestActivity::class.java) startActivity(intent) |
这里的TestActivity并没有注册,但是程序不会崩溃,他会跳转到ProxyActivity中(想想我们替换的过程)ProxyActivity一定是注册的Activity
1 2 3 4 5 6 7 | if ("startActivity" == method.name) { // 做自己的业务逻辑 // 换成 可以 通过 AMS检查的 ProxyActivity val intent = Intent(this, ProxyActivity::class.java) intent.putExtra("actionIntent", args[2] as Intent) args[2] = intent } |