【SDK热更系列】Android Hook 技术之 绕过系统对Activity验证
前言:我为啥要搞这玩意儿呢?原因如下:
1. 需求,还是xxxx的需求问题
2. 公司主要的SDK项目需要实现SDK级别的热更新功能
3. 市场上所有的组件化、插件化类的项目有如下缺点
1. 对主项目工程的依赖太大, 往往一些基本配置都要依赖于主要工程的项目源码才行
2. 使用接入成本高:配置麻烦,而SDK接入方需要的是快速接入
3. 插件化框架对系统原生代码的运行会造成不可预估的影响
4. 有很多不需要的功能,但是属于框架的部分,不能不依赖
基于以上原因,我打算自己搞一个这种简易型,没有这么多负责功能的SDK热更框架,而SDK热更无可避免的遇到Activity必须要在AndroidManifest.xml注册才能使用的阻碍,今天我们就来解决这个坑点
主要思路是我们预先在AndroidManifest.xml 注册一个代理的Activity,如下所示
<activity
android:name="com.pudding.hook.proxy.GenProxyActivity"
android:configChanges="orientation|keyboardHidden|navigation|screenSize"/>
但是我们要启动的Activity 是 UserCenterActivity,系统理论上是要去AndroidManifest.xml去找UserCenterActivity是否有注册的,这个时候我们将系统去寻找UserCenterActivity 这个动作导向 去寻找GenProxyActivity是否存在,这样就绕过了系统的Activity验证。主要采用的技术是Hook技术
1. 分析Activity启动源码
在我们的Activity中调用startActivity方法,会执行Activity中的startActivity, 然后在Activity中的startActivity方法体里调用了startActivity的重载方法,这里我们看一下其重载方法的实现:
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}
好吧,我们继续往下看,startActivityForResult方法的具体实现:
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (mParent == null) {
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());
}
if (requestCode >= 0) {
// If this start is requesting a result, we can avoid making
// the activity visible until the result is received. Setting
// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
// activity hidden during this time, to avoid flickering.
// This can only be done when a result is requested because
// that guarantees we will get information back when the
// activity is finished, no matter what happens to it.
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
// TODO Consider clearing/flushing other event sources and events for child windows.
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// Note we want to go through this method for compatibility with
// existing applications that may have overridden it.
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
可以发现由于我们是第一次启动Activity,所以这里的mParent为空,所以会执行if分之,然后调用mInstrumentation.execStartActivity方法,并且这里需要注意的是,有一个判断逻辑:
if (requestCode >= 0) {
mStartedActivity = true;
}
通过注释也验证了我们刚刚的说法即,调用startActivityForResult的时候只有requestCode的值大于等于0,onActivityResult才会被回调。
然后我们看一下mInstrumentation.execStartActivity方法的实现。在查看execStartActivity方法之前,我们需要对mInstrumentation对象有一个了解?什么是Instrumentation?Instrumentation是android系统中启动Activity的一个实际操作类,也就是说Activity在应用进程端的启动实际上就是Instrumentation执行的,那么为什么说是在应用进程端的启动呢?实际上acitivty的启动分为应用进程端的启动和SystemServer服务进程端的启动的,多个应用进程相互配合最终完成了Activity在系统中的启动的,而在应用进程端的启动实际的操作类就是Intrumentation来执行的,可能还是有点绕口,没关系,随着我们慢慢的解析大家就会对Instrumentation的认识逐渐加深的。
可以发现execStartActivity方法传递的几个参数:
this,为启动Activity的对象;
contextThread,为Binder对象,是主进程的context对象;
token,也是一个Binder对象,指向了服务端一个ActivityRecord对象;
target,为启动的Activity;
intent,启动的Intent对象;
requestCode,请求码;
options,参数;
这样就调用了Imstrument.execStartActivity方法了:
ublic ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
...
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess();
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
我们发现在这个方法中主要调用ActivityManagerNative.getDefault().startActivity方法,那么ActivityManagerNative又是个什么鬼呢?查看一下getDefault()对象的实现:
static public IActivityManager getDefault() {
return gDefault.get();
}
好吧,相当之简单直接返回的是gDefault.get(),那么gDefault又是什么呢?
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
可以发现启动过asInterface()方法创建,然后我们继续看一下asInterface方法的实现:
static public IActivityManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IActivityManager in =
(IActivityManager)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new ActivityManagerProxy(obj);
}
由以上代码我们可以知道我们实际上是要找到 ActivityManagerNative里面的gDefault,然后替换他的mInstance 属性,达到真正去替换启动Activity的目的, 替换代码如下:
public void hookStartActivity() throws Exception {
if (Build.VERSION.SDK_INT < 26) {
// 3.1 获取ActivityManagerNative里面的gDefault
Class<?> amnClass = Class.forName("android.app.ActivityManagerNative");
Object gDefault;
Field gDefaultField = amnClass.getDeclaredField("gDefault");
// 设置权限
gDefaultField.setAccessible(true);
gDefault = gDefaultField.get(null);
// 3.2 获取gDefault中的mInstance属性
// mInstanceField 就是 android.util.Singleton 对象中的 mInstance 属性,此时还是一个属性
// mInstance 属性 实际上是一个 类别为 IActivityManager 的属性
// iamInstance 则是从 mInstance 属性中获取到的 IActivityManager 对象
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object iamInstance = mInstanceField.get(gDefault);
// 重新依照原来的 iamInstance(IActivityManager 对象) 定义一个能够被我们自己 Hook 指向的 iamInstance
Class<?> iamClass = Class.forName("android.app.IActivityManager");
iamInstance = Proxy.newProxyInstance(HookStartActivityUtil.class.getClassLoader(),
new Class[]{iamClass},
// InvocationHandler 必须执行者,谁去执行
new StartActivityInvocationHandler(iamInstance));
// 3.重新指定
// 实际上是要把原来的 IActivityManager 对象替换成上面我们自己定义可以支持 Hook 的新 IActivityManager 对象
mInstanceField.set(gDefault, iamInstance);
} else {
// 3.1 获取ActivityManager里面的IActivityManagerSingleton
Class<?> amnClass = Class.forName("android.app.ActivityManager");
Object gDefault;
Field gDefaultField = amnClass.getDeclaredField("IActivityManagerSingleton");
// 设置权限
gDefaultField.setAccessible(true);
gDefault = gDefaultField.get(null);
// 3.2 获取gDefault中的mInstance属性
// mInstanceField 就是 android.util.Singleton 对象中的 mInstance 属性,此时还是一个属性
// mInstance 属性 实际上是一个 类别为 IActivityManager 的属性
// iamInstance 则是从 mInstance 属性中获取到的 IActivityManager 对象
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object iamInstance = mInstanceField.get(gDefault);
// 重新依照原来的 iamInstance(IActivityManager 对象) 定义一个能够被我们自己 Hook 指向的 iamInstance
Class<?> iamClass = Class.forName("android.app.IActivityManager");
iamInstance = Proxy.newProxyInstance(HookStartActivityUtil.class.getClassLoader(),
new Class[]{iamClass},
// InvocationHandler 必须执行者,谁去执行
new StartActivityInvocationHandler(iamInstance));
// 3.重新指定
// 实际上是要把原来的 IActivityManager 对象替换成上面我们自己定义可以支持 Hook 的新 IActivityManager 对象
mInstanceField.set(gDefault, iamInstance);
}
}
接下来替换我们启动的Activty class,让系统去验证另外一个Activity对象在AndroidManifest.xml是否存在,类似360插件化框架的占坑思想
private class StartActivityInvocationHandler implements InvocationHandler {
// 方法执行者
private Object mObject;
public StartActivityInvocationHandler(Object object) {
this.mObject = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.e(TAG, method.getName());
// 替换Intent,过AndroidManifest.xml检测
if (method.getName().equals("startActivity")) {
// 1.首先获取原来的Intent
if (args[2] instanceof Intent) {
Intent originIntent = (Intent) args[2];
if (originIntent.getComponent() != null) {
Log.e(TAG, originIntent.getComponent().getClassName());
// 2.创建一个安全的可以被启动的Activity 的 Class
Class mProxyClass = ProxyUtils.getPluginActivityClass(originIntent.getComponent().getClassName());
if (mProxyClass != null) {
Intent safeIntent = new Intent(mContext, mProxyClass);
args[2] = safeIntent;
// 3.绑定原来的Intent
safeIntent.putExtra(EXTRA_ORIGIN_INTENT, originIntent);
}
}
}
}
return method.invoke(mObject, args);
}
}
基本思路如上所示,经过试验可以使用,Android版本支持情况:
Runtime | Android Version | Support |
---|---|---|
Dalvik | 2.2 | Yes |
Dalvik | 2.3 | Yes |
Dalvik | 3.0 | Yes |
Dalvik | 4.0-4.4 | Yes |
ART | L (5.0) | Yes |
ART | L MR1 (5.1) | Yes |
ART | M (6.0) | Yes |
ART | N (7.0) | Yes |
ART | N MR1 (7.1) | Yes |
ART | O (8.0) | Yes |
ART | O MR1(8.1) | Yes |
Demo献上:https://download.csdn.net/download/u012573920/10549527