【Android Framework系列】第9章 AMS之Hook实现登录页跳转

1 前言

前面章节我们学习了【Android Framework系列】第5章 AMS启动流程和【Android Framework系列】第6章 AMS原理之Launcher启动流程,大概了解了AMS的原理及启动流程,这一章节我们通过反射和动态代理对不同Android版本下的AMS进行Hook实现登录页面的跳转

这里我们只简单介绍一下HookAMS思路和重点代码,需要详细了解的请到文末处项目地址下载查看。

1.1 实现打开统一登录页面

我们在Android的APP中,一般会有登录状态。如果非登录状态下,除了闪屏页和登录页外,其他页面打开需要先登录。往往登录状态也会有有效期的说法,如果在有效期到了跳转,我们则需要跳转到登录页面,而不是继续打开页面,这种情况下我们通过HookAMS可以实现。

1.2 实现打开动态插件下发页面

另外通过HookAMS还可以实现动态下发插件的功能,比如动态下发的ActivityAndroidManifest.xml里是没有注册,要想打开则需要通过HookAMS的方式,使用代理页面在AndroidManifest.xml注册,在跳转时动态切换到下发下来的插件内Activity

2 实现

2.1 实现思路

通过动态代理的方式,将AMSstartActivity方法拦截下来,把要跳转的意图替换成我们要打开的Activity。由于不同的Android版本AMS源码有所差别,所以这里区分SDK<=23SDK<=28SDK>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());@Overridepublic 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启动时,ApplicationonCreate()方法内通过Handlerpost方法进行对HookAMSUtils类的hookAms()方法调用。为什么要使用Handler呢?初始化ApplicationonCreate()初始化还没完成,直接调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的classClass SingletonClz = Class.forName("android.util.Singleton");Field mInstanceField = SingletonClz.getDeclaredField("mInstance");mInstanceField.setAccessible(true);// 4.获取AMS ProxyObject 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.反射获取类>ActivityManagerNativeClass ActivityManagerClz = Class.forName("android.app.ActivityManagerNative");//2.获取变量>gDefaultField IActivityManagerSingletonFiled = ActivityManagerClz.getDeclaredField("gDefault");//2.1 设置访问权限IActivityManagerSingletonFiled.setAccessible(true);//3. 获取变量的实例值Object IActivityManagerSingletonObj = IActivityManagerSingletonFiled.get(null);//4.获取mInstanceClass SingletonClz = Class.forName("android.util.Singleton");Field mInstanceField = SingletonClz.getDeclaredField("mInstance");mInstanceField.setAccessible(true);//5.获取AMS ProxyObject 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;}@Overridepublic 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//为跳到我们的传入的proxyActivityargs[index] = proxyIntent;}return method.invoke(iActivityManagerObject, args);}}//上面的主要是替换成我们自己的intent,骗过系统//下面的主要是将我们上面替换的intent中,取出我们真正的意图(也就是正在要启动的Activity)////下面是为了拿到mH对象,但是mH是一个非static 的值,那我们就只能拿到他的持有对象,也就是ActivityThread//正好发现在ActivityThread类中有一个static变量sCurrentActivityThread值可以拿到ActivityThread类,那我们就从他入手public void hookSystemHandler() throws Exception {//1.反射ActivityThreadClass 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的mCallbackField mCallbackField = Handler.class.getDeclaredField("mCallback");mCallbackField.setAccessible(true);//7.创建我们自己的Callback,自己处理handleMessageHandler.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;@Overridepublic 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,也就是我们真正要打开的ActivityIntent 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;@Overridepublic 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,也就是我们真正要打开的ActivityIntent 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);}}}

从上面代码我们能看到:

  1. hookAms()方法分别是SDK<=23、SDK<=28、SDK>28三种情况进行HookAMS,其实都是大同小异。实际上是获取到IActivityManager对象,通过动态代理Proxy.newProxyInstance()Hook到其所有方法,通过AmsInvocationHandler进行方法调用的回调。
  2. hookSystemHandler()方法在hookAms()方法调用后立刻执行,通过反射获取android.app.ActivityThread类对象的sCurrentActivityThread属性和Handler实例mH,将Handler的回调handleMessage()方法进行拦截。根据SDK<=23、SDK<=28、SDK>28三种情况不同来区别处理。
  3. 1中的AmsInvocationHandler负责hookAms()内Hook到的方法调用的处理,在Hook到的Callback中判断为startActivity()方法则拦截下来,将我们真正要跳转的Activity意图存在Extra内,由于在原来的Intent中隐藏了真正的Activity意图,所以只需要将真正的意图拿出来替换将其Intent的意图替换为要打开的Activity
  4. 2中handleMessage()其实就是将startActivity()方法进行拦截,判断如果是未登录状态,则将真正要跳转的ActivityExtra内拿出来进行跳转,已登录则不替换Intent意图跳转。
    在这里插入图片描述

