前言
本文主要是记录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
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方法,那么进入到对应的类中的这个方法
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方法 ,那么我们看一下这个方法
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 这个方法
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()
/**
* @hide
*/
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton IActivityManagerSingleton =
new Singleton() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
这里我在贴一下这个singleton这个类
/**
* Singleton helper class for lazily initialization.
*
* Modeled after frameworks/base/include/utils/Singleton.h
*
* @hide
*/
public abstract class Singleton {
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,就是一个接口
ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options)
这里就是拿到接口的对象,然后调用接口的startActivity方法,之后代码会进入AMS的startActivity方法中
//这里是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的对象于是我们就有了如下代码
val mActivityManagerClass = Class.forName("android.app.ActivityManager")
val mIActivityManager = mActivityManagerClass.getMethod("getService").invoke(null)
这样就拿到了IActivityManager接口的对象,接下来使用动态代理
//这个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字段,就达到了替换的效果)
/**
* 为了拿到 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) // 替换
这里稍微说一下反射注意的点,反射的关键点就是需要静态变量(看客姥爷们如果看不懂上面的,就复习一下反射)。
下面贴一下完整的代码
/**
* 要在执行 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了
val intent = Intent(this, TestActivity::class.java)
startActivity(intent)
这里的TestActivity并没有注册,但是程序不会崩溃,他会跳转到ProxyActivity中(想想我们替换的过程)ProxyActivity一定是注册的Activity
if ("startActivity" == method.name) {
// 做自己的业务逻辑
// 换成 可以 通过 AMS检查的 ProxyActivity
val intent = Intent(this, ProxyActivity::class.java)
intent.putExtra("actionIntent", args[2] as Intent)
args[2] = intent
}