Hook技术

news2024/12/25 22:38:11

Hook 英文直译是“钩子”的意思,在程序中将其理解为“劫持”更好理解,意思是,通过 Hook 技术来劫持某个对象,从而控制它与其它对象的交互。

Hook 技术是一种用于改变 API 执行结果的技术,Android 系统中有一套自己的事件分发机制,所有的代码调用和回调都是按照一定顺序执行的。Hook 技术存在的意义在于,Hook 可以帮助我们在 Android SDK 的源代码逻辑执行过程中,通过代码手动拦截执行该逻辑,加入自己的代码逻辑。

说到 Hook 技术就要先提到逆向工程,逆向工程源于商业及军事领域中的硬件分析,其主要目的是在不能轻易获得必要的生产信息的情况下,直接从成品分析,推导出产品的设计原理。

逆向分析分为静态分析和动态分析,其中静态分析指的是一种在不执行程序的情况下对程序行为进行分析的技术;动态分析是指在程序运行时对程序进行调试的技术。Hook 技术属于动态分析,它不仅在 Android 平台中被应用,早在 Windows 平台中就已经被应用了。

为了保证 Hook 的稳定性,一般拦截的点都会选择比较容易找到并且不易发生变化的对象,比如静态变量和单例。

1 Hook 技术概述

Hook 原理:创建一个代理对象,然后把原始对象替换为我们的代理对象,这样就可以在这个代理对象“为所欲为”,修改参数或替换返回值。

Android 系统的代码调用和回调都是按照一定顺序执行的,举个简单的例子:

正常的调用和回调

对象 A 调用对象 B,对象 B 处理后将数据回调给对象 A。接着来看采用 Hook 的调用流程:

Hook 的调用和回调

上图中的 Hook 可以是一个方法或者一个对象,它像一个钩子一样挂在对象 A 和对象 B 之间,当对象 A 调用对象 B 之前做一些处理(比如修改方法的参数或者返回值),起到了“欺上瞒下”的作用。因此,与其说 Hook 是钩子,不如说是劫持来的更贴切些。

应用程序进程之间是彼此独立的,应用程序进程和系统进程之间也是如此,想要在应用程序进程更改系统进程的某些行为很难直接实现,有了 Hook 技术,就可以在进程间进行行为更改:

Hook

通过上图可以看到 Hook 可以将自己融入到它所要劫持的对象(对象 B)所在的进程中,成为系统进程的一部分,这样就可以通过 Hook 来更改对象 B 的行为。被劫持的对象(对象 B),称为 Hook 点,为了保证 Hook 的稳定性,Hook 点一般选择容易找到并且不易变化的对象,静态变量和单例就符合这一条件。

Hook 的过程:

  • 找到 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象或者方法,非 public 不保证每个版本都一样,需要适配;
  • 选择合适的代理方法,如果是接口可以用动态代理;如果是类,可以用静态代理;
  • 偷梁换柱,用代理对象替换原始对象;

2 Hook 技术分类

Hook 技术根据不同的角度会有很多种分类。

根据 Hook 的 API 语言划分,分为 Hook Java 和 Hook Native:

  • Hook Java 主要通过反射和代理来实现,应用于 SDK 开发环境中修改 Java 代码;
  • Hook Native 则应用于在 NDK 开发环境和系统开发中修改 Native 代码;

根据 Hook 的进程划分,分为应用程序进程 Hook 和全局 Hook:

  • 应用程序进程 Hook 只能 Hook 当前所在的应用程序进程;
  • 应用程序进程是 Zygote 进程 fork 出来的,如果对 Zygote 进行 Hook,就可以实现 Hook 系统所有的应用成勋进程,这就是全局 Hook;

根据 Hook 的实现方式划分,分为如下两种:

  • 通过反射和代理实现,只能 Hook 当前的应用程序进程;
  • 通过 Hook 框架来实现,比如 XPosed,可以实现全局 Hook,但是需要 root;

Hook Native、全局 Hook 和通过 Hook 框架实现这些分类和插件化技术关联不大。

3 反射

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法和属性。

反射使得在运行状态中时,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有方法和属性),这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

4 代理模式

设计模式(五)—— 代理模式

