【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());

    @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启动时,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的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);
        }
    }

}

从上面代码我们能看到:

  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.mfbz.cn/a/57154.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

亿发软件:专业医疗器械GSP管理系统解决方案,智能化药械规范经营

如今医疗器械市场得到进一步发展&#xff0c;为了确保市场上医疗器械管理效率与品质保障&#xff0c;直击医疗器械行业管理困境&#xff0c;推出医疗器械GSP管理软件&#xff01;专业医疗器械GSP软件全面满足医疗器械企业的应用要求&#xff0c;打造管理解决方案。1、信息传输和…

2023-08-02 LeetCode每日一题(翻转卡片游戏)

2023-08-02每日一题 一、题目编号 822. 翻转卡片游戏二、题目链接 点击跳转到题目位置 三、题目描述 在桌子上有 N 张卡片&#xff0c;每张卡片的正面和背面都写着一个正数&#xff08;正面与背面上的数有可能不一样&#xff09;。 我们可以先翻转任意张卡片&#xff0c;…

云上 Index:看「简墨」如何为云原生打造全新索引

拓数派首款数据计算引擎 PieCloudDB Database 是一款全新的云原生虚拟数仓。为了提升用户使用体验&#xff0c;提高查询效率&#xff0c;在实现存算分离的同时&#xff0c;PieCloudDB 设计与打造了全新的存储引擎「简墨」等模块&#xff0c;并针对云场景和分析型场景设计了高效…

ELD透明屏在智能家居中有哪些优点展示?

ELD透明屏是一种新型的显示技术&#xff0c;它能够在不需要背光的情况下显示图像和文字。 ELD透明屏的原理是利用电致发光效应&#xff0c;通过在透明基板上涂覆一层特殊的发光材料&#xff0c;当电流通过时&#xff0c;发光材料会发出光线&#xff0c;从而实现显示效果。 ELD…

零碎小知识点汇总——记录工作中遇到的问题——基础积累

1.npm install安装包时&#xff0c;常用的-S -D有什么区别&#xff1f; 参考链接&#xff1a;https://blog.csdn.net/sunyctf/article/details/127667543 主要的区别就是依赖配置写入package.json文件的位置不同而已 npm install有一个别名&#xff1a;npm i -S:写入dependen…

Qt 中引入ffmpeg 动态库

1、前期准备 在qt引入ffmpeg动态库的时候&#xff0c;需要准备ffmpeg的动态库和头文件。 2、打开qt项目 在qt项目的.pro文件中添加以下几行代码 INCLUDEPATH $$PWD/thirtLib/ffmpeg4.2/include win32: LIBS -L$$PWD/thirtLib/ffmpeg4.2/lib/ -lavcodec -lavdevice -lavf…

Java的代理模式

java有三种代理模式 静态代理 jdk动态代理 cglib实现动态代理 代理模式的定义&#xff1a; 为其他对象提供一种代理以控制对这个对象的访问。在某些情况下&#xff0c;一个对象不适合或者不能直接引用另一个对象&#xff0c;而代理对象可以在客户端和目标对象之间起到中介的…

TCP三次握手与四次断开

TCP三次握手机制 三次握手是指建立一个TCP连接时&#xff0c;需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。 1、客户端发送建立TCP连接的请求报文&#xff0c;其…

小研究 - 基于解析树的 Java Web 灰盒模糊测试(二)

由于 Java Web 应用业务场景复杂, 且对输入数据的结构有效性要求较高, 现有的测试方法和工具在测试Java Web 时存在测试用例的有效率较低的问题. 为了解决上述问题, 本文提出了基于解析树的 Java Web 应用灰盒模糊测试方法. 首先为 Java Web 应用程序的输入数据包进行语法建模创…

《零基础入门学习Python》第077讲:Tkinter 模块:标准对话框

Tkinter 为了提供了三种标准对话框模块&#xff0c;它们分别是&#xff1a; messageboxfiledialogcolorchooser 注&#xff1a;这三个模块原来是独立的&#xff0c;分别是 tkMessageBox、tkFileDialog 和 tkColorChooser&#xff0c;需要导入才能使用。在 Python3 之后&#…

HCIP OSPF链路状态类型总结

OSPF的LSA OSPF是典型的链路状态路由协议&#xff0c;使用LAS&#xff08;链路状态通告&#xff09;来承载链路状态信息。LSA是OSPF的一个核心内容&#xff0c;如果没有LSA&#xff0c;OSPF 是无法描述网络的拓扑结构及网段信息的&#xff0c;也无法传递路由信息&#xff0c;更…

2023年信息系统项目管理师-粗略了解整体主要知识脉络

1. 五大过程组 2.十大知识领域 3. 十大知识领域与五大过程组的关系 4. 十大知识领域的关系图 5. 十大知识域与五大过程组的记忆方法 6. 仅开展一次或仅在项目预定义的时候开展 7.需要定期开展的过程

【方法】PDF可以转换成Word文档吗?如何操作?

很多人喜欢在工作中使用PDF&#xff0c;因为PDF格式可以准确地保留文档的原始格式&#xff0c;比如字体、图像、布局和颜色等。 但如果编辑文档的话&#xff0c;PDF还是没有Word文档方便。那可以将PDF转换成Word格式&#xff0c;再来编辑吗&#xff1f;如何操作呢&#xff1f;…

网络安全进阶学习第十一课——MySQL手工注入(2)

文章目录 一、UA注入1、原理2、靶场演示&#xff1a;1&#xff09;一旦页面出现如下现状&#xff0c;就可以使用UA注入2&#xff09;BP抓包3&#xff09;修改User-Agent 二、referer注入1、原理2、靶场演示&#xff1a;1&#xff09;使用BP抓包2&#xff09;修改Referer 三、DN…

Express中间件

1.创建最基本的中间件 const express require(express); const send require(send);const app express()const mw function (req, res, next) {console.log(middleware);// 一定要调用next() 把流转关系交给下一个中间件或路由next() }app.listen(80, () > {console.l…

CRM系统如何进行公海池线索分配自动化?

在销售过程中&#xff0c;线索分配是一个非常重要的环节。传统的线索分配方式往往是由销售主管手动进行&#xff0c;不仅效率低下&#xff0c;还存在着不公平、不灵活的问题。因此&#xff0c;许多企业通过CRM来实现公海池线索分配自动化。 1、基于规则的分配 CRM可以让用户设…

Vivado常见critical warning 、error

综合 Designutils 20-1281 如下图所示 可能原因 在复制Vivado工程时&#xff0c;IP文件缺少导致 解决方法 重新生成IP即可

arcgis字段计算器

1、两字段叠加。要求待叠加的字段类型为文本或字符串类型。如下&#xff1a; 2、字符串部分提取。

VBA技术资料MF35:VBA_在Excel中过滤数据

【分享成果&#xff0c;随喜正能量】好马好在腿&#xff0c;好人好在嘴。不会烧香得罪神&#xff0c;不会讲话得罪人。慢慢的你就会发现&#xff0c;一颗好心&#xff0c;永远比不上一张好嘴。。 我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#…

带头循环双向链表详解

目录 一、什么是带头循环双向链表&#xff1f; 1.特点&#xff1a; 2.优点&#xff1a; 二、实现接口 1.前置准备 1.1需要的三个文件 1.2结构体的创建和头文件的引用 2.接口实现 2.1函数创建新节点 2.2打印链表内容 2.3尾插新节点 2.4头插新节点 2.5头删节点 2.6尾删…