溺的文档
KernelPatch · 第 12 篇 / 共 22 篇

Hook 机制详解

2026-06-29 · 阅读 0

概述

KernelPatch 的 Hook 机制是整个项目最核心的功能之一。它实现了对内核函数的完全透明拦截,支持 before/after 回调和参数修改。

核心文件kernel/base/hook.c

支持的 Hook 类型

  1. Inline Hook:直接修改函数入口,跳转到 trampoline
  2. Syscall Hook:修改系统调用表(后续文档详解)

核心数据结构

hook_t - 基础 Hook 结构

// kernel/include/hook.h

#define TRAMPOLINE_MAX_NUM 6
#define RELOCATE_INST_NUM (4 * 8 + 8 - 4)  // = 36

// PAC 指令常量(Pointer Authentication Code)
#define ARM64_PACIASP 0xd503233f
#define ARM64_PACIBSP 0xd503237f

typedef struct
{
    // in
    uint64_t func_addr;          // 被 hook 的函数地址
    uint64_t origin_addr;        // hook 点的地址(通常 = func_addr)
    uint64_t replace_addr;       // trampoline 地址
    uint64_t relo_addr;          // 重定位后原始指令的地址
    // out
    int32_t tramp_insts_num;     // trampoline 指令数
    int32_t relo_insts_num;      // 重定位指令数
    uint32_t origin_insts[TRAMPOLINE_MAX_NUM] __attribute__((aligned(8)));  // 备份的原始指令(最多 6 条)
    uint32_t tramp_insts[TRAMPOLINE_MAX_NUM] __attribute__((aligned(8)));   // trampoline 指令(最多 6 条)
    uint32_t relo_insts[RELOCATE_INST_NUM] __attribute__((aligned(8)));     // 重定位后的原始指令(36 条)
} hook_t __attribute__((aligned(8)));

0.13.2 变化要点

  • TRAMPOLINE_NUM 4 更名为 TRAMPOLINE_MAX_NUM 6,最大可覆盖 6 条指令
  • RELOCATE_INST_NUM 公式改为 (4 * 8 + 8 - 4) = 36,重定位缓冲区更大
  • 新增 ARM64_PACIASP / ARM64_PACIBSP 常量,用于函数入口 PAC 检测
  • 所有指令数组添加 __attribute__((aligned(8))) 保证 8 字节对齐
  • 整体结构体也添加 __attribute__((aligned(8)))

hook_chain_t - 链式 Hook

// kernel/include/hook.h

#define HOOK_CHAIN_NUM   0x10  // 16
#define TRANSIT_INST_NUM 0x60  // 96

typedef struct _hook_chain
{
    // must be the first element
    hook_t hook;
    int32_t chain_items_max;
    chain_item_state states[HOOK_CHAIN_NUM];   // 状态数组
    void *udata[HOOK_CHAIN_NUM];               // 用户数据数组
    void *befores[HOOK_CHAIN_NUM];             // before 回调数组
    void *afters[HOOK_CHAIN_NUM];              // after 回调数组
    uint32_t transit[TRANSIT_INST_NUM];         // transit 函数(固定长度 96 条指令)
} hook_chain_t __attribute__((aligned(8)));

// 链项状态
#define CHAIN_ITEM_STATE_NONE    0
#define CHAIN_ITEM_STATE_READY   1
#define CHAIN_ITEM_STATE_DISABLE 2

链式 Hook 的优势

  • 同一个函数可以被多次 hook
  • 按照添加顺序依次执行
  • 支持动态添加/删除回调
  • 最多支持 16 个链项(HOOK_CHAIN_NUM)

Hook 执行流程

流程图

原始函数调用:
  func(arg0, arg1, arg2, arg3)
    |
    v
+------------------------------------------+
| func 入口(已被修改):                    |
|   0x000: LDR X17, #8                     |
|   0x004: RET X17                         |
|   0x008: <trampoline 地址低32位>         |
|   0x00C: <trampoline 地址高32位>         |
+------------------------------------------+
    |
    v
+------------------------------------------+
| hook_chain->transit (在 hook 区):        |
|   BTI JC                                 |
|   LDR X16, #12    (加载 chain 地址)      |
|   B   #16         (跳过嵌入数据)         |
|   NOP                                    |
|   <chain 地址低32位>                     |
|   <chain 地址高32位>                     |
|   ... transit 代码 ...                   |
+------------------------------------------+
    |
    v