Java 提供了动态的代理接口 InvocationHandler,实现该接口需要重写 invoke 方法。 以下是动态代理的代码实现:

public class DynamicSinger implements InvocationHandler {
    private Object obj;

    public DynamicSinger(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("sing")) {
            System.out.println("say hello");
            Object result = method.invoke(obj, args);
            System.out.println("say thanks");
            return result;
        }

        return null;
    }
}

在动态代理中声明一个 Object 的引用,该引用指向被代理类,我们调用被代理类的具体方法在 invoke 方法中执行,接下来修改客户端类代码:

class Client {
    public static void main(String[] args) {
        ISinger singer = new Singer();
        DynamicSinger dynamicSinger = new DynamicSinger(singer);
        ISinger iSinger = (ISinger) Proxy.newProxyInstance(
                dynamicSinger.getClass().getClassLoader(), new Class[]{ISinger.class}, 
          dynamicSinger);
        iSinger.sing();
    }
}

调用 Proxy.newProxyInstance() 来生成动态代理类,调用 iSinger.sing() 方法会调用 DynamicSinger.invoke 方法。代理模式从编码的角度可以分为静态代理和动态代理。

5 Hook startActivity 方法

Hook 可以用来劫持对象,被劫持的对象叫做 Hook 点,用代理对象来替代 Hook 点,这样我们聚可以在代理上实现自己想要做的操作。

下面以 Hook 常用的 startActivity 方法来举例,startActivity 方法分为两个:

startActivity(Intent);
getApplicationContext().startActivity(Intent);

第一个是 Activity 的 startActivity 方法,第二个是 Context 的 startActivity 方法,这两个方法的调用链是不同的。

5.1 Hook Activity 的 startActivity 方法

以下是相关源码:

// frameworks/base/core/java/android/app/Activity.java
@Override
public void startActivity(Intent intent) {
    this.startActivity(intent, null); // 1
}

@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
    getAutofillClientController().onStartActivity(intent, mIntent);
    if (options != null) {
        startActivityForResult(intent, -1, options); // 2
    } else {
        startActivityForResult(intent, -1); // 3
    }
}

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
    startActivityForResult(intent, requestCode, null); // 4
}

private Instrumentation mInstrumentation;

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
      @Nullable Bundle options) {
  if (mParent == null) {
      options = transferSpringboardActivityOptions(options);
      Instrumentation.ActivityResult ar =
          mInstrumentation.execStartActivity(
              this, mMainThread.getApplicationThread(), mToken, this,
              intent, requestCode, options); // 5
      if (ar != null) {
          mMainThread.sendActivityResult(
              mToken, mEmbeddedID, requestCode, ar.getResultCode(),
              ar.getResultData());
      }
      if (requestCode >= 0) {
          mStartedActivity = true;
      }

      cancelInputsAndStartExitTransition(options);
  } else {
      if (options != null) {
          mParent.startActivityFromChild(this, intent, requestCode, options);
      } else {
          mParent.startActivityFromChild(this, intent, requestCode);
      }
  }
}

注释 5 处调用了 mInstrumentation.execStartActivity 方法来启动 Activity,这个 mInstrumentation 是 Activity 的成员变量,这里就选择 Instrumentation 作为 Hook 点,用代理 Instrumentation 来替代原始的 Instrumentation 来完成 Hook。首先写出代理 Instrumentation 类:

public class InstrumentationProxy extends Instrumentation {
    private static final String TAG = "InstrumentationProxy";

    Instrumentation mInstrumentation;

    public InstrumentationProxy(Instrumentation instrumentation) {
        mInstrumentation = instrumentation;
    }

