技术概念来源:[ 360开源插件框架,项目地址:https://github.com/DroidPluginTeam/DroidPlugin ]
一、Hook系统剪切板服务流程回顾
在之前的一篇文章中已经介绍了 Android中的应用启动流程,这个流程一定要理解透彻,这样我们才可以进行后续的Hook操作,在之前还介绍了Android中如何Hook系统的剪切板服务实现方法的拦截效果,实现原理就是:
1、先找到Hook点,这个一般是分析源码来得到,而一般的Hook点都是静态变量或者是单例方法。
2、构造一个需要拦截的代理对象,需要的条件是代理的对象必须实现一个接口,其次就是需要获取到原始对象实例
有了这两步就是用反射机制,把代理对象替换源对象即可,然后在InvocationHandler的invoke回调方法中进行制定方法的拦截。
那么有了之前的Hook知识点基础和启动流程分析,今天我们就要开始说Hook系统AMS服务的知识点的了,因为这个服务和应用启动相关的,所以一定要理解了应用启动流程。然后按照之前Hook系统的剪切板服务之后,在任何Hook操作之前第一步得先找到Hook点,那下面就开始找这个点。
还是按照之前的逻辑,如果我们想拦截方法,肯定是Hook本地化服务对象,比如之前Hook掉系统的剪切板的方法,肯定是Hook掉IClipboard对象,当时还记得找到的Hook点步骤思路是:
1、首先通过反射从ServiceManager中获取到Clipboard的远端Binder对象
2、Hook掉这个Binder对象,拦截他的queryLocalInterface方法,在这个方法中再次Hook掉本地化服务对象
3、然后在拦截本地化服务对象的具体实现方法即可
在这个过程中我们发现需要做两次Hook操作,因为我们拦截的对象是本地化服务对象,我们实际在使用服务的时候也是这个对象,而在第一步得先Hook掉服务对应的远端Binder对象,然后在Hook掉服务对象即可。
二、Hook系统的AMS服务
那么今天我们介绍如何Hook掉系统的AMS服务,来实现拦截Activity的启动流程,那么我们依然可以采用上面的这种方式来进行操作,但是我们还可以采用另外一种更方便的技术来做操作,在上面的三个步骤中,会发现第一次Hook其实是可以省略的,因为我们在Hook掉AMS服务的时候,可以发现在系统中某一个地方,有AMS本地化实际对象IActivityManager,我们只要找到他就可以先获取对象,然后在通过反射进行Hook代理对象的重置即可。
所以在之前剪切板服务没有这么做,是因为我们并没有发现系统中有一个地方可以去获取IClipboardManager本地化实际对象,所以在以后的Hook操作中,一定要记住这点,如果能够通过分析Android源码,获取服务的本地化对象那么就可以直接Hook即可,如果不行,那只能通过两次Hook操作了。
下面我们通过源码来找到AMS本地化实际对象
我们在前面一篇文章中介绍了Android中应用的启动流程,当时有这张图:
当时分析的时候也说了,这个是整个应用启动的第一处远程通信的地方,从图上可以清晰的看到,这里的对象之间的关系,那么我们其实就是需要Hook本地端的中间者,也就是ActivityManagerProxy对象,那么我们如何去查找这个对象呢?因为我们的目的就是要拦截startActivity方法,那么可以通过这个方法跟进源码,这里就不在说明了,在之前的启动流程中已经讲解的非常清楚了,我们最终会跟进到ActivityManagerNative类中的getDefault方法:
而这里的gDefault是Singleton类型实现单例模式功能的:
好了,这里发现其实内部就是先从ServiceManager中获取到远程的AMS的Binder对象,然后在转化成本地操作的实际对象,其实就是ActivityManagerProxy类型的,那么这里我们可以省略Hook远端的Binder对象操作了,同时会发现这里的方法和变量都是static类型的,那么对于反射来说就非常有利了,之前也说过在Hook技术中:对于单例方法和static变量以及static方法最好用反射了!
那么我们就可以这么弄:
1、使用反射机制获取到AMS的本地化对象
2、然后在使用动态代理技术,生成一个代理对象
3、最后把代理对象在重置回去即可
在之前介绍动态代理生成代理对象也说到,只要符合两个规则即可:
1》有原始对象,这里正好是第一步中获取到的本地化对象
2》原始对象必须实现接口,这里也正好符合,因为ActivityManagerProxy是实现了IActivityManager接口类型的
下面就开始操作吧:
这里我们可以拦截startActivity的方法:
我们运行程序,但是需要需要注意哦,一般会先把Hook操作就是上面的反射工作放到最开始的地方,最好是Application的attachBaseContext方法中,因为这个时机是最早的。
哈哈,看到了,这里我们拦截成功了,而且可以获取拦截的参数,从中可以知道启动的Activity信息了。
三、拦截应用启动流程
到这里我们就已经实现可以拦截系统中Activity启动的流程了,那么下面咋们就来实践一下,我们做一个案例,就是启动一个没有在AndroidManifest.xml中声明的Activity:
这个思路可以是这样,上面已经拦截了启动Activity流程,也得到了启动参数intent信息,那么就在这里,我们可以自己构造一个假的Activity信息的intent,然后启动:
然后我们启动activity的代码:
这里启动的是TargetActivity,但是需要在AndroidManifest.xml中声明StubActivity:
然后启动,会发现没有报错,而是启动成功了StubActivity了。
那么这里其实想想应该没问题的,虽然我们代码中启动的是TargetActivity,但是我们进行拦截然后替换成了StubActivity,而StubActivity在AndroidManifest.xml中声明了,也不会报错,所以运行成功了。
从上面的简单例子可以看到,我们没有在AndroidManifest.xml中声明TargetActivity,运行也没有报错的,原因是因为我们替换了StubActivity,而StubActivity声明了,运行也是不会报错的。那么如果我们把StubActivity的声明去了,运行会报错吗?
答案是肯定会报错的,而且报错的信息是:StubActivity没有声明,而不是TargetActivity没有声明,因为我们已经替换了。那么这里就引出第二个问题了,关于Activity启动的远端操作具体如何?
这个就需要去看ActivityManagerService源码了,在这里会做Activity启动前的校验工作,而这个是运行在远端的system_server进程中的。有的同学说那继续在这里Hook呀,要是把这个校验工作也给替换了,就可以实现真正意义上无需声明Activity就可以启动了,做当然可以做,但是不是本文重点,因为这个校验工作实在system_server进程中的,如果要Hook的话,就需要注入到进程中,而注入system_server系统进程是在这里讲到:Android中通过系统进程注入拦截应用行为。
上面的一个例子中会发现虽然拦截了,也替换了启动参数信息,但是发现然并卵,因为我们最终想启动的还是TargetActivity,而不是替换的StubActivity,所以这里还需要在后面把StubActivity给换回来。那么这里就要去分析Activity校验之后开始启动的逻辑了,而这里就要涉及到之前文章分析的启动流程中第二个远程通信:
这个通信其实是为了Activity的生命周期工作,而在这个过程中和上面的AMS的通信不一样,这里的远端服务是在应用进程中,而本地服务实在系统进程中,也就是说系统进程会发送一些生命周期命令给应用进程,而具体实际的处理逻辑实在应用中的。
那么我们就又要开始进行Hook了,按照之前的逻辑,这里我们需要Hook掉ApplicationThreadProxy即可,但是这里不需要这么弄,因为还有一种更为方便的路径,就是在最终的处理地方进行拦截,这个就要分析ActivityThread类了,因为ApplicationThread类就是在这里定义,也就说具体处理逻辑就在这里,而在之前分析流程的时候介绍过了,其实最终的处理都是在ActivityThread中的Handler中:
看到了,在这里就是我们最好的还原时机,那么我们就需要Hook掉这个Handler机制了,但是我们在之前介绍了Handler源码内部机制解析 知道,最终处理消息dispatchMessage方法的处理逻辑:
1、如果传递的Message本身就有callback,那么直接使用Message对象的callback方法;
2、如果Handler类的成员变量mCallback存在,那么首先执行这个mCallback回调;
3、如果mCallback的回调返回true,那么表示消息已经成功处理;直接结束。
4、如果mCallback的回调返回false,那么表示消息没有处理完毕,会继续使用Handler类的handleMessage方法处理消息
有了这个逻辑,那么我们如果要拦截Handler的handleMessage方法,只需要构造一个变量mCallback即可:
这里我们就可以自定义一个Callback,然后设置到Handler的变量中,最后在自定义的Callback中处理消息实现拦截:
到这里我们就可以看一下实现的效果了:
看到了,这里就可以完全的实现了没有声明的TargetActivity的启动,但是这里还是需要StubActivity的声明,而对于StubActivity也叫作代理Activity,后续如果在启动Activity都可以把他当做代理Activity,而这个代理最大的作用就是欺骗AMS,让AMS检测通过即可,
四、知识总结与回顾
好了,下面咋们就来总结一下拦截流程:
1、首先通过源码分析得知,在ActivityManagerNative类中有一个静态方法和静态变量维护这一个AMS本地化对象
2、那么就可以使用反射机制获取到这个AMS本地化对象,也就是ActivityManagerProxy。
3、而我们正好想要拦截的方法都是在这个对象中,所以只需要Hook掉这个对象即可,因为使用动态代理生成代理对象必须符合两个规则:
1》有原始对象,这里就是ActivityManagerProxy对象
2》原始对象必须实现一个接口,正好ActivityManagerProxy对象实现了IActivityManager接口
那么这里既可以生成一个ActivityManagerProxy的代理对象,然后在使用反射把这个对象重置回去。
4、然后就可以在InvocationHandler中进行方法拦截了,本文中只对启动方法做了拦截操作
5、我们拦截方法成功之后,就实现了一个简单的功能,对启动的Activity进行调换,也就是通过修改参数实现的。
6、最后我们要完全实现真正意义上的拦截,还得在最后把原始Activity给替换回来。这里还需要拦截系统处理Activity的生命周期逻辑的Handler机制,这里主要借助Handler本身的处理消息机制,构造一个回调,然后重置变量即可。
在这整个过程中,我们其实可以发现,首先咋们得“狸猫换太子”,骗过AMS的检测,然后在最后生不知鬼不觉的在换回来,整个过程AMS完全无感知:
五、技术用途
到这里我们就实现了Android中无需声明Activity就可以启动的效果,那么这个有什么用呢?
1、现在很多应用有时候会集成微信和支付宝支付功能,但是这时候就需要在AndroidManifest.xml中声明一些Activity,而恶心的是,有些市场在审核个人开发者提交的app的时候,如果有支付功能是不能审核通过的,这个应该也是为了防止恶意扣费吧,那么对于个人开发者就是没辙了?想想路子还是有的:
1》可以先上一个没有支付功能的,先到市场再说,然后在自己的app中自升级带有支付功能的即可,完全绕过市场审核了,但是这种方式是需要自升级工作。
2》采用插件化开发,把支付功能SDK做成动态加载,这样市场在扫描包的时候是找不到指定支付api就可以的,同时还得把AndroidManifest.xml中的支付Activity给隐藏起来躲避检测,那么如何隐藏就用到了这里的技术了,咋们可以自定义一个代理假的Activity,然后通过这种方式启动真正的支付Activity即可。
2、上面也提到了,在插件化开发中处理Activity的生命周期问题,也是可以采用这种方式去做处理的。
六、Hook技术精华总结
通过之前的两篇文章了解了如何Hook掉系统的服务,规则和方法都是大同小异,主要有两种情况:
1、如果在系统中找不到指定服务的本地化对象,即没法使用反射机制获取,就需要进行两次Hook工作,先Hook远程服务的Binder对象,在Hook掉本地化服务对象拦截指定方法。像之前的剪切板服务ClipboardManager。
2、如果能够直接使用反射机制获取到本地化服务对象,那么只需要一次Hook操作即可完成拦截,像这里的AMS服务。
关于Android中的Hook技术其实全局可以分为两种:
1、第一种是获取root权限,利用进程注入技术,修改指定函数指针,达到拦截效果,这种方式可以拦截系统所有的服务。对系统所有应用有效果。
2、无需root权限,利用反射机制和动态代理技术,达到拦截效果,这种方式只能对本应用有效果。
项目案例地址:http://download.csdn.net/detail/jiangwei0910410003/9641479
七、总结
到这里介绍完了如何Hook系统的AMS服务实现应用启动流程的拦截工作,同样的我们可以使用这种方式去Hook系统的其他服务,比如PMS服务等,当然到这里,我们全面的介绍了Android中的Hook技术实现,后续还有一个文章主要通过编译源码,添加一个系统服务,让每个应用都可以使用该服务,这个了解之后,那么对于Android中的Binder机制以及远端服务通信机制就有了非常深刻的了解了。
更多内容:点击这里
关注微信公众号,最新技术干货实时推送