3 总结

文章只做核心HookAMS代码思路的分析,这里是项目地址,小伙伴可以自行下载查看,别忘了点Star喔,谢谢!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/74572.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloudAlibaba:服务网关之Gateway的cors跨域问题

目录 一&#xff1a;解决问题 二&#xff1a;什么是跨域 三&#xff1a;cors跨域是什么&#xff1f; 一&#xff1a;解决问题 遇到错误&#xff1a; 前端请求时报错 解决&#xff1a; 网关中添加配置文件&#xff0c;注意springboot版本&#xff0c;添加配置。 springboo…

【FPGA IP系列】FIFO的通俗理解

FPGA厂商提供了丰富的IP核&#xff0c;基础性IP核都是可以直接免费调用的&#xff0c;比如FIFO、RAM等等。 本文主要介绍FIFO的一些基础知识&#xff0c;帮助大家能够理解FIFO的基础概念。 一、FIFO介绍 FIFO全称是First In First Out&#xff0c;即先进先出。 FIFO是一个数…

Go学习第三天

map的三种声明定义方式 声明map后&#xff0c;一定要make开辟空间&#xff0c;否则会报越界且不能使用 package mainimport "fmt"func main() {// 第一种声明方式// 声明myMap1是一种map类型 key是string value是stringvar myMap1 map[string]string// 判断一下map在…

ad+硬件每日学习十个知识点(20)23.7.31 (芯片和天线间的巴伦电路)

文章目录 1.什么是前端电路&#xff1f;2.什么是巴伦电路&#xff1f;3.巴伦电路的性能参数4.LC巴伦电路5.ADS是干什么的&#xff1f;6.HFSS是干什么的&#xff1f;7.ANSYS有限元软件8.常用的电路仿真软件都有什么&#xff1f;9.巴伦电路的复端阻抗LC10.微带巴伦&#xff08;不…

数据可视化(七)常用图表的绘制

1. #seaborn绘制常用图表 #折线图 #replot&#xff08;x&#xff0c;y&#xff0c;kind&#xff0c;data&#xff09; #lineplot&#xff08;x&#xff0c;y&#xff0c;data&#xff09; #直方图 #displot&#xff08;data&#xff0c;rug&#xff09; #条形图 #barplot&…

【雕爷学编程】 MicroPython动手做(35)——体验小游戏

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

机器学习笔记之优化算法(九)收敛速度的简单认识