    public ActivityResult execStartActivity(Context who, IBinder contextThread, 
                                            IBinder token, Activity target,
                                            Intent intent, int requestCode, Bundle options) {
        System.out.println("【InstrumentationProxy】[execStartActivity] who: " + who);
        try {
            Method execStartActivity = 
              Instrumentation.class.getDeclaredMethod("execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class, 
                    Intent.class, int.class, Bundle.class);
            return (ActivityResult) execStartActivity.invoke(mInstrumentation, who, 
                       contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

InstrumentationProxy 继承了 Instrumentation,并包含 Instrumentation 的引用。InstrumentationProxy 实现了 execStartActivity 方法,其内部会通过反射找到并调用 Instrumentation.execStartActivity 方法。接下来用 InstrumentaionProxy 来替换 Instrumentation,代码如下:

public void replaceActivityInstrumentation(Activity activity) {
    try {
        // 得到 Activity 的 mInstrumentation
        Field field = Activity.class.getDeclaredField("mInstrumentation"); // 1
        // 取消 Java 的权限控制检查
        field.setAccessible(true); // 2
        Instrumentation instrumentation = (Instrumentation) field.get(activity); // 3
        Instrumentation instrumentationProxy = new InstrumentationProxy(instrumentation); // 4
        field.set(activity, instrumentationProxy);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

注释 1 处得到 Activity 的成员变量 mInstrumentation,这个成员变量是私有的,因此在注释 2 处取消 Java 的权限控制检查,这样就可以访问 mInstrumentation 字段。在注释 3 处得到 Activity 中的 Instrumentation 对象,在注释 4 处创建 InstrumentationProxy 并传入注释 3 处得到的 Instrumentation 对象,最后用 InstrumentationProxy 来替换 Instrumentation,这样就实现了代理 Instrumentation 替换 Instrumentation 的目的。最后在 MainActivity.onCreate 方法中使用 replaceActivityInstrumentation 方法,如下所示:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        replaceActivityInstrumentation(this);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse("https://www.baidu.com"));
        startActivity(intent);

    }
}

// System.out: 【InstrumentationProxy】[execStartActivity] who: com.example.myapplication.MainActivity@8df42b9

5.2 Hook Context 的 startActivity 方法

Context 的实现类为 ContextImpl,ContextImpl.startActivity 方法如下:

// frameworks/base/core/java/android/app/ContextImpl.java
@Override
public void startActivity(Intent intent) {
    warnIfCallingFromSystemProcess();
    startActivity(intent, null); // 1
}

@Override
public void startActivity(Intent intent, Bundle options) {
    warnIfCallingFromSystemProcess();

    final int targetSdkVersion = getApplicationInfo().targetSdkVersion;

    if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
            && (targetSdkVersion < Build.VERSION_CODES.N
                    || targetSdkVersion >= Build.VERSION_CODES.P)
            && (options == null
                    || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
        throw new AndroidRuntimeException(
                "Calling startActivity() from outside of an Activity"
                        + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                        + " Is this really what you want?");
    }
    mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity) null, intent, -1, options); // 2
}

注释 2 处调用了 ActivityThread.getInstrumentation() 方法获取 Instrumentation。ActivityThread 是主线程的管理类,Instrumentation 是 ActivityThread 的成员变量,一个进程中只有一个 ActivityThread,因此仍然选择 Instrumentation 作为 Hook 点,用代理 Instrumentation 来替换 Instrumentation,代理 Instrumentation 和 5.1 节的代码是一样的,接下来用 InstrumentationProxy 来替换 Instrumentation,代码如下所示:

public void replaceContextInstrumentation() {
    try {
        // 获取 ActivityThread 类
        Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
        Field activityThreadField = 
          activityThreadClazz.getDeclaredField("sCurrentActivityThread"); // 1
        activityThreadField.setAccessible(true);
        Object currentActivityThread = activityThreadField.get(null); // 2
        // 获取 ActivityThread 中的 mInstrumentation 字段
        Field mInstrumentationField = 
          activityThreadClazz.getDeclaredField("mInstrumentation");
        mInstrumentationField.setAccessible(true);
        Instrumentation mInstrumentation = 
          (Instrumentation) mInstrumentationField.get(currentActivityThread);
        Instrumentation mInstrumentationProxy = new InstrumentationProxy(mInstrumentation); // 3
        mInstrumentationField.set(currentActivityThread, mInstrumentationProxy);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

}

首先通过反射获取 ActivityThread 类,ActivityThread 类中有一个静态变量 sCurrentActivityThread,用于表示当前的 ActivityThread 对象,因此在注释 1 处获取 ActivityThread 中定义的 sCurrentActivityThread 字段,在注释 2 处获取 Field 类型的 activityThreadField 对象的值,这个值就是 sCurrentActivityThread 对象。同理获取当前 ActivityThread 的 mInstrumentation 对象,最后用 InstrumentationProxy 来替换 mInstrumentation。在 MainActivity 中使用 replaceContextInstrumentation 方法,如下所示:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        replaceContextInstrumentation();
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse("https://www.baidu.com"));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        getApplicationContext().startActivity(intent);

    }
}

