1 前言
前面章节我们学习了【Android Framework系列】第5章 AMS启动流程和【Android Framework系列】第6章 AMS原理之Launcher启动流程,大概了解了AMS
的原理及启动流程,这一章节我们通过反射和动态代理对不同Android版本下的AMS
进行Hook
,实现登录页面的跳转
。
这里我们只简单介绍一下HookAMS思路和重点代码,需要详细了解的请到文末处项目地址下载查看。
1.1 实现打开统一登录页面
我们在Android的APP中,一般会有登录状态。如果非登录状态下,除了闪屏页和登录页外,其他页面打开需要先登录。往往登录状态也会有有效期的说法,如果在有效期到了跳转,我们则需要跳转到登录页面,而不是继续打开页面,这种情况下我们通过HookAMS
可以实现。
1.2 实现打开动态插件下发页面
另外通过HookAMS
还可以实现动态下发插件的功能,比如动态下发的Activity
在AndroidManifest.xml
里是没有注册,要想打开则需要通过HookAMS
的方式,使用代理页面在AndroidManifest.xml
注册,在跳转时动态切换到下发下来的插件内Activity
2 实现
2.1 实现思路
通过动态代理的方式,将AMS
中startActivity
方法拦截下来,把要跳转的意图替换成我们要打开的Activity
。由于不同的Android
版本AMS
源码有所差别,所以这里区分SDK<=23
、SDK<=28
、SDK>28
这三种情况做HookAMS
适配。下面我们来看看项目结构
2.2 项目结构
上图我们可以看到项目结构如下:
// Config 常量配置类
// HookAMSApplication Application进行HookAMS初始化
// HookAMSUtils HookAMS工具类,主要的Hook逻辑
// ListActivity 数据页面,登录后才可打开
// LoginActivity 登录页
// MainActivity 首页,这里打开数据页面
// ProxyActivity 代理页,用于欺瞒AMS,跳转时动态替换为真正Activity
首先我们来看是怎么HookAMSApplication
:
2.3 HookAMSApplication
package com.yvan.hookams;
import android.app.Application;
import android.os.Handler;
import android.os.Looper;
/**
* @author yvan
* @date 2023/7/28
* @description
*/
public class HookAMSApplication extends Application {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void onCreate() {
super.onCreate();
handler.post(this::hookAMS);
}
public void hookAMS() {
try {
HookAMSUtils hookUtils = new HookAMSUtils(this, ProxyActivity.class);
hookUtils.hookAms();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在App启动时,Application
的onCreate()
方法内通过Handler
的post
方法进行对HookAMSUtils
类的hookAms()
方法调用。为什么要使用Handler呢?初始化Application
的onCreate()
初始化还没完成,直接调hookAms()
方法会崩溃,这里加了post,将任务加入到主线程的队列里,这样就不会出现崩溃异常。
我们继续看HookAMSUtils
类:
2.4 HookAMSUtils
package com.yvan.hookams;
import static android.os.Build.VERSION.SDK_INT;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
/**
* @author yvan
* @date 2023/7/28
* @description Hook AMS工具类
*/
public class HookAMSUtils {
private static final String TAG = HookAMSUtils.class.getSimpleName();
private Context context;
private Class<?> proxyActivity;
/**
* proxyActivity 传入一个有注册在AndroidManifest的就行
*
* @param context
* @param proxyActivity
*/
public HookAMSUtils(Context context, Class<?> proxyActivity) {
this.context = context;
this.proxyActivity = proxyActivity;
}
public void hookAms() throws Exception {
if (SDK_INT <= 23) {
hookAmsFor6();
} else if (SDK_INT <= 28) {
hookAmsFor9();
} else {
hookAmsFor10();
}
hookSystemHandler();
}
public void hookAmsFor10() throws Exception {
Class<?> iActivityManagerClazz = Class.forName("android.app.IActivityTaskManager");
Class<?> clazz = Class.forName("android.app.ActivityTaskManager");
Field singletonField = clazz.getDeclaredField("IActivityTaskManagerSingleton");
singletonField.setAccessible(true);
Object singleton = singletonField.get(null);
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
final Object mInstance = mInstanceField.get(singleton);
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()
, new Class[]{iActivityManagerClazz}, new AmsInvocationHandler(mInstance));
mInstanceField.setAccessible(true);
mInstanceField.set(singleton, proxyInstance);
}
public void hookAmsFor9() throws Exception {
// 1.反射获取类>ActivityTaskManager,这个就是AMS实例
Class ActivityManagerClz = Class.forName("android.app.ActivityManager");
// 2.获取IActivityManagerSingleton,并设置访问权限
Field iActivityManagerSingletonFiled = ActivityManagerClz.getDeclaredField("IActivityManagerSingleton");
iActivityManagerSingletonFiled.setAccessible(true);
// 因为是静态变量,所以获取的到的是默认值
final Object iActivityManagerSingletonObj = iActivityManagerSingletonFiled.get(null);
// 3.现在创建我们的AMS实例
// 由于IActivityManager是一个接口,那么其实我们可以使用Proxy类来进行代理对象的创建
// 结果被摆了一道,IActivityManager这玩意居然还是个AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯
// 反射创建一个Singleton的class
Class SingletonClz = Class.forName("android.util.Singleton");
Field mInstanceField = SingletonClz.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// 4.获取AMS Proxy
Object iActivityManagerObj = mInstanceField.get(iActivityManagerSingletonObj);
// 5.获取需要实现的接口IActivityManager实现类
Class iActivityManagerClz = Class.forName("android.app.IActivityManager");
// 6.动态生成接口对象
// 构建代理类需要两个东西用于创建伪装的Intent
// 拿到AMS实例,然后用代理的AMS换掉真正的AMS,代理的AMS则是用 假的Intent骗过了 activity manifest检测.
Object proxyIActivityManager = Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class<?>[]{iActivityManagerClz}, new AmsInvocationHandler(iActivityManagerObj));
mInstanceField.setAccessible(true);
// 7.替换掉系统的变量
mInstanceField.set(iActivityManagerSingletonObj, proxyIActivityManager);
}
public void hookAmsFor6() throws Exception {
//1.反射获取类>ActivityManagerNative
Class ActivityManagerClz = Class.forName("android.app.ActivityManagerNative");
//2.获取变量>gDefault
Field IActivityManagerSingletonFiled = ActivityManagerClz.
getDeclaredField("gDefault");
//2.1 设置访问权限
IActivityManagerSingletonFiled.setAccessible(true);
//3. 获取变量的实例值
Object IActivityManagerSingletonObj = IActivityManagerSingletonFiled.get(null);
//4.获取mInstance
Class SingletonClz = Class.forName("android.util.Singleton");
Field mInstanceField = SingletonClz.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
//5.获取AMS Proxy
Object AMSProxy = mInstanceField.get(IActivityManagerSingletonObj);
//6.由于不能去手动实现IActivityManager实现类,
// 所以只能通过动态代理去动态生成实现类
//6.1 获取需要实现的接口
Class IActivityManagerClz = Class.forName("android.app.IActivityManager");
//6.2 动态生成接口对象
Object proxyIActivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{IActivityManagerClz}, new AmsInvocationHandler(AMSProxy));
mInstanceField.setAccessible(true);
//7.替换掉系统的变量
mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);
}
private class AmsInvocationHandler implements InvocationHandler {
private Object iActivityManagerObject;
public AmsInvocationHandler(Object iActivityManagerObject) {
this.iActivityManagerObject = iActivityManagerObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".contains(method.getName())) {
Intent intent = null;
int index = 0;
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof Intent) {
intent = (Intent) args[i]; // 原意图,过不了安检
index = i;
break;
}
}
Intent proxyIntent = new Intent();
ComponentName componentName = new ComponentName(context, proxyActivity);
proxyIntent.setComponent(componentName);
proxyIntent.putExtra("realIntent", intent);
//替换原有的intent为我们自己生成的,为了骗过PMS
//为跳到我们的传入的proxyActivity
args[index] = proxyIntent;
}
return method.invoke(iActivityManagerObject, args);
}
}
//上面的主要是替换成我们自己的intent,骗过系统
//下面的主要是将我们上面替换的intent中,取出我们真正的意图(也就是正在要启动的Activity)
//
//下面是为了拿到mH对象,但是mH是一个非static 的值,那我们就只能拿到他的持有对象,也就是ActivityThread
//正好发现在ActivityThread类中有一个static变量sCurrentActivityThread值可以拿到ActivityThread类,那我们就从他入手
public void hookSystemHandler() throws Exception {
//1.反射ActivityThread
Class ActivityThreadClz = Class.forName("android.app.ActivityThread");
//2. 获取sCurrentActivityThread 是一个static变量
Field field = ActivityThreadClz.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
//3.获取ActivityThread对象
Object ActivityThreadObj = field.get(null);
//4.通过ActivityThreadObj获取到mH变量
Field mHField = ActivityThreadClz.getDeclaredField("mH");
mHField.setAccessible(true);
//5.获取到mH的对象
Handler mHObj = (Handler) mHField.get(ActivityThreadObj);//ok,当前的mH拿到了
//到这里,获取到mH的对象了,那我们怎么去监听他的方法调用呢?
//能不能通过动态代理?不能,因为它不是个接口
//由于在Handler的源码中,我们知道如果mCallback如果不等于空,就会调用mCallback的handleMessage方法。
//6.获取mH的mCallback
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
//7.创建我们自己的Callback,自己处理handleMessage
Handler.Callback proxyMHCallback = getMHCallback();
//8.给系统的mH(Handler)的mCallback设值(proxyMHCallback)
mCallbackField.set(mHObj, proxyMHCallback);
}
private Handler.Callback getMHCallback() {
if (SDK_INT <= 23) {
return new ProxyHandlerCallbackFor6();
} else if (SDK_INT <= 28) {
return new ProxyHandlerCallbackFor();
} else {
return new ProxyHandlerCallbackFor();
}
}
private class ProxyHandlerCallbackFor6 implements Handler.Callback {
private int LAUNCH_ACTIVITY = 100;
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
try {
Class ActivityClientRecord = Class.forName("android.app.ActivityThread$ActivityClientRecord");
//判断传过来的值(msg.obj)是不是ClientTransaction对象
if (!ActivityClientRecord.isInstance(msg.obj)) return false;
//获取ActivityClientRecord的intent变量
Field intentField = ActivityClientRecord.getDeclaredField("intent");
intentField.setAccessible(true);
if (intentField == null) return false;
Intent mIntent = (Intent) intentField.get(msg.obj);
if (mIntent == null) return false;
//获取我们之前传入的realIntent,也就是我们真正要打开的Activity
Intent realIntent = mIntent.getParcelableExtra("realIntent");
if (realIntent == null) {
return false;
}
realStartActivity(mIntent, realIntent);
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
}
private class ProxyHandlerCallbackFor implements Handler.Callback {
private int EXECUTE_TRANSACTION = 159;
@Override
public boolean handleMessage(Message msg) {
if (msg.what == EXECUTE_TRANSACTION) {
try {
Class ClientTransactionClz = Class.forName("android.app.servertransaction.ClientTransaction");
//判断传过来的值(msg.obj)是不是ClientTransaction对象
if (!ClientTransactionClz.isInstance(msg.obj)) return false;
Class LaunchActivityItemClz = Class.forName("android.app.servertransaction.LaunchActivityItem");
//获取ClientTransaction的mActivityCallbacks变量
Field mActivityCallbacksField = ClientTransactionClz
.getDeclaredField("mActivityCallbacks");//ClientTransaction的成员
//设值成
mActivityCallbacksField.setAccessible(true);
//获取到ASM传递过来的值(ClientTransaction对象)里的mActivityCallbacks变量
Object mActivityCallbacksObj = mActivityCallbacksField.get(msg.obj);
List list = (List) mActivityCallbacksObj;
if (list.size() == 0) return false;
Object LaunchActivityItemObj = list.get(0);
if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return false;
//获取mIntent变量
Field mIntentField = LaunchActivityItemClz.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
//获取mIntent对象
Intent mIntent = (Intent) mIntentField.get(LaunchActivityItemObj);
//获取我们之前传入的realIntent,也就是我们真正要打开的Activity
Intent realIntent = mIntent.getParcelableExtra("realIntent");
if (realIntent == null) {
return false;
}
realStartActivity(mIntent, realIntent);
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
}
private void realStartActivity(Intent mIntent, Intent realIntent) {
//登录判断
SharedPreferences share = context.getSharedPreferences(Config.SP_NAME,
Context.MODE_PRIVATE);
if (share.getBoolean(Config.SP_KEY_LOGIN, false)) {
mIntent.setComponent(realIntent.getComponent());
} else {
Log.i(TAG, "handleLauchActivity: " + realIntent.getComponent().getClassName());
ComponentName componentName = new ComponentName(context, LoginActivity.class);
mIntent.putExtra("extraIntent", realIntent.getComponent()
.getClassName());
mIntent.setComponent(componentName);
}
}
}
从上面代码我们能看到:
hookAms()
方法分别是SDK<=23、SDK<=28、SDK>28三种情况进行HookAMS,其实都是大同小异。实际上是获取到IActivityManager
对象,通过动态代理Proxy.newProxyInstance()
Hook到其所有方法,通过AmsInvocationHandler
进行方法调用的回调。hookSystemHandler()
方法在hookAms()
方法调用后立刻执行,通过反射获取android.app.ActivityThread
类对象的sCurrentActivityThread
属性和Handler
实例mH
,将Handler的回调handleMessage()
方法进行拦截。根据SDK<=23、SDK<=28、SDK>28三种情况不同来区别处理。- 1中的
AmsInvocationHandler
负责hookAms()
内Hook到的方法调用的处理,在Hook
到的Callback
中判断为startActivity()
方法则拦截下来,将我们真正要跳转的Activity意图存在Extra内,由于在原来的Intent中隐藏了真正的Activity意图,所以只需要将真正的意图拿出来替换将其Intent
的意图替换为要打开的Activity
。 - 2中
handleMessage()
其实就是将startActivity()
方法进行拦截,判断如果是未登录状态,则将真正要跳转的Activity
从Extra
内拿出来进行跳转,已登录则不替换Intent意图跳转。
3 总结
文章只做核心HookAMS
代码思路的分析,这里是项目地址,小伙伴可以自行下载查看,别忘了点Star喔,谢谢!!