+------------------------------------------+
| transit 函数(如 _transit4):             |
|   1. mov x0, x16 取出 hook_chain 指针    |
|   2. 打包参数到 hook_fargs4_t            |
|   3. 遍历 before 回调:                  |
|        for (i=0; i<max; i++)             |
|          befores[i](&fargs, udata[i])    |
|   4. 如果 !skip_origin:                  |
|        调用重定位后的原始函数             |
|   5. 遍历 after 回调:                   |
|        for (i=max-1; i>=0; i--)          |
|          afters[i](&fargs, udata[i])     |
|   6. 返回 fargs.ret                      |
+------------------------------------------+
    |
    +-- before_callback(&fargs, udata)
    |     +-- 可以修改参数、设置 skip_origin
    |
    +-- (可选) 原始函数执行
    |     +-- relo_addr (重定位后的原始指令)
    |
    +-- after_callback(&fargs, udata)
    |     +-- 可以修改返回值
    |
    +-- 返回到调用者

transit 机制(0.13.2 彻底重写)

旧机制 vs 新机制

旧机制(0.12.0)

  • transit 函数内部使用 ADR 获取当前地址
  • 向前扫描查找 NOP 标记
  • 通过 container_of 从 NOP 位置反算 hook_chain 地址

新机制(0.13.2)

  • hook_chain_prepare() 在 transit 代码前方嵌入 hook_chain 地址
  • 通过 LDR X16, #12 将地址加载到 X16 寄存器
  • transit 函数通过 current_inline_hook_chain() 宏直接读取 X16

current_inline_hook_chain() 宏

// kernel/base/hook.c
#define current_inline_hook_chain() ({          \
    uint64_t chain_va;                          \
    asm volatile("mov %0, x16" : "=r"(chain_va)); \
    (hook_chain_t *)chain_va;                   \
})

原理hook_chain_prepare() 生成的指令序列已经将 hook_chain 地址加载到 X16,transit 函数只需读取即可。相比旧机制的运行时扫描,新机制是零开销的直接寄存器读取。

hook_chain_prepare() - 生成 transit 前置序列

// kernel/base/hook.c
static hook_err_t hook_chain_prepare(uint32_t *transit, int32_t argno)
{
    // 根据参数数量选择 transit 函数
    uint32_t *transit_func_start, *transit_func_end;
    switch (argno) {
        case 0:
            transit_func_start = (uint32_t *)_transit0;
            transit_func_end   = (uint32_t *)_transit0_end;
            break;
        case 1 ... 4:
            transit_func_start = (uint32_t *)_transit4;
            transit_func_end   = (uint32_t *)_transit4_end;
            break;
        case 5 ... 8:
            transit_func_start = (uint32_t *)_transit8;
            transit_func_end   = (uint32_t *)_transit8_end;
            break;
        default:
            transit_func_start = (uint32_t *)_transit12;
            transit_func_end   = (uint32_t *)_transit12_end;
            break;
    }
    
    // 生成前置指令序列
    transit[0] = ARM64_BTI_JC;             // BTI JC(分支目标标识)
    transit[1] = 0x58000070;               // LDR X16, #12(加载chain地址到X16)
    transit[2] = 0x14000004;               // B #16(跳过嵌入的地址数据)
    transit[3] = ARM64_NOP;                // NOP(对齐填充)
    
    // 嵌入 hook_chain 地址(64 位,分两个 32 位存储)
    uint64_t chain_addr = local_container_of(transit, hook_chain_t, transit);
    transit[4] = chain_addr & 0xFFFFFFFF;  // 地址低 32 位
    transit[5] = chain_addr >> 32u;        // 地址高 32 位
    
    // 复制 transit 函数体
    uint32_t transit_num = transit_func_end - transit_func_start;
    for (int i = 0; i < transit_num; i++) {
        transit[6 + i] = transit_func_start[i];
    }
    
    return HOOK_NO_ERR;
}

transit 内存布局

hook_chain->transit[] 数组:
  +-------+------------------------------------+
  | [0]   | BTI JC         (0xD50324DF)        |
  | [1]   | LDR X16, #12   (0x58000070)        |
  | [2]   | B #16          (0x14000004)         |
  | [3]   | NOP            (0xD503201F)         |
  | [4]   | chain 地址低32位                    |
  | [5]   | chain 地址高32位                    |
  +-------+------------------------------------+
  | [6]   | transit 函数体开始                  |
  | [6+n] | transit 函数体结束                  |
  +-------+------------------------------------+