机器学习笔记之优化算法——收敛速度的简单认识 引言收敛速度的判别标准 Q \mathcal Q Q-收敛速度 R \mathcal R R-收敛速度关于算法复杂度与收敛速度 引言 本节对收敛速度简单介绍。 收敛速度的判别标准 我们之前几节介绍了线搜索方法 ( Line Search Method ) (\text{Line …

bash的特性(二)IO重定向与管道

bash的I/O重定向及管道 一、概述 在shell中&#xff0c;最常使用的fd(file descriptor)有三个&#xff0c;标准输入&#xff0c;标准输出&#xff0c;错误输出。进程用文件描述符来管理打开的文件。 名称 文件描述符 标准输入&#xff08;stdin) 0 键盘&#xff0c;也可以…

【BEV感知】3-BEV开源数据集

3-BEV开源数据集 1 KITTI1.1 KITTI数据怎么采集?1.2 KITTI数据规模有多大?1.3 KITTI标注了哪些目标?1.4 转换矩阵1.5 标签文件 2 nuScenes2.1 nuScenes Vs KITTI2.2 标注文件 1 KITTI KITTI 1.1 KITTI数据怎么采集? 通过车载相机、激光雷达等传感器采集。 只提供了相机正…

【BEV感知】1-BEV感知算法介绍

1-BEV感知算法介绍 1 什么是BEV感知算法&#xff1f;1.1 什么是BEV&#xff1f;1.2 什么是感知&#xff1f;1.3 什么是算法&#xff1f;1.4 什么是BEV感知&#xff1f; 1 什么是BEV感知算法&#xff1f; 1.1 什么是BEV&#xff1f; Bird’s-Eye-View&#xff0c;尺度变化小、…

优化供应链和库存管理:PDM系统的物料控制之道

在现代制造业中&#xff0c;优化供应链和库存管理是企业实现高效运营和降低成本的重要目标。PDM系统作为一款强大的数字化工具&#xff0c;扮演着物料控制之道的角色&#xff0c;帮助企业实现优化供应链和库存管理的目标。让我们一同深入探讨&#xff0c;看看PDM系统是如何通过…

站点可靠性工程 (SRE)

随着世界各地的组织努力开发安全、可靠、可扩展且可持续的 IT 基础架构&#xff0c;对高效基础架构监控和管理的需求日益增长&#xff0c;企业正在用不可扩展的遗留架构换取现代解决方案&#xff0c;在尖端技术的推动下&#xff0c;这些使基础设施管理过程更加顺畅和轻松&#…

测试|测试分类

测试|测试分类 文章目录 测试|测试分类1.按照测试对象分类&#xff08;部分掌握&#xff09;2.是否查看代码&#xff1a;黑盒、白盒灰盒测试3.按开发阶段分&#xff1a;单元、集成、系统及验收测试4.按实施组织分&#xff1a;α、β、第三方测试5.按是否运行代码&#xff1a;静…

【雕爷学编程】MicroPython动手做(25)——语音合成与语音识别

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

内网横向移动—NTLM-Relay重放Responder中继攻击LdapEws

内网横向移动—NTLM-Relay重放&Responder中继攻击&Ldap&Ews 1. 前置了解1.1. MSF与CS切换权限1.1.1. CS会话中切换权限1.1.1.1. 查看进程1.1.1.2. 权限权限 1.1.2. MSF会话中切换权限 2. NTLM中继攻击—Relay重放—SMB上线2.1. 案例测试2.1.1. 同账户密码测试2.1.2…

GC 深入(小白,对gc有一个进一步的了解)

垃圾回收器的搭配 一般固定 一般这年轻代垃圾回收器&#xff0c;老年代垃圾回收器&#xff0c;如上图搭配着使用 1.8呢默认就是最后边那哥俩 jvm调优 一个就是增加吞吐量 一个就是减少STW的时间。 三色标记算法&#xff08;理解根可达算法&#xff09; 并发的可达性分析 有…

k8s集群部署nacos,采用的是 emptyDir 临时目录挂载

官方参考地址&#xff1a;https://nacos.io/zh-cn/docs/use-nacos-with-kubernetes.html 说明&#xff1a; 1、官网采用的nfs持久化部署 我将nacos持久化改成 emptyDir 临时目录挂载&#xff0c;同时又能满足自行调节nacos集群实例数。 2. emptyDir 临时目录挂载的nacos.ya…

【前端知识】React 基础巩固(四十三)——Effect Hook

React 基础巩固(四十三)——Effect Hook 一、Effect Hook的基本使用 Effect Hook 用来完成一些类似class中生命周期的功能。 在使用类组件时&#xff0c;不管是渲染、网路请求还是操作DOM&#xff0c;其逻辑和代码是杂糅在一起的。例如我们希望把计数器结果显示在标签上&…

【计算机视觉|人脸建模】3D人脸重建基础知识(入门)

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 一、三维重建基础 三维重建&#xff08;3D Reconstruction&#xff09;是指根据单视图或者多视图的图像重建三维信息的过程。 1. 常见三维重建技术 人工几何模型仪器采集基于图像的建模描述基于几何建模…

opencv37-形态学操作-开运算(先腐蚀后膨胀)cv2.morphologyEx()-参数 op 设置为“cv2.MORPH_OPEN”

腐蚀操作和膨胀操作是形态学运算的基础&#xff0c;将腐蚀和膨胀操作进行组合&#xff0c;就可以实现开运算、闭运算&#xff08;关运算&#xff09;、形态学梯度&#xff08;MorphologicalGradient&#xff09;运算、礼帽运算&#xff08;顶帽运算&#xff09;、黑帽运算、击中…