startActivity 的两种形式
我们知道,最常见的启动Activity的方式:
- 在Activity 中通过Activity 的startActivity 来启动
- 利用Context 的startActivity 方式
在前面我们了解过了,二者最终都是使用 Instrumentation 的 EXEStartActivity 来实现的,只不过前面的步骤略有不同而已。
对Activity 的startActivity 方法进行Hook
Activity1通过startActivity 启动Activity2,这个流程很长,上半场是:Activity1通知AMS要启动Activity2;下半场是: AMS 通知App进程,要启动Activity2 。针对上半场,我们可以Hook的地方包括:
- Activity 的startActivityForResult方法
- Activity 的 mInstrumentation 字段
- AMN 的 getDefault 方法获取到的对象
针对下半场,我们可以Hook的地方包括:
- H 的 mCallback 字段
- ActivityThread 的 mInstrumentation 对象,对应的 newActivity 方法和 callActivityOnCreate 方法
注意:这里为什么说只能Hook这些地方,一开始我也不理解。经过请教旭哥(刘旭),我们只能访问到自己所在进程的内容,所以只能Hook自己本身进程,而不能Hook到其他进程去。这也是为什么我们不能Hook到AMS的原因,因为压根就不运行在同一个进程。经国斌大神指出,只能Hook到自己的进程以及子进程,如果要Hook其他进程,得有Root权限。
Hook Activity 的 startActivityForResult 方法
实际上就是为App写个BaseActivity基类,重写其startActivityForResult方法,这样,不论调用 startActivity 还是 startActivityForResult ,都会执行这个重写逻辑。实际上这都不能称为Hook,只是覆写了而已。
对Activity 的mInstrumentation 字段进行Hook
Activity 的startActivityForResult 最终会用 mInstrumentation 去调用 execStartActivity ,mInstrumentation 是private的,可以通过反射来获取这个对象,之后把它Hook成我们自己写的 EvilInstrumentation 类型对象,这次我们只是在调用 execStartActivity 之前打印一行日志。
对AMN的getDefault 方法进行Hook
在之前曾经介绍过,AMN的getDefault 返回的是IActivityManager 类型,**IActivityManager 是个接口,那么我们就可以使用 Proxy.newProxyInstance 这种动态代理,把这个IActivityManager 接口类型的对象Hook成我们自定义类MockClass1生成的对象。在实际应用的框架中,一般在 Application 的 attachBaseContext 方法中进行 Hook,这样可以在进入任意一个Activity的时候就能应用这个Hook。
对H类的mCallback 字段进行Hook
因为 App 在收到 AMS 发送的 LAUNCH_ACTIVITY 命令后,会通过 Handler 类型的 H 类发送消息,以启动指定的Activity,我们知道,在Handler 内部有个 CallBack 类型的 mCallback 对象,所以我们可以对 H 类的 mCallback 字段进行Hook,拦截这个过程。这时候,你也许会问,为什么不直接Hook了ActivityThread 的mH字段,答案是: 实现不了。截止现在,可以回顾下:
- 使用静态代理,只有两个类,一个是Handler.Callback,另一个是 Instrumentation,参与Android运转的类,系统只暴露了这两个
- 使用动态代理,只有两个接口: 一个是IActivityManager ,一个是IpackageManager,这很符合Proxy.newProxyInstance 方法特性,它只能对接口类型对象进行Hook
再次对Instrumentation字段进行Hook
与前面不同,我们这次截获的是 Instrumentation 的 newActivity 和 callActivityOncreate 方法,这两个方法会创建目标 Activity 实例,并且调用它的 onCreate。
对AMN的getDefault 方法进行Hook是一劳永逸的
Instrumentation 调用execStartActivity ,最终调用 AMN.getDefault().startActivity() 方法来启动Activity。我们前面知道,Context 和 Activity 都是通过 Instrumentation 来启动Activity。所以如果我们对 AMN 的 getDefault 方法进行 Hook,那么,不管是从Context 进行startActivity还是从 Activity 进行 startActivityForResult,都能生效,是一劳永逸的。
启动没有在AndroidManifest 中声明的Activity
我们插件的App一般是没有在宿主App的AndroidManifest中声明的。
“欺骗AMS”策略分析
这要从Activity 页面跳转流程说起:
AMS在第2步会检查Activity是否在AndroidManifest中声明,如果不存在就会报错。如果要让AMS检查不到要启动的Activity怎么办呢?难道要Hook AMS ?不行,做不到的,AMS还管理着其他的App,如果这么做,所有App都受影响了,Android整个的安全性都会有问题了。既然如此,我们就只能在第 1 步(检查之前) 和第 5 步上做文章了。基本思路是:
- 在第 1 步,发送要启动的Activity 信息给AMS 之前,把这个Activity 替换为一个在AndroidManifest中声明的StubActivity,这样就能绕过AMS的检查了。在替换过程中,要把原来的Activity信息存放在Bundle中。
- 在第 5 步,AMS通知App启动StubActivity的时候,我们肯定不是启动StubActivity ,而是要替换成目标 Activity,原先的Activity 存在Bundle中,取出来就行。
整个流程如下图所示:
Hook 的上半场
前面说了,对AMN进行Hook,可以一劳永逸,这里我们就按照整个思路来:
1 | //代码参见: https://github.comBaoBaoJianqiang/Hook31 |
MockClass1 的基本思路是:拦截startActivity 方法,从参数中取出原油的Intent,替换为启动StubActivity的newIntent,同时把原有的Intent保存在newIntent中,后面换回来的时候还会用到。
Hook的下半场:对H类的mCallback字段进行Hook
经过前面的AMS欺骗,在第4步的时候AMS就会通知App启动 StubActivity了,我们没有权限修改AMS进程,只能需改第5步。本节的解决方案是基于对H类的mCallback字段进行Hook。
1 | //代码可以参考: https://github.com/BaoBaoJianqiang/Hook31 |
这里主要是将“替身”换成“真身”。
Hook下半场:对ActivityThread的mInstrumentation字段Hook
上一节是通过 Hook Handler 的mCallback 把真身换回来,这节换个思路:
1 | //代码可以参考: https://github.com/BaoBaoJianqiang/Hook32 |
对Instrumentation的 newActivity 和 callActivityOnCreate 方法进行拦截。虽然我们没有在源头把真身换回来,但是我在创建目标Activity的对象时,创建的是目标Activity,并调用目标Activity的onCreate 。
“欺骗AMS”的弊端
这种欺骗AMS手段有个大大的问题——AMS会认为每次打开的都是StubActivity。在AMS端有个栈,会存放每次打开的Activity,那么现在栈上就都是StubActivity了,这就相当于那些没有在AndroidManifest中声明的Activity的LaunchMode就只能是standard类型了,即使为此设置了singleTask或者singleTop也不会生效。