为什么用 X16? ARM64 调用约定中 X16 是 IP0(intra-procedure-call scratch register),不参与参数传递也不需要跨函数保存,非常适合作为 trampoline 的临时数据通道。

_transit4 - 4 参数函数

// kernel/base/hook.c
typedef uint64_t (*transit4_func_t)(uint64_t, uint64_t, uint64_t, uint64_t);

uint64_t __attribute__((section(".transit4.text"))) __attribute__((__noinline__))
_transit4(uint64_t arg0, uint64_t arg1, uint64_t arg2, uint64_t arg3)
{
    // ============ 步骤 1:获取 hook_chain ============
    
    hook_chain_t *hook_chain = current_inline_hook_chain();
    // 直接通过 X16 寄存器获取,无需扫描
    
    
    // ============ 步骤 2:打包参数 ============
    
    hook_fargs4_t fargs;
    fargs.skip_origin = 0;
    fargs.arg0 = arg0;
    fargs.arg1 = arg1;
    fargs.arg2 = arg2;
    fargs.arg3 = arg3;
    fargs.chain = hook_chain;
    
    
    // ============ 步骤 3:执行 before 回调 ============
    
    for (int32_t i = 0; i < hook_chain->chain_items_max; i++) {
        if (hook_chain->states[i] != CHAIN_ITEM_STATE_READY) continue;
        hook_chain4_callback func = hook_chain->befores[i];
        if (func) func(&fargs, hook_chain->udata[i]);
    }
    
    
    // ============ 步骤 4:调用原始函数 ============
    
    if (!fargs.skip_origin) {
        transit4_func_t origin_func = (transit4_func_t)hook_chain->hook.relo_addr;
        fargs.ret = origin_func(fargs.arg0, fargs.arg1, fargs.arg2, fargs.arg3);
    }
    
    
    // ============ 步骤 5:执行 after 回调 ============
    
    for (int32_t i = hook_chain->chain_items_max - 1; i >= 0; i--) {
        if (hook_chain->states[i] != CHAIN_ITEM_STATE_READY) continue;
        hook_chain4_callback func = hook_chain->afters[i];
        if (func) func(&fargs, hook_chain->udata[i]);
    }
    
    return fargs.ret;
}

hook_fargs 参数结构

不同参数数量的版本

// kernel/include/hook.h

// 0 参数
typedef struct {
    uint64_t ret;
    int skip_origin;
    void *chain;
} hook_fargs0_t;

// 4 参数
typedef struct {
    uint64_t arg0, arg1, arg2, arg3;
    uint64_t ret;
    int skip_origin;
    void *chain;
    union {
        uint64_t data[4];
        uint64_t data0, data1, data2, data3;
    } local;
} hook_fargs4_t;

// 8 参数
typedef struct {
    uint64_t arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7;
    uint64_t ret;
    int skip_origin;
    void *chain;
    union {
        uint64_t data[4];
        uint64_t data0, data1, data2, data3;
    } local;
} hook_fargs8_t;

// 12 参数
typedef struct {
    uint64_t arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11;
    uint64_t ret;
    int skip_origin;
    void *chain;
    union {
        uint64_t data[4];
        uint64_t data0, data1, data2, data3;
    } local;
} hook_fargs12_t;

local 字段的用途

// 在 before 回调中保存数据
void before_callback(hook_fargs4_t *args, void *udata)
{
    args->local.data0 = some_value;  // 保存
}

// 在 after 回调中使用
void after_callback(hook_fargs4_t *args, void *udata)
{
    uint64_t value = args->local.data0;  // 读取
}

Hook 准备:hook_prepare()

