Android插件化之Hook Activity
插件化:Android插件化技术,可以实现功能模块的按需加载和动态更新(从服务器上下载),其本质是动态加载未安装的apk。从而减小apk的大小。其中最主要的就是Activity的插件化技术,主要是通过hook来实现的。因为Activity的启动是要经过AMS的校验的,所以就需要对AMS下功夫。
-
Step1: 在宿主工程的AndroidManifest.xml中预先注册一个没有任何功能的Activity进行占坑。
-
Step2.:使用占坑Activity绕过AMS验证:Activity的启动,实际会调用
Instrumentation
类的execStartActvity
方法,所以可以对其进行hook,将启动插件Activity的Intent替换成宿主预注册的插桩Activity,从而绕过ASM的验证,并将插件Activity的Intent保存起来。 -
Step3: 还原插件Activity:在 AMS 校验完毕的时候,通过 binder 告知我们的应用启动相应 activity 的时候,我们将 插件Activity的intent 的信息再取出来,还原。
本文以 API 27 的源码为基础分析
Hook 的选择点:
静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。
Hook 过程:
1、寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法。
选择合适的代理方式,如果是接口可以用动态代理。
2、用代理对象替换原始对象。
3、Android 的 API 版本比较多,方法和类可能不一样,所以要做好 API 的兼容工作。
Hook AMS
我们先来 mInstrumentation.execStartActivity 方法
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
result = am.onStartActivity(intent);
}
if (result != null) {
am.mHits++;
return result;
} else if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
}
break;
}
}
}
}
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);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
这里我们留意 ActivityManager.getService().startActivity 这个方法
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
可以看到 IActivityManagerSingleton 是一个单例对象,因此,我们可以 hook 它。
public static void hookAMSAfter26() throws Exception {
// 第一步:获取 IActivityManagerSingleton
Class<?> aClass = Class.forName("android.app.ActivityManager");
Field declaredField = aClass.getDeclaredField("IActivityManagerSingleton");
declaredField.setAccessible(true);
Object value = declaredField.get(null);
Class<?> singletonClz = Class.forName("android.util.Singleton");
Field instanceField = singletonClz.getDeclaredField("mInstance");
instanceField.setAccessible(true);
Object iActivityManagerObject = instanceField.get(value);
// 第二步:获取我们的代理对象,这里因为 IActivityManager 是接口,我们使用动态代理的方式
Class<?> iActivity = Class.forName("android.app.IActivityManager");
InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new
Class<?>[]{iActivity}, handler);
// 第三步:偷梁换柱,将我们的 proxy 替换原来的对象
instanceField.set(value, proxy);
}
public class AMSInvocationHandler implements InvocationHandler {
private static final String TAG = "AMSInvocationHandler";
Object iamObject;
public AMSInvocationHandler(Object iamObject) {
this.iamObject = iamObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Log.e(TAG, method.getName());
if ("startActivity".equals(method.getName())) {
Log.i(TAG, "ready to startActivity");
for (Object object : args) {
Log.d(TAG, "invoke: object=" + object);
}
}
return method.invoke(iamObject, args);
}
}
执行以下测试代码
try {
HookHelper.hookAMS();
} catch (Exception e) {
e.printStackTrace();
}
Intent intent = new Intent(this,TestActivity.class);
startActivity(intent);
接下来我们一起来看一下 API 25 Instrumentation 的代码(自 API 26 开始 ,Instrumentation execStartActivity 方法有所改变)
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
Uri referrer = target != null ? target.onProvideReferrer() : null;
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
if (am.match(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult() : null;
}
break;
}
}
}
}
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
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;
}
可以看到这里启动 activity 是调用 ActivityManagerNative.getDefault().startActivity 启动的。
public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
/**
* Retrieve the system's default/global activity manager.
*/
static public IActivityManager getDefault() {
return gDefault.get();
}
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;
}
};
}
同理我们看到 ActivityManagerNative 的 gDefault 是一个静态变量,因此,我们可以尝试 hook gDefault.
public static void hookAmsBefore26() throws Exception {
// 第一步:获取 IActivityManagerSingleton
Class<?> forName = Class.forName("android.app.ActivityManagerNative");
Field defaultField = forName.getDeclaredField("gDefault");
defaultField.setAccessible(true);
Object defaultValue = defaultField.get(null);
Class<?> forName2 = Class.forName("android.util.Singleton");
Field instanceField = forName2.getDeclaredField("mInstance");
instanceField.setAccessible(true);
Object iActivityManagerObject = instanceField.get(defaultValue);
// 第二步:获取我们的代理对象,这里因为 IActivityManager 是接口,我们使用动态代理的方式
Class<?> iActivity = Class.forName("android.app.IActivityManager");
InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{iActivity}, handler);
// 第三步:偷梁换柱,将我们的 proxy 替换原来的对象
instanceField.set(defaultValue, proxy);
}
启动一个没有在 AndroidManifest 声明的 Activity
我们知道我们启动的 activity 信息都储存在 intent 中,那么我们若想要 启动一个没有在 AndroidManifest 声明的 Activity,那我们只需要在 某个时机,即调用 startActivity 方法之前欺骗 AMS ,我们的 activity 已经注册(即替换 intent)。
这里我们重新理一下 Activity 大概的启动流程:
app 调用 startActivity 方法 -> Instrumentation 类通过 ActivityManagerNative 或者 ActivityManager( API 26以后)将启动请求发送给 AMS -> AMS 进行一系列检查并将此请求通过 Binder 派发给所属 app -> app 通过 Binder 收到这个启动请求 -> ActivityThread 中的实现将收到的请求进行封装后送入 Handler -> 从 Handler 中取出这个消息,开始 app 本地的 Activity 初始化和启动逻辑。
public class HookHelper {
private static final String TAG = "xiaosanye";
public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
public static void hookIActivityManager() {
//TODO:
// 1. 找到了Hook的点
// 2. hook点 动态代理 静态?
// 3. 获取到getDefault的IActivityManager原始对象
// 4. 动态代理 准备classloader 接口
// 5 classloader, 获取当前线程
// 6. 接口 Class.forName("android.app.IActivityManager");
// 7. Proxy.newProxyInstance() 得到一个IActivityManagerProxy
// 8. IActivityManagerProxy融入到framework
try {
Field gDefaultField = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Class<?> activityManager = Class.forName("android.app.ActivityManager");
gDefaultField = activityManager.getDeclaredField("IActivityManagerSingleton");
} else {
Class<?> activityManager = Class.forName("android.app.ActivityManagerNative");
//拿到 Singleton<IActivityManager> gDefault
gDefaultField = activityManager.getDeclaredField("gDefault");
}
gDefaultField.setAccessible(true);
//Singlon<IActivityManager>
//所有静态对象的反射可以通过传null获取。如果是实列必须传实例
Object gDefault = gDefaultField.get(null);
//拿到Singleton的Class对象
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
//获取到ActivityManagerNative里面的gDefault对象里面的原始的IActivityManager对象
final Object rawIActivityManager = mInstanceField.get(gDefault);
//进行动态代理
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
//生产IActivityManager的代理对象
Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{iActivityManagerInterface}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i(TAG, "invoke: method " + method.getName());
if ("startActivity".equals(method.getName())) {
Log.i(TAG, "准备启动activity");
for (Object obj : args) {
Log.i(TAG, "invoke: obj= " + obj);
}
//偷梁换柱 把Target 换成我们的Stub,欺骗AMS的权限验证
//拿到原始的Intent,然后保存
Intent raw = null;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
raw = (Intent) args[i];
index = i;
break;
}
}
Log.i(TAG, "invoke: raw= " + raw);
//替换成Stub
Intent newIntent = new Intent();
String stubPackage = "com.xiaosanye.activityhookdemo";
newIntent.setComponent(new ComponentName(stubPackage, StubActivity.class.getName()));
//把这个newIntent放回到args,达到了一个欺骗AMS的目的
newIntent.putExtra(EXTRA_TARGET_INTENT, raw);
args[index] = newIntent;
}
return method.invoke(rawIActivityManager, args);
}
});
//把我们的代理对象融入到framework
mInstanceField.set(gDefault, proxy);
} catch (Exception e) {
Log.e(TAG, "hookIActivityManager: " + e.getMessage());
e.printStackTrace();
}
}
/**
* hook ActivityThread 的 mH,还原未注册的Activity
*/
public static void hookHandler() {
//TODO:
try {
Class<?> atClass = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField = atClass.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object sCurrentActivityThread = sCurrentActivityThreadField.get(null);
//ActivityThread 一个app进程 只有一个,获取它的mH
Field mHField = atClass.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mH = (Handler) mHField.get(sCurrentActivityThread);
//获取mCallback
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.i(TAG, "handleMessage: " + msg.what);
switch (msg.what) {
case 100: {
// final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
// static final class ActivityClientRecord {
// IBinder token;
// int ident;
// Intent intent;//hook 恢复
//恢复真身
try {
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(msg.obj);
Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT);
intent.setComponent(targetIntent.getComponent());
} catch (Exception e) {
e.printStackTrace();
}
}
break;
case 159: {
Object obj = msg.obj;
Log.i(TAG, "handleMessage: obj=" + obj);
try {
Field mActivityCallbacksField = obj.getClass().getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
List mActivityCallbacks = (List)mActivityCallbacksField.get(obj);
Log.i(TAG, "handleMessage: mActivityCallbacks= " + mActivityCallbacks);
if(mActivityCallbacks.size()>0){
Log.i(TAG, "handleMessage: size= " + mActivityCallbacks.size());
String className = "android.app.servertransaction.LaunchActivityItem";
if(mActivityCallbacks.get(0).getClass().getCanonicalName().equals(className)){
Object object = mActivityCallbacks.get(0);
Field intentField =object.getClass().getDeclaredField("mIntent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(object);
Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT);
intent.setComponent(targetIntent.getComponent());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
break;
}
mH.handleMessage(msg);
return true;
}
});
} catch (Exception e) {
Log.e(TAG, "hookHandler: " + e.getMessage());
e.printStackTrace();
}
}
}
public void onBtnHookClicked() {
HookHelper.hookIActivityManager();
HookHelper.hookHandler();
Intent intent = new Intent(this,TestActivity.class);
startActivity(intent);
}
运行以上代码,可以看到我们可以正常启动没有在 AndroidManifest 的 activity
小结
启动没有在 AndroidManifest 注册的 Activity 可以分为连个步骤
- 1、在 AMS 通过 intent 校验 activity 是否注册的时候,用已经在 AndroidManifet 注册的 Activity 欺骗 AMS,绕过 原有 activity 的校验,并将原有的 intent 信息储存起来
- 2、在 AMS 校验完毕的时候,通过 binder 告知我们的应用启动相应 activity 的时候,我们将 intent 的信息取出来,还原。
参考文章:https://blog.csdn.net/gdutxiaoxu/article/details/81459910