1.插件化
关于插件化的原理和插件化框架之前的优缺点对比,已经有很多的文章,这里不再赘述。
2.Activity的启动
APK在安装的时候,PMS(PackageManagerService)解析Apk中的AndroidManifest.xml文件,根据Apk包路径创建一个对应的资源管理器对象AssetManager,通过该对象来访问Apk包中的资源信息,包括AndroidManifest.xml文件。
Activity的启动由AMS管理,Launcher通知AMS启动Activity,AMS通过PMS去查是否存在该Activity,若不存在,则停止了。若存在,再判断是否已经创建了进程。若未创建,则请求Zygote创建应用进程,然后启动新的进程,在进程中创建ActivityThread对象,执行其中的main函数方法,这里会创建启动主线程,再通知AMS,传入applicationThread以便通讯,AMS再通知应用启动Application,创建启动Activity。
3.插桩Activity
要启动一个Activity必须在AndroidManifest.xml中注册,如果我们要启动一个新的Activity,则必须先在发布的APK内占坑注册。这样,在启动Activity的时候,可以通过AMS的校验。然后将插件里的新Activity来代替之前占坑的Activity,去执行代码。
那么问题来了,hook技术应该hook什么,才能替换Activity。这考验对Android系统工作原理的熟悉程度。
此外,为了保证hook的稳定性,hook点一般找不容易变化的对象,比如单例、静态变量。
3.1Activity的启动分析
启动Activity调用startActivity,最终会调用startActivityForResult。
在内部调用Instrumentation的execStartActivity方法.
Instrumentation:用户监控应用程序与系统的交互。
Instrumentation的startActivityForResult在Android的7.0和8.0中代码逻辑是不同的。先看Android7.0。
Android7.0
启动Activity需先通过AMS的校验,则需要获取AMS对象。Android7.0通过
ActivityManagerNative的getDefault来获取AMS的代理对象。
getDefault通过ServiceManager得到“activity”的Service引用,也就是IBinder类型的AMS的引用。
将其封装成ActivityManagerProxy类型对象。它用来与AMS进行进程间通信。
getDefault借助Singleton类来实现单例,且它又是静态的,因此hook IActivityManager。
Android8.0
ActivityManager的getService方法,该方法得到名为“activity的”Servcie的引用,也是IBinder类型,将其转化成IActivityManager类型对象,这里使用了AIDL的方式,IActivityManager类是由AIDL工具在编译时自动生成,与AMS通信,只要集成IActivityManager.Stub并实现对象的方法就可以。因此IActivityManager就是AMS在本地的代理。因此startActivityAsUser实际上调用的是AMS的方法。
IActivityManager借助Singleton类实现单例,因此也选择IActivityManager为hook点。
4.Hook Activity的具体实现(Kotlin)
先写一个插桩Activity(OldActivity),在AndroidManifest内注册。
再写一个新的Activity(PluginActivity),不注册。
最后写一个测试类AcTestActivity,点击启动PluginActivity。
下面开始写核心代码
IActivityManager的代理类:
IActivityManagerProxy继承自InvocationHandler
class IActivityManagerProxy :InvocationHandler {
var mActivityManager:Any
companion object {
private val mMethod:String="startActivity"
private val OLD_INTENT="OLD_ACTIVITY_INTENT"
}
constructor(activityManager: Any){
this.mActivityManager=activityManager
}
@Throws(Throwable::class)
override fun invoke(proxy: Any?, method: Method?, args: Array<Any>): Any ?{
//判断是否为"startActivity"方法
if (mMethod == method?.name){
val intent:Intent
var index=0
//获取intent
for (i in args.indices){
if (args[i] is Intent){
index=i
break
}
}
intent=args[index] as Intent
//替换intent类要启动的Activity为在AndroidManifest内已经注册的Activity
val newIntent=Intent()
val packageName=BuildConfig.APPLICATION_ID
//指定插桩Activity
val componentName=ComponentName(packageName,OldActivity::javaClass.name)
newIntent.component = componentName
//获取到的intent先保存到新的intent内,后面会用到。
newIntent.putExtra(OLD_INTENT,intent)
//将新的intent赋值
args[index]=newIntent
}
return method?.invoke(mActivityManager,*(args))
}
}
利用反射,将系统原定的IActivityManager对象替换成我们自定义的代理类。
class HookUtil {
companion object {
@Throws(Throwable::class)
fun hookAMS(){
var defaultSingleton:Any
if (Build.VERSION.SDK_INT>=26){
//android8.0以上,获取hook的IActivityManagerSingleton字段
val activityManageClass=Class.forName("android.app.ActivityManager")
val filed=activityManageClass.getDeclaredField("IActivityManagerSingleton")
filed.isAccessible=true
defaultSingleton= filed.get(null)
}else{
//android 7.0以下,获取hook的gDefault字段
val activityManagerClass=Class.forName("android.app.ActivityManagerNative")
val filed=activityManagerClass.getDeclaredField("gDefault")
filed.isAccessible=true
defaultSingleton=filed.get(null)
}
//获取Singleton中的即将被替换的字段的值--mInstance
val singletonClass=Class.forName("android.util.Singleton")
val singletonClassFiled=singletonClass.getDeclaredField("mInstance")
singletonClassFiled.isAccessible=true
//通过反射,得到即将被代理的对象(即mInstance的实例)--iActivityManager
val iActivityManager=singletonClassFiled.get(defaultSingleton)
//创建代理类对象,该对象持有被代理的对象 :IActivityManagerProxy(iActivityManager)
// 我们可以在IActivityManagerProxy做一些自定义的操作,其内部持有原来的iActivityManager,
val iActivityManagerClass=Class.forName("android.app.IActivityManager")
val interfaces= arrayOf(iActivityManagerClass)
val proxy=Proxy.newProxyInstance(Thread.currentThread().contextClassLoader, interfaces,
IActivityManagerProxy(iActivityManager)) as Any
//用创建的代理类proxy对象来替换Singleton中的mInstance字段原来的对象
singletonClassFiled.set(defaultSingleton,proxy)
}
}
}
自定义Application类(记得替换AndroidManifest内的Application)
class ActivityApplication: Application() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
try {
HookUtil.hookAMS()
} catch (e:Exception){
e.printStackTrace()
}
}
}
最后点击AcTestActivity内的按钮,启动一个未注册的PluginActivity最终会启动插桩OldActivity。那么就成功“骗过”AMS启动了一个已注册的Activity。
启动新的Activity
上面讲到启动了已注册的Activity,但整个是空的,我们真正想启动的是新Activity而不是插桩Activity。那么这就要找到真正启动Activity的位置,对其进行替换改造。我们知道通过AMS校验后,AMS会通知应用可以启动Activity。AMS支持ApplicationThread的IBinder类IApplicationThread对象,用来进程间通信。
ApplicationThread的scheduleLaunchActivity方法,会调用ActivityThread的sendMessage(H.LAUNCH_ACTIVITY, r)方法。H是Activity的内部类,继承自Handler,是应用程序进程中主线程的消息管理类。Activity的生命周期都是在主线程中执行,所以这里会通过H来切换。
其中handleLaunchActivity方法最终会调到Activity的onCreate方法。
我们将H的handleMessage的msg进行替换,让其携带的消息为新的Activity。(即从代理类IActivityManagerProxy保存的intent取出来。还记得之前我们将intent内的Activity做了一次替换吗?现在再换回来!)
====未完,待续~~