// kernel/base/hook.c
hook_err_t hook_prepare(hook_t *hook)
{
    if (is_bad_address((void *)hook->func_addr)) return -HOOK_BAD_ADDRESS;
    if (is_bad_address((void *)hook->origin_addr)) return -HOOK_BAD_ADDRESS;
    if (is_bad_address((void *)hook->replace_addr)) return -HOOK_BAD_ADDRESS;
    if (is_bad_address((void *)hook->relo_addr)) return -HOOK_BAD_ADDRESS;
    
    
    // ============ 步骤 1:备份原始指令 ============
    
    for (int i = 0; i < TRAMPOLINE_MAX_NUM; i++) {
        hook->origin_insts[i] = *((uint32_t *)hook->origin_addr + i);
    }
    // 现在备份 6 条指令(TRAMPOLINE_MAX_NUM = 6)
    
    
    // ============ 步骤 2:生成 trampoline(PAC 感知)============
    
    uint32_t first_inst = hook->origin_insts[0];
    
    if (first_inst == ARM64_PACIASP || first_inst == ARM64_PACIBSP) {
        // 函数开头是 PAC 签名指令(PACIASP / PACIBSP)
        // trampoline[0] 设为 BTI_JC,跳转指令从 tramp_insts[1] 开始
        hook->tramp_insts[0] = ARM64_BTI_JC;
        hook->tramp_insts_num = 1 + branch_from_to(
            hook->tramp_insts + 1,
            hook->origin_addr + 4,
            hook->replace_addr
        );
    } else {
        // 普通函数,直接从 tramp_insts[0] 开始
        hook->tramp_insts_num = branch_from_to(
            hook->tramp_insts,
            hook->origin_addr,
            hook->replace_addr
        );
    }
    
    
    // ============ 步骤 3:重定位原始指令 ============
    
    for (int i = 0; i < hook->tramp_insts_num; i++) {
        uint64_t inst_addr = hook->origin_addr + i * 4;
        uint32_t inst = hook->origin_insts[i];
        
        hook_err_t relo_res = relocate_inst(hook, inst_addr, inst);
        if (relo_res) {
            logke("relocate instruction error, origin: %llx, func: %llx, error: %d\n",
                  hook->origin_addr, hook->func_addr, relo_res);
            return relo_res;
        }
    }
    
    
    // ============ 步骤 4:添加返回跳转 ============
    
    uint64_t ret_addr = hook->origin_addr + hook->tramp_insts_num * 4;
    branch_from_to(hook->relo_insts + hook->relo_insts_num, hook->relo_addr, ret_addr);
    
    return HOOK_NO_ERR;
}

0.13.2 PAC 处理逻辑

场景 A:函数以 PACIASP/PACIBSP 开头

  函数入口:
    0x000: PACIASP       <-- PAC 签名指令
    0x004: STP X29,X30   <-- 后续指令
    0x008: ...
    ...

  trampoline 覆盖:
    0x000: BTI JC        <-- tramp_insts[0](替代 PACIASP)
    0x004: LDR X17, #8   <-- tramp_insts[1](branch_from_to 生成)
    0x008: RET X17       <-- tramp_insts[2]
    0x00C: <地址低32>    <-- tramp_insts[3]
    0x010: <地址高32>    <-- tramp_insts[4]

  为什么用 BTI_JC 替换 PACIASP?
    - PACIASP 具有隐式 BTI 语义(硬件视为合法分支目标)
    - 替换后必须保留这个语义,否则间接调用会触发 BTI 异常
    - BTI_JC 正好满足这个需求


场景 B:普通函数(无 PAC)

  trampoline 覆盖:
    0x000: LDR X17, #8   <-- tramp_insts[0]
    0x004: RET X17       <-- tramp_insts[1]
    0x008: <地址低32>    <-- tramp_insts[2]
    0x00C: <地址高32>    <-- tramp_insts[3]

对比旧版本的变化

  • 旧版在 relo_insts 开头插入 BTI_JC + NOP,新版不再如此
  • PAC 检测逻辑完全新增

branch_from_to() - 跳转指令生成

// kernel/base/hook.c

// 0.13.2 使用 ret_absolute 方案
int32_t ret_absolute(uint32_t *buf, uint64_t addr)
{
    buf[0] = 0x58000051;           // LDR X17, #8
    buf[1] = 0xD65F0220;           // RET X17
    buf[2] = addr & 0xFFFFFFFF;    // 目标地址低 32 位
    buf[3] = addr >> 32u;          // 目标地址高 32 位
    return 4;
}

int32_t branch_from_to(uint32_t *tramp_buf, uint64_t src_addr, uint64_t dst_addr)
{
    return ret_absolute(tramp_buf, dst_addr);
}

0.13.2 变化:RET X17 替代 BR X17

