
前言
Android Hook 插件化技术已经不再新奇,你是否想过中的小软件,如淘票票、火车票等,是否都是支付宝自己编写的?这显然是不可能的,否则需要十年的开发时间,软件体积可能达到几十G。实际上,游戏中的皮肤包也是根据用户需求下载的。
一、未在配置文件中注册的Activity可以启动吗?
学习Android时,我们知道Activity必须在配置文件中注册,否则无法启动并会报错。然而,Hook技术告诉我们,未在配置文件中注册的Activity也可以启动,这是否让你感到惊讶?
通过本文你可以学到:
-
通过对startActivity方法进行Hook,实现为startActivity方法添加日志。
1.1 通过对Instrumentation进行Hook
1.2 通过对AMN进行Hook
-
如何启动一个未在配置文件中注册的Activity实现插件化
本文的基础建立在Java反射机制和App启动流程解析之上,建议不了解的朋友先阅读相关文章。
二、对startActivity方法进行Hook
通过查阅startActivity的源码,可以发现startActivity最终都会调用startActivityForResult方法。
通过mInstrumentation.execStartActivity调用(详细的源码解析已在上篇文章中讲解),再看mInstrumentation.execStartActivity方法源码如下:
最终会交给 int var16 = ActivityManagerNative.getDefault().startActivity(whoThread, intent,…处理,所以如果我们想对startActivity方法进行Hook,可以从这两个地方入手(其实不止这两个地方,我们只讲解这两个地方,下面使用的反射封装类也在上篇文章中给出)。
2.1 对mInstrumentation进行Hook
在Activity.class类中定义了私有变量
我们首先要做的就是修改这个私有变量的值,在执行方法前打印一行日志,首先我们通过反射来获取这一私有变量。
我们要做的是将这个Instrumentation替换成我们自己的Instrumentation,所以下面我们新建MyInstrumentation继承自Instrumentation,并且MyInstrumentation的execStartActivity方法不变。
我们直接通过反射调用这个方法,参数就是 Class[] classes = {Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class}与方法名中一致
如果我们这里不调用它本身的execStartActivity方法的话,那么startActivity就无效了。
然后我们将自定义的替换为原来的Instrumentation
完整代码就是
运行日志如下:
这个时候我们就成功的Hook了startActivity方法
2.2 对AMN进行Hook
execStartActivity方法最终会走到ActivityManagerNative.getDefault().startActivity方法
你可能会说上面说过了啊,替换ActivityManagerNative.getDefault(),重写startActivity方法,我们来看ActivityManagerNative.getDefault()
可以看出IActivityManager是一个接口,gDefault.get()返回的是一个泛型,上述方案我们无法入手,所以我们这里要用方案
我们定义一个AmsHookHelperUtils类,在AmsHookHelperUtils类中处理反射代码
gDefault是个final静态类型的字段,首先我们获取gDefault字段
gDefault是 Singleton 类型的对象,Singleton是一个单例模式
接下来我们来取出mInstance字段
然后创建一个代理对象
我们的代理对象就是new AMNInvocationHanlder(mInstance),(ps:代理模式分为静态代理和动态代理,如果对代理模式不了解可以一波,也可以关注我,等待我的代理模式相关文章)
所有的代理类都要实现InvocationHandler接口,在invoke方法中method.invoke(target,args);表示的就是 执行被代理对象所对应的方法。因为AMN Singleton做的事情比较多,所以这里只对startActivity方法hook
然后我们将gDefault字段替换为我们的代理类
AmsHookHelperUtils方法整体如下:
我们调用AmsHookHelperUtils.hookAmn();然后启动一个新的Activity,运行日志如下:
这样我们就成功Hook了AMN的getDefault方法。
2.3 如何启动一个未注册的Activity
如何启动一个未注册的Activity,首先我们了解Activity的启动流程,App的启动流程已经在上篇文章中讲解了,,还不了解的小伙伴,可先移步至上篇文章。假设现在MnActivity,Main2Activity,Main3Activity,其中Main3Activity未注册,我们在MainActivity中启动Main3Activity,当启动Main3Activity的时候,AMS会在配置文件中检查,是否有Main3Activity的配置信息如果不存在则报错,存在则启动Main3Activity。
所以我们可以做的是,将要启动的Activity发送给AMS之前,将要启动的Activity替换未已经注册Activity Main2Activity,这样AMS就可以检验通过,当AMS要启动目标Activity的时候再将Main2Activity替换为真正要启动的Activity。
首先我们按照上面逻辑先对startActivity方法进行Hook,这里采用对AMN Hook的方式。和上述代码一样,不一样的地方在于mInstance的代理类不同。
新建一个AMNInvocationHanlder1对象同样继承自InvocationHandler,只拦截startActivity方法。
在这里我们要做的就是将要启动的Main3Activity替换为Main2Activity,这样能绕过AMS的检验,首先我们从目标方法中取出目标Activity。
你可能会问你怎么知道args中一定有intent类的参数,因为invoke方法中最终会执行
表示会执行原本的方法,而我们来看原本的startActivity方法如下:
所以我们说args中肯定有个intent类型的参数,获取真实目标Activity之后,我们获取目标的包名
新建一个Intent 将intent设置为 冒充者Main2Activity的相关信息
这样目标Activity就被替换成了Main2Activity,不过这个冒充者还要将原本的目标携带过去,等待真正打开的时候再替换回来,否则就真的启动这个冒充者了
这个时候我们调用这个方法什么都不做,这个时候启动Main3Activity
显示的其实是Main2Activity,如图所示:
这样说明我们的冒充者已经成功替换了真实目标,所以我们接下来要在启动的时候,将冒充者再重新替换为目标者,ActivityThread通过mH发消息给AMS
AMS收到消息后进行处理
mH是Handler类型的消息处理类,所以sendMessage方法会调用callback,Handler消息处理机制可看我之前一篇博客
深入理解Android消息机制,所以我们可以对callback字段进行Hook。
新建hookActivityThread方法,首先我们获取当前的ActivityThread对象
然后获取对象的mH对象
将mH替换为我们的自己自定义的MyCallback。
自定义MyCallback首先 Handler.Callback接口,重新处理handleMessage方法
我们获取传递过来的目标对象
然后从目标对象中取出携带过来的真实对象,并将intent修改为真实目标对象的信息,这样就可以启动真实的目标Activity
MyCallback如下
这个时候再启动未注册的Main3Activity,就可以成功启动了
这样我们就成功的启动了未注册Activity
以上就是Android Hook告诉你 如何启动未注册的Activity的详细内容,更多请关注php中文网其它相关文章!