Swift5之Hook初试

1,707 阅读3分钟

工具源码

Hook方法简介

Swift的方法调度

Swift的方法调度有3种不同方式:

  • 直接派发(Direct Dispatch)
  • 函数表派发(Table Dispatch)
  • 消息派发(Message Dispatch) 关于这些方式的说明,网上有许多文章介绍,本文不再赘述。

过程说明

本文探讨的Hook方法,针对后两种调度方式( 函数表派发消息派发 ),不需要区分具体属于哪种类型,过程如下

  1. 创建一个 Target类 的子类( Hook类
  2. 重写目标函数
  3. 通过调用 super函数 的方式调用 origin函数
  4. 执行Hook操作 PS: 直接在内存中修改Class对象属性,会导致意外的问题,需要手动创建一个中间的空子类( Empty类 ),具体原因下文会说明
protocol DDSwiftHookable: AnyObject {
}
extension DDSwiftHookable {
    static func enableHook() {
        // do hook
    }
}


/**
 * Demo
 */
class TargetClass {
    func targetFunction() {
        // do something
    }
}

class TargetClassEmpty : TargetClass {  // An empty class for save objc origin function
}
class TargetClassHook : TargetClassEmpty, DDSwiftHookable {
    override func targetFunction() {
        super.targetFunction();  // call origin function
    } 
}

TargetClassHook.enableHook();  // do hook

原理简介

Swift的子类会继承对应函数的调度方式,所以通过重写函数,可以不用区分具体的调度方式,由编译器自动保持对齐,只需要保证以下两个问题:

  1. 外部调度时,将 Target类 的函数地址替换成 Hook类 函数地址
  2. Hook类 函数调度 super函数 时,能正确调度origin函数 针对不同的调度方式,以上两个问题,采取不同的处理方式

函数表派发

问题 1

函数表的设计机制,保证同个函数在 Target类Hook类 中的偏移量是一致,所以只需要用 Hook类 的函数表直接覆盖 Target类 的,就能修改外部调用。

image.png

问题 2

函数表派发 的情况下,调用 super函数 时,是 直接派发 的,所以 Hook类super函数 就是 origin函数 ,所以不需要额外的处理。

PS:基于这个原因,本方案在 函数表派发 情况下,无法Hook到 Target类 子类的函数

消息派发

问题 1

直接利用Objc的动态机制,直接替换 Target类 的函数地址。

问题 2

对于问题2, 消息派发 的方式会通过调用_objc_msgSendSuper2,动态调度 super函数 。但是传给_objc_msgSendSuper2Hook类 是静态指针对象,无法动态替换。所以本方案采取中 Target类Hook类 的继承关系中加多一个 Empty类 ,用来存储 origin函数 ,这样_objc_msgSendSuper2就会在 Empty类 中找到origin函数

image.png

更多

Protocol

Protocol的调度是基于witness table的,而witness table存储的函数则是由编译器生成的中间函数,再通过中间函数去调用相应的函数,属于一个外部调用,所以本方案也能覆盖相关的情况。

范型

由于范型的继承,父类和子类的 generic参数 是存储在不同属性中。因此,无法用范型的 Hook类 来实现Hook。简单的理解就是, Hook类 的函数可能访问自己的 generic参数 ,这样操作发生在 Target类 上,就会越界。

image.png

方案优劣

优点

  1. 不需要区分具体实现是函数表派发,还是消息派发
  2. 对于 函数表派发 情况,不需要定位 origin函数 在表中的位置
  3. 支持同一函数同时支持 函数表派发消息派发 的情况

缺陷

  1. 目标类的子类,基于 函数表派发 的函数,是无法Hook到
  2. 在同一函数同时支持 函数表派发消息派发 的情况下,子类的不同方式调用,会出现两种情况
  3. 对于范型支持,只能做到用具体类型的 Hook类 去Hook具体类型的 Target类