指令 编码 行为
BR X17 0xD61F0220 无条件间接跳转,受 BTI 约束
RET X17 0xD65F0220 子程序返回,硬件视为返回分支

为什么选择 RET X17?

  • BR Xn 在启用 BTI 的内核中,目标地址必须以 BTI 指令开头,否则触发异常
  • RET Xn 被 ARM 硬件视为函数返回,不受 BTI 分支目标检查约束
  • 这使得 trampoline 可以跳转到任意地址而无需目标配合

branch_func_addr_once() - BTI 跳过

// kernel/base/hook.c
static uint64_t branch_func_addr_once(uint64_t addr)
{
    uint64_t ret = addr;
    uint32_t inst = *(uint32_t *)addr;
    
    if ((inst & MASK_B) == INST_B) {
        // 是 B 指令,跟随跳转
        uint64_t imm26 = bits32(inst, 25, 0);
        uint64_t imm64 = sign64_extend(imm26 << 2u, 28u);
        ret = addr + imm64;
    } else if (inst == ARM64_BTI_C || inst == ARM64_BTI_J ||
               (inst == ARM64_BTI_JC && !hook_get_mem_from_origin(addr))) {
        // 是 BTI 指令,跳过它
        ret = addr + 4;
    }
    
    return ret;
}

0.13.2 变化:BTI_JC 的跳过条件增加了 !hook_get_mem_from_origin(addr) 检查。

含义:如果一个地址已经被 hook(即 hook_get_mem_from_origin(addr) 返回非空),则不跳过该处的 BTI_JC。这是因为被 hook 的函数入口处的 BTI_JC 可能是 trampoline 写入的(PAC 场景下 tramp_insts[0] = BTI_JC),跳过它会跳到 trampoline 的跳转指令中间,导致错误。


Hook 安装/卸载:hotpatch 原子化(0.13.2 重写)

旧机制(0.12.0)

旧版直接操作页表权限来写入指令:

// 旧版流程(已废弃)
uint64_t *pte = pgtable_entry_kernel(addr);
uint64_t old_pte = modify_pte_bit_disable(*pte, PTE_RDONLY);  // 移除只读
flush_tlb_kernel_page(addr);
// ... 写入指令 ...
modify_pte_bit_enable(*pte, old_pte);  // 恢复只读
flush_tlb_kernel_page(addr);

问题

  • 修改页表和写入指令之间存在窗口,其他 CPU 可能执行半修改的代码
  • 多条指令逐条写入不是原子操作
  • SMP 场景下可能触发竞态条件

新机制(0.13.2)

// kernel/base/hook.c
void hook_install(hook_t *hook)
{
    void *addrs[TRAMPOLINE_MAX_NUM];
    for (int32_t i = 0; i < hook->tramp_insts_num; ++i) {
        addrs[i] = (uint32_t *)hook->origin_addr + i;
    }
    hotpatch(addrs, hook->tramp_insts, hook->tramp_insts_num);
}
// kernel/base/hook.c
void hook_uninstall(hook_t *hook)
{
    void *addrs[TRAMPOLINE_MAX_NUM];
    for (int32_t i = 0; i < hook->tramp_insts_num; ++i) {
        addrs[i] = (uint32_t *)hook->origin_addr + i;
    }
    hotpatch(addrs, hook->origin_insts, hook->tramp_insts_num);
}

hotpatch() 的工作原理

hotpatch(addrs, values, cnt)
  |
  v
stop_machine()  -- 暂停所有其他 CPU
  |
  v
对每个 addrs[i]:
  1. 修改页表权限为可写
  2. 写入 values[i]
  3. 恢复页表权限
  |
  v
刷新 I-Cache 和 TLB
  |
  v
恢复所有 CPU

优势

  • 原子化:通过 stop_machine 保证所有指令同时生效,其他 CPU 不会看到中间状态
  • 安全性:不存在页表权限修改和指令写入之间的竞态窗口
  • 简洁性:install 和 uninstall 逻辑完全对称,代码更清晰

Hook 接口函数

hook_wrap4() - Hook 4 参数函数