// System.out: 【InstrumentationProxy】[execStartActivity] who: android.app.Application@5147617

Hook Activity 的 startActivity 方法和 Hook Context 的 startActivity 方法最大的区别就是替换 Instrumentation 不同,前者是 Activity 中的 Instrumentation,后者是 ActivityThread 中的 Instrumentation。另外有一点需要注意的是,这里替换 Instrumentation 都是在 Activity.onCreate 方法中进行替换的,但是,这里未必是最佳的替换时间点。可以在 Activity.attachBaseContext 中进行 Instrumentation 替换,因为这个方法要优先于 Activity.onCreate 方法被调用。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

架构设计基础设施保障IaaS存储

目录 1. 云硬盘2. 对象存储3. 表单上传案例4. 服务上传验证5. 云数据库6. 云数据库操作7. 服务连接云数据库8. 新一代原生数据库9 阿里云PolarDB生产最佳实践 1. 云硬盘 HDD&#xff08;普通云盘&#xff09; 特征&#xff1a; 性能一般&#xff0c; IOPS大概在数百左右。 应…

iPhone 15 Pro展示设计:7项全新变化呈现

我们不应该再等iPhone 15 Pro在苹果9月12日的“Wonderlust”活动上发布了&#xff0c;而且可能会有很多升级。有传言称&#xff0c;iPhone 15 Pro将是自iPhone X以来最大的飞跃&#xff0c;这要归功于大量的新变化&#xff0c;从带有更薄边框的新钛框架到顶级A17仿生芯片和动作…

通过Siri打造智能爬虫助手:捕获与解析结构化数据

在信息时代&#xff0c;我们经常需要从互联网上获取大量的结构化数据。然而&#xff0c;传统的网络爬虫往往需要编写复杂代码和规则来实现数据采集和解析。如今&#xff0c;在苹果公司提供的语音助手Siri中有一个强大功能可以帮助我们轻松完成这项任务——通过使用自定义指令、…

micro python 编译流程和方法,以及一部分问题解决

micro python官网 https://micropython.org/ 点击 点击对应的芯片&#xff0c;我这里是ESP32-S3 点击到git 到esp32目录下 按照指引下载安装ESP-IDF IDF版本查看连接如下&#xff1a; https://docs.espressif.com/projects/esp-idf/en/latest/esp32/versions.html 我这里选择…

Kubernetes入门 十三、配置管理

目录 VolumeConfigMap概述创建ConfigMap使用ConfigMap用作环境变量用作命令行参数使用 volume 挂载 不可变 ConfigMap Secret概述Secret 的种类创建Secretkubectl创建yaml文件创建 使用Secret使用 Volume 挂载用作环境变量挂载指定的 key 使用kubernetes.io/dockerconfigjson类…

【Vue】 Vue3 安装说明,适合小白新手

1、独立版本 我们可以在 Vue.js 的官网上直接下载最新版本, 并用 下载 Vue.js https://unpkg.com/vuenext 2、使用 CDN 方法 以下推荐国外比较稳定的两个 CDN&#xff0c;国内还没发现哪一家比较好&#xff0c;目前还是建议下载到本地。 Staticfile CDN&#xff08;国内&am…

Unity资源无法下载 反复提示需同意Terms of Service和EULA 同意后无效的解决方案

前言 最近在玩Unity&#xff0c;跟着tutorial做点项目&#xff0c;但是在下载免费资源时&#xff0c;只有从网站上点“打开Unity”&#xff0c;才能在本地Unity Editor的Package Manager里找到这个资源&#xff08;且点一下下面的刷新就没有了&#xff09;&#xff0c;并且点击…

【MySQL】MySQL 慢SQL如何避险

我们在日常开发中&#xff0c;一定遇见过某些SQL执行较慢的情况&#xff0c;我们俗称“慢SQL”&#xff0c;如果你对系统的接口性能要求较高的话&#xff0c;一定不会放过这种SQL&#xff0c;肯定会想办法进行解决&#xff0c;那么&#xff0c;导致慢 SQL 出现的原因&#xff0…