// kernel/base/hook.c
hook_err_t hook_wrap4(void *func_addr, hook_chain4_callback before, 
                      hook_chain4_callback after, void *udata)
{
    hook_chain_t *hook_chain = (hook_chain_t *)hook_chain_alloc(sizeof(hook_chain_t) + 
                                                                 sizeof(_transit4));
    if (!hook_chain) return -HOOK_NO_MEM;
    
    hook_chain->chain_items_max = 1;
    hook_chain->befores[0] = before;
    hook_chain->afters[0] = after;
    hook_chain->udata[0] = udata;
    hook_chain->states[0] = CHAIN_ITEM_STATE_READY;
    
    // 生成 transit 前置序列 + 复制 transit 函数体
    hook_chain_prepare(hook_chain->transit, 4);
    
    hook_t *hook = &hook_chain->hook;
    hook->func_addr = (uint64_t)func_addr;
    hook->origin_addr = (uint64_t)func_addr;
    hook->replace_addr = (uint64_t)hook_chain->transit;
    hook->relo_addr = hook_alloc();
    
    hook_err_t err = hook_prepare(hook);
    if (err) return err;
    
    hook_install(hook);
    return HOOK_NO_ERR;
}

hook_wrap8() / hook_wrap12() 类似,只是传递不同的参数数量给 hook_chain_prepare()


完整示例:Hook rest_init

安装 Hook

// kernel/patch/patch.c
unsigned long init_addr = patch_config->rest_init;
if (init_addr) {
    rc = hook_wrap4((void *)init_addr, before_rest_init, 0, (void *)init_addr);
    log_boot("hook rest_init rc: %d\n", rc);
}

Before 回调

// kernel/patch/patch.c
static void before_rest_init(hook_fargs4_t *args, void *udata)
{
    int rc = 0;
    log_boot("entering init ...\n");
    
    // 执行各种初始化
    bypass_kcfi();
    resolve_struct();
    bypass_selinux();
    task_observer();
    supercall_install();
    // ...
    
    // 不需要修改参数或跳过原始函数
    // args->skip_origin = 0;  (默认)
}

执行流程

1. 内核调用 rest_init()
   |
2. 函数入口已被改写为 LDR X17, #8; RET X17
   |
3. 跳转到 hook_chain->transit
   |
4. transit 前置序列: BTI JC; LDR X16, #12 加载 chain 指针; B #16 跳到函数体
   |
5. _transit4 通过 mov x0, x16 获取 hook_chain
   |
6. _transit4 调用 before_rest_init(&fargs, udata)
   |
7. before_rest_init 执行初始化
   |
8. _transit4 调用原始 rest_init (重定位后的版本)
   |
9. 原始 rest_init 正常执行
   |
10. 返回到内核

实际案例:bypass_selinux

Hook avc_denied

// kernel/patch/common/selinux.c
void before_avc_denied(hook_fargs12_t *args, void *udata)
{
    struct task_struct *task = current;
    struct task_ext *ext = get_task_ext(task);
    
    if (ext && ext->sel_allow) {
        // 这个进程被标记为 SELinux 豁免
        args->skip_origin = 1;  // 跳过原始 avc_denied
        args->ret = 0;          // 返回允许
    }
}

int bypass_selinux()
{
    unsigned long avc_denied_addr = patch_config->avc_denied;
    
    if (avc_denied_addr) {
        hook_err_t err = hook_wrap12((void *)avc_denied_addr, 
                                     before_avc_denied, 0, 0);
        log_boot("hook avc_denied: %d\n", err);
    }
    
    return 0;
}

效果

  • 标记为豁免的进程(如 root shell)绕过 SELinux 检查
  • 其他进程正常执行 SELinux 策略

Hook 内存管理

hook_alloc() - 分配 relo 空间

// kernel/base/hmem.c
static hmem_t hook_hmem = { 0 };

void hook_mem_add(uint64_t start, uint64_t size)
{
    hmem_add(&hook_hmem, start, size);
}

uint64_t hook_alloc()
{
    return hmem_alloc(&hook_hmem, HOOK_MEM_ALLOC_SIZE);
}

hmem (hook memory) 管理器

  • 简单的线性分配器
  • 从 hook 区域(1MB)分配
  • 每次分配 HOOK_MEM_ALLOC_SIZE(通常 4KB)

高级功能:链式 Hook

hook_chain_add() - 添加到链