寻找重复数

题目链接 寻找重复数 题目描述 注意点 nums 中 只有一个整数 出现 两次或多次 &#xff0c;其余整数均只出现 一次不修改 数组 nums 且只用常量级 O(1) 的额外空间 解答思路 参照题解可以将本题的数组抽象为链表&#xff0c;由于nums中只有一个整数出现多次&#xff0c;所…

Python操作Excel教程(图文教程,超详细)Python xlwings模块详解,

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 xlwings模块详解 1、快速入门1、打开Excel2、创建工作簿2.1、使用工作簿2.2、操作…

9.(Python数模)(分类模型一)K-means聚类

Python实现K-means聚类 K-means原理 K-means均值聚类算法作为最经典也是最基础的无标签分类学习算法。其实质就是根据两个数据点的距离去判断他们是否属于一类&#xff0c;对于一群点&#xff0c;就是类似用几个圆去框定这些点&#xff08;簇&#xff09;&#xff0c;然后圆心…

7英寸触摸显示屏企业网络电话

SV-X77英寸触摸显示屏企业网络电话 SV-X7网络电话是一款带有7英寸触摸显示屏的高端式企业级电话&#xff0c;以先进设计及强大的功能大幅度提高企业工作效率。 功能亮点 √ 虚拟可编程按键 — 可动态显示4个分页&#xff0c;每页可设置显示29个DSS键的状态&#xff0c;最多支持…

线 竖线 横线 系谱图 before 伪类

ul.xipt.level-1 li:before {position: absolute;width: 1px;border-right: dashed 1px #a52520;left: 50%;bottom: -40px;content: "";height: 84px; }

患者随访模板移动端设计

医院随访模板是一款集患者资料整理、随访计划执行和数据统计分析功能于一体的医患服务系统。旨在帮助医院规范随访工作、提高随访效率、提升医疗水平、提高患者依从度。 一、模板临床作用及意义 1、减轻随访工作人员劳动强度、提升随访工作效率。帮助医生或者医院从繁重无序的…

【机器学习】人工智能概述(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

权限框架之jcasbin讲解

文章目录 1 jcasbin1.1 前言1.2 工作原理1.2.1 PERM模型1.2.2 Model语法1.2.2.1 Request定义1.2.2.2 Policy定义1.2.2.3 Policy effect定义1.2.2.4 角色定义1.2.2.5 匹配器1.2.2.6 完整model.conf 1.2.3 policy.csv 1.3 准备1.3.1 mavan依赖1.3.2 配置文件1.2.3 读取权限信息进…

Server - PyTorch BFloat16 “TypeError: Got unsupported ScalarType BFloat16“ 解决方案

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132665807 BFloat16 类型是 16 位的浮点数格式&#xff0c;可以用来加速深度学习的计算和存储。BFloat16 类型的特点是保留 32 位浮点数&#xff…

龙智携手Atlassian和JFrog举办线下研讨会,探讨如何提升企业级开发效率与质量

2023年9月8日&#xff0c;龙智将携手Atlassian和JFrog于上海举办线下研讨会&#xff0c;以“大规模开发创新&#xff1a;如何提升企业级开发效率与质量”为主题&#xff0c;邀请龙智高级咨询顾问、Atlassian认证专家叶燕秀&#xff0c;紫龙游戏上海研发中心高级项目管理主管叶凯…

PHP8创建数组-PHP8知识详解

在php 8中&#xff0c;您可以使用以下方法创建数组&#xff1a;使用数组字面量创建数组、使用 array() 函数创建数组、通过赋值的方式创建数组、使用array_push()函数将元素添加到数组末尾、使用range()函数创建数值数组、使用compact()函数创建带有变量名的数组、使用array_fi…

C++ | 程序暂停功能

C | 程序暂停功能 文章目录 C | 程序暂停功能初衷rosbag 播包暂停功能源码 识别键盘输入&#xff08;需输入Enter&#xff09;识别键盘输入&#xff08;无需输入Enter&#xff09;opencv waitKey函数kill 信号包装成空格命令 Reference[C/C 获取键盘事件](https://www.runoob.c…