// kernel/base/hook.c
hook_err_t hook_chain_add(void *func_addr, hook_chain_callback before, 
                          hook_chain_callback after, void *udata)
{
    hook_chain_t *hook_chain = find_hook_chain(func_addr);
    
    if (!hook_chain) {
        // 第一次 hook,创建新链
        return hook_wrap4(func_addr, before, after, udata);
    }
    
    // 已有 hook_chain,添加到链中
    for (int i = 0; i < hook_chain->chain_items_max; i++) {
        if (hook_chain->states[i] == CHAIN_ITEM_STATE_NONE) {
            hook_chain->befores[i] = before;
            hook_chain->afters[i] = after;
            hook_chain->udata[i] = udata;
            hook_chain->states[i] = CHAIN_ITEM_STATE_READY;
            return HOOK_NO_ERR;
        }
    }
    
    return -HOOK_CHAIN_FULL;
}

多个回调的执行顺序

// before: 按添加顺序
before_1() -> before_2() -> before_3()

// 原始函数
original_func()

// after: 反向顺序
after_3() -> after_2() -> after_1()

实际 Hook 示例

示例 1:监控 execve

void before_execve(hook_fargs3_t *args, void *udata)
{
    const char __user *filename = (const char __user *)args->arg0;
    char buf[256];
    
    // 从用户空间复制文件名
    long len = compat_strncpy_from_user(buf, filename, sizeof(buf));
    if (len > 0) {
        pr_info("execve: %s\n", buf);
        
        // 可以拦截特定程序
        if (!strcmp(buf, "/system/bin/forbidden")) {
            args->skip_origin = 1;  // 阻止执行
            args->ret = -EACCES;    // 返回权限拒绝
        }
    }
}

// 安装 hook
unsigned long execve_addr = kallsyms_lookup_name("__arm64_sys_execve");
hook_wrap3((void *)execve_addr, before_execve, 0, 0);

示例 2:修改返回值

void after_check_permission(hook_fargs4_t *args, void *udata)
{
    int ret = args->ret;
    
    if (ret == -EACCES || ret == -EPERM) {
        // 原始函数拒绝了访问
        struct task_struct *task = current;
        struct task_ext *ext = get_task_ext(task);
        
        if (ext && ext->allow_all) {
            // 如果进程被标记为全权限,强制允许
            args->ret = 0;  // 改为允许
        }
    }
}

示例 3:修改参数

void before_open(hook_fargs4_t *args, void *udata)
{
    const char __user *filename = (const char __user *)args->arg0;
    int flags = (int)args->arg1;
    
    char buf[256];
    compat_strncpy_from_user(buf, filename, sizeof(buf));
    
    // 重定向文件访问
    if (!strcmp(buf, "/etc/hosts")) {
        static const char *redirect = "/data/local/tmp/hosts";
        args->arg0 = (uint64_t)redirect;  // 修改文件名参数
    }
}

总结

Hook 机制的核心特点

  1. 完全透明:原始函数的调用者无感知
  2. 参数可修改:before 回调可以修改参数
  3. 返回值可修改:after 回调可以修改返回值
  4. 可跳过原函数:设置 skip_origin = 1
  5. 支持链式:多个回调按顺序执行,最多 16 个
  6. 指令重定位:正确处理 PC 相对寻址
  7. PAC 感知:正确处理 PACIASP/PACIBSP 函数签名
  8. 原子化修补:通过 hotpatch + stop_machine 保证 SMP 安全

0.12.0 -> 0.13.2 技术变化总结

方面 旧版(0.12.0) 新版(0.13.2)
trampoline 大小 TRAMPOLINE_NUM = 4 TRAMPOLINE_MAX_NUM = 6
relo 缓冲区 旧公式 RELOCATE_INST_NUM = 36
PAC 处理 检测 PACIASP/PACIBSP,BTI_JC 替代
transit 定位 ADR + NOP 扫描 + container_of X16 寄存器直接传递
跳转指令 BR X17 RET X17(绕过 BTI 检查)
指令写入 手动改页表 + 逐条写入 hotpatch() 原子化写入
relo 开头 插入 BTI_JC + NOP 不再插入
BTI_JC 跳过 无条件跳过 检查 hook_get_mem_from_origin

下一篇10-syscall与supercall.md - 系统调用 hook 和用户空间通信


文档版本:2.0
最后更新:2026-06-26
对应代码版本:KernelPatch 0.13.2

评论

  • 还没有评论,来说点什么吧。

无需注册或登录,填个昵称即可评论。