KernelPatch框架下的InlineHook

KernelPatch框架下的InlineHook

InlineHook 的核心数据结构(hook.h) KernelPatch 的 inline hook 不是只保存一个“替换函数地址”,而是把一次 hook 拆成三段东西:

  • 入口信息与指令缓冲(hook_t) :记录被 hook 函数地址、真正要改写的入口地址、替换目标地址、以及两块缓冲:

    • origin_insts[] :被覆盖掉的原始指令(固定最多 4 条, TRAMPOLINE_NUM=4 )
    • tramp_insts[] :要写回到函数入口处的“跳板指令”
    • relo_insts[] :把那 4 条原始指令“重定位后”放到这里执行,最后再跳回原函数剩余部分
typedef struct
{
    // 输入
    uint64_t func_addr;    // 你要 Hook 的符号地址
    uint64_t origin_addr;  // 真正要改写的入口(跳过内核跳板)
    uint64_t replace_addr; // 跳转到哪里(中转 transit)
    uint64_t relo_addr;    // 重定位指令存放地址

    // 输出
    int32_t tramp_insts_len;  // 跳板指令长度
    int32_t relo_insts_len;   // 重定位指令长度

    uint32_t origin_insts[4]; // 备份被覆盖的 4 条原始指令
    uint32_t tramp_insts[4];  // 写入函数入口的跳板
    uint32_t relo_insts[...]; // 重定位后的原始指令
} hook_t;

链式 hook(hook_chain_t) :为了支持多个模块/多个回调同时挂在同一个函数上,KernelPatch 把 hook_t 放在结构体第一个成员(必须第一个),后面跟一个 “before/after 回调数组 + transit 代码缓冲”:

  • befores[] / afters[] / udata[] / states[] :最多 16 组( HOOK_CHAIN_NUM=0x10 )
  • transit[] :放“中转 wrapper 的机器码”
typedef struct _hook_chain
{
    hook_t hook; // 必须放在第一个!

    int32_t chain_items_max;
    // 最多 16 组回调
    void *befores[16];  // 原函数执行前
    void *afters[16];   // 原函数执行后
    void *udata[16];    // 自定义数据
    uint32_t transit[...]; // 中转 wrapper 机器码
} hook_chain_t;

回调参数约定(hook_fargsX_t) : hook_fargs2_t 里有 arg0/arg1/ret/skip_origin/local 等字段,你在 before/after 里改 args->ret 就能改返回值,设 skip_origin=1 就能跳过原函数。


  1. Hook 内存从哪里来:启动阶段划出 RWX 专用区 inline hook 需要一块“可写 + 可执行”的内存来放 hook_chain_t 、 relo_insts 、 transit 等内容。KernelPatch 在启动时做了:
  • 计算 _kp_hook_start/_kp_hook_end ,并把这段页表权限改成 可写、可执行(清 PXN/清 RO)
  • 调用 hook_mem_add(_kp_hook_start, HOOK_ALLOC_SIZE) 把这段区域交给 hook 的简易分配器管理
    // rwx for hook
    _kp_hook_start = (uint64_t)align_extra_end;
    _kp_hook_end = _kp_hook_start + HOOK_ALLOC_SIZE;
    log_boot("Hook: %llx, %llx\n", _kp_hook_start, _kp_hook_end);

    for (uint64_t i = _kp_hook_start; i < _kp_hook_end; i += page_size) {
        uint64_t *pte = pgtable_entry_kernel(i);
        *pte = (*pte & ~PTE_PXN & ~PTE_RDONLY) | PTE_DBM | PTE_SHARED;
    }
    flush_tlb_kernel_range(_kp_hook_start, _kp_hook_end);
    hook_mem_add(_kp_hook_start, HOOK_ALLOC_SIZE);

分配器实现非常直接:把这段区域按 hook_mem_warp_t 切块,线性扫描找 using==0 的块用掉。

int hook_mem_add(uint64_t start, int32_t size)
{
    for (uint64_t i = start; i < start + size; i += 8) {
        *(uint64_t *)i = 0;
    }
    mem_region_start = start;
    mem_region_end = start + size;
    return 0;
}

void *hook_mem_zalloc(uintptr_t origin_addr, enum hook_type type)
{
    uint64_t start = mem_region_start;
    for (uint64_t addr = start; addr < mem_region_end; addr += sizeof(hook_mem_warp_t)) {
        hook_mem_warp_t *wrap = (hook_mem_warp_t *)addr;
        if (wrap->using) continue;

        wrap->using = 1;
        wrap->addr = origin_addr;
        wrap->type = type;

        for (uintptr_t i = (uintptr_t)&wrap->chain; i < (uintptr_t)&wrap->chain + sizeof(wrap->chain); i += 8) {
            *(uint64_t *)i = 0;
        }

        // todo: assert
        if (((uintptr_t)&wrap->chain) & 0b111) {
            return 0;
        }
        return &wrap->chain;
    }
    return 0;
}

void hook_mem_free(void *hook_mem)
{
    hook_mem_warp_t *warp = local_container_of(hook_mem, hook_mem_warp_t, chain);
    warp->using = 0;
}

void *hook_get_mem_from_origin(uint64_t origin_addr)
{
    uint64_t start = mem_region_start;

    for (uint64_t addr = start; addr < mem_region_end; addr += sizeof(hook_mem_warp_t)) {
        hook_mem_warp_t *wrap = (hook_mem_warp_t *)addr;
        if (wrap->using && wrap->addr == origin_addr) {
            return &wrap->chain;
        }
    }
    return 0;
}

  1. 从 KPM 示例入口开始:hook_wrap2 → hook_wrap demo 里 hook 的是 add(int,int) :
static long inline_hook_demo_init(const char *args, const char *event, void *__user reserved)
{
    logkd("kpm inline-hook-demo init\n");

    int a = 20;
    int b = 10;

    int ret = add(a, b);
    logkd("%d + %d = %d\n", a, b, ret);

    hook_err_t err = hook_wrap2((void *)add, before_add, after_add, 0);
    logkd("hook err: %d\n", err);

    ret = add(a, b);
    logkd("%d + %d = %d\n", a, b, ret);

hook_wrap2 只是个内联封装,把 argno=2 传给 hook_wrap :

static inline hook_err_t hook_wrap2(void *func, hook_chain2_callback before, hook_chain2_callback after, void *udata)
{
    return hook_wrap(func, 2, before, after, udata);
}

所以真正的流程都在 kernel/base/hook.c 的 hook_wrap() 。


详细步骤

第一步

  1. 找“真正该改写的入口” branch_func_addr 很多 arm64 内核/CFI 场景里,函数符号地址开头可能是一个跳板(比如 hint; b xxx )。KernelPatch 在 hook 前会把这种跳板“跟到头”,拿到真正的落点地址:
  • branch_func_addr_once() :识别 B 或 HINT + B 两种模式,计算目标地址
  • branch_func_addr() :循环调用直到不再变化
#ifdef HOOK_INTO_BRANCH_FUNC

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) {
        uint64_t imm26 = bits32(inst, 25, 0);
        uint64_t imm64 = sign64_extend(imm26 << 2u, 28u);
        ret = addr + imm64;
    } else {
        addr += 4;
        uint32_t inst1 = *(uint32_t *)addr;
        if (((inst & MASK_HINT) == INST_HINT) && ((inst1 & MASK_B) == INST_B)) {
            uint64_t imm26 = bits32(inst1, 25, 0);
            uint64_t imm64 = sign64_extend(imm26 << 2u, 28u);
            ret = addr + imm64;
        }
    }
    return ret;
}

uint64_t branch_func_addr(uint64_t addr)
{
    uint64_t ret;
    for (;;) {
        ret = branch_func_addr_once(addr);
        if (ret == addr) break;
        addr = ret;
    }
    return ret;
}

hook_wrap() 一开始就用它:

  • origin = branch_func_addr(faddr);
hook_err_t hook_wrap(void *func, int32_t argno, void *before, void *after, void *udata)
{
    if (is_bad_address(func)) return -HOOK_BAD_ADDRESS;
    uint64_t faddr = (uint64_t)func;
    uint64_t origin = branch_func_addr(faddr);
    if (is_bad_address(func)) return -HOOK_BAD_ADDRESS;
    hook_chain_t *chain = (hook_chain_t *)hook_get_mem_from_origin(origin);
    if (chain) return hook_chain_add(chain, before, after, udata);
    chain = (hook_chain_t *)hook_mem_zalloc(origin, INLINE_CHAIN);
    if (!chain) return -HOOK_NO_MEM;

意义:你以为你 hook 的是 func ,实际改写的是 origin ,避免把跳板改坏。


第二步

  1. 决定是“新建一条 hook 链”还是“往已有链上追加回调” hook_wrap() 会先查这个 origin 是否已经被 hook 过:
  • hook_get_mem_from_origin(origin) :从 hook 专用区里扫描是否已有 wrap->addr == origin
void *hook_get_mem_from_origin(uint64_t origin_addr)
{
    uint64_t start = mem_region_start;

    for (uint64_t addr = start; addr < mem_region_end; addr += sizeof(hook_mem_warp_t)) {
        hook_mem_warp_t *wrap = (hook_mem_warp_t *)addr;
        if (wrap->using && wrap->addr == origin_addr) {
            return &wrap->chain;
        }
    }
    return 0;
}

有的话:直接 hook_chain_add(chain, before, after, udata) ,不会再次改写入口

    hook_chain_t *chain = (hook_chain_t *)hook_get_mem_from_origin(origin);
    if (chain) return hook_chain_add(chain, before, after, udata);

没有的话:会 hook_mem_zalloc(origin, INLINE_CHAIN) 新建 hook_chain_t ,并准备安装(下面第 6~8 步)。

    chain = (hook_chain_t *)hook_mem_zalloc(origin, INLINE_CHAIN);
    if (!chain) return -HOOK_NO_MEM;
    chain->chain_items_max = 0;
    hook_t *hook = &chain->hook;
    hook->func_addr = faddr;
    hook->origin_addr = origin;
    hook->replace_addr = (uint64_t)chain->transit;
    hook->relo_addr = (uint64_t)hook->relo_insts;
    logkv("Wrap func: %llx, origin: %llx, replace: %llx, relocate: %llx, chain: %llx\n", hook->func_addr,
          hook->origin_addr, hook->replace_addr, hook->relo_addr, chain);
    hook_err_t err = hook_prepare(hook);
    if (err) goto err;
    err = hook_chain_prepare(chain->transit, argno);
    if (err) goto err;
    err = hook_chain_add(chain, before, after, udata);
    if (err) goto err;
    hook_chain_install(chain);
    logkv("Wrap func: %llx succsseed\n", hook->func_addr);
    return HOOK_NO_ERR;

第三步

  1. hook_prepare —— 生成“入口跳板 tramp”与“重定位后的原指令 relo” 这是 inline hook 最关键的一步,入口在:
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;

    // backup origin instruction
    for (int i = 0; i < TRAMPOLINE_NUM; i++) {
        hook->origin_insts[i] = *((uint32_t *)hook->origin_addr + i);
    }
    // trampline to replace_addr
    hook->tramp_insts_len = branch_from_to(hook->tramp_insts, hook->origin_addr, hook->replace_addr);

    // relocate
    for (int i = 0; i < sizeof(hook->relo_insts) / sizeof(hook->relo_insts[0]); i++) {
        hook->relo_insts[i] = ARM64_NOP;
    }

    for (int i = 0; i < hook->tramp_insts_len; 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) {
            return -HOOK_BAD_RELO;
        }
    }

    // jump back
    uint64_t back_src_addr = hook->relo_addr + hook->relo_insts_len * 4;
    uint64_t back_dst_addr = hook->origin_addr + hook->tramp_insts_len * 4;
    uint32_t *buf = hook->relo_insts + hook->relo_insts_len;
    hook->relo_insts_len += branch_from_to(buf, back_src_addr, back_dst_addr);
    return HOOK_NO_ERR;

它做的事可以概括成:

6.1 备份入口原指令
  • 固定备份 4 条( TRAMPOLINE_NUM ):
    // backup origin instruction
    for (int i = 0; i < TRAMPOLINE_NUM; i++) {
        hook->origin_insts[i] = *((uint32_t *)hook->origin_addr + i);
    }
6.2 生成 tramp_insts:从 origin 跳到 replace
  • hook->tramp_insts_len = branch_from_to(hook->tramp_insts, hook->origin_addr, hook->replace_addr);
    // trampline to replace_addr
    hook->tramp_insts_len = branch_from_to(hook->tramp_insts, hook->origin_addr, hook->replace_addr);

这里的 replace_addr 在链式模式下不是你的“替换函数”,而是 chain->transit (中转 wrapper 机器码缓冲区)。

    hook_t *hook = &chain->hook;
    hook->func_addr = faddr;
    hook->origin_addr = origin;
    hook->replace_addr = (uint64_t)chain->transit;
    hook->relo_addr = (uint64_t)hook->relo_insts;
    logkv("Wrap func: %llx, origin: %llx, replace: %llx, relocate: %llx, chain: %llx\n", hook->func_addr,

branch_from_to() 当前实现直接走 ret_absolute() ,也就是生成:

  • LDR X17, #8
  • RET X17
  • <64-bit addr literal>
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;
    buf[3] = addr >> 32u;
    return 4;
}
KP_EXPORT_SYMBOL(ret_absolute);

int32_t branch_from_to(uint32_t *tramp_buf, uint64_t src_addr, uint64_t dst_addr)
{
#if 0
    uint32_t len = branch_relative(tramp_buf, src_addr, dst_addr);
    if (len) return len;
#endif
    // return branch_absolute(tramp_buf, dst_addr);
    return ret_absolute(tramp_buf, dst_addr);
}
KP_EXPORT_SYMBOL(branch_from_to);
6.3 重定位 origin_insts

把被覆盖的指令搬到 relo_insts 里还能正确执行 难点在于:入口处很多指令是 PC-relative 的( B/BL 、 ADR/ADRP 、literal LDR 、 CBZ/TBZ 等),搬家后偏移就错了,所以要“翻译”为等价的绝对跳/绝对取址形式。

  • relocate_inst() 先用 mask/type 表判断指令类别,然后分发到不同 relo_*() :
static __noinline hook_err_t relocate_inst(hook_t *hook, uint64_t inst_addr, uint32_t inst)
{
    hook_err_t rc = HOOK_NO_ERR;
    inst_type_t it = INST_IGNORE;
    int len = 1;

    for (int j = 0; j < sizeof(relo_len) / sizeof(relo_len[0]); j++) {
        if ((inst & masks[j]) == types[j]) {
            it = types[j];
            len = relo_len[j];
            break;
        }
    }

    switch (it) {
    case INST_B:
    case INST_BC:
    case INST_BL:
        rc = relo_b(hook, inst_addr, inst, it);
        break;
    case INST_ADR:
    case INST_ADRP:
        rc = relo_adr(hook, inst_addr, inst, it);
        break;
    case INST_LDR_32:
    case INST_LDR_64:
    case INST_LDRSW_LIT:
    case INST_PRFM_LIT:
    case INST_LDR_SIMD_32:
    case INST_LDR_SIMD_64:
    case INST_LDR_SIMD_128:
        rc = relo_ldr(hook, inst_addr, inst, it);
        break;
    case INST_CBZ:
    case INST_CBNZ:
        rc = relo_cb(hook, inst_addr, inst, it);
        break;
    case INST_TBZ:
    case INST_TBNZ:
        rc = relo_tb(hook, inst_addr, inst, it);
        break;
    case INST_IGNORE:
    default:
        rc = relo_ignore(hook, inst_addr, inst, it);
        break;
    }

    hook->relo_insts_len += len;

    return rc;
}

典型例子:

  • B/BL/条件分支 :改写成“装载绝对地址到 X17 + BR/BLR”
hook_err_t relo_b(hook_t *hook, uint64_t inst_addr, uint32_t inst, inst_type_t type)
{
    uint32_t *buf = hook->relo_insts + hook->relo_insts_len;
    uint64_t imm64;
    if (type == INST_BC) {
        uint64_t imm19 = bits32(inst, 23, 5);
        imm64 = sign64_extend(imm19 << 2u, 21u);
    } else {
        uint64_t imm26 = bits32(inst, 25, 0);
        imm64 = sign64_extend(imm26 << 2u, 28u);
    }
    uint64_t addr = inst_addr + imm64;
    addr = relo_in_tramp(hook, addr);

    uint32_t idx = 0;
    if (type == INST_BC) {
        buf[idx++] = (inst & 0xFF00001F) | 0x40u; // B.<cond> #8
        buf[idx++] = 0x14000006; // B #24
    }
    buf[idx++] = 0x58000051; // LDR X17, #8
    buf[idx++] = 0x14000003; // B #12
    buf[idx++] = addr & 0xFFFFFFFF;
    buf[idx++] = addr >> 32u;
    if (type == INST_BL) {
        buf[idx++] = 0xD63F0220; // BLR X17
    } else {
        buf[idx++] = 0xD61F0220; // BR X17
    }
    buf[idx++] = ARM64_NOP;
    return HOOK_NO_ERR;
}

ADR/ADRP :改写成 LDR Xd, #8; ...; ,避免 PC 相关

hook_err_t relo_adr(hook_t *hook, uint64_t inst_addr, uint32_t inst, inst_type_t type)
{
    uint32_t *buf = hook->relo_insts + hook->relo_insts_len;

    uint32_t xd = bits32(inst, 4, 0);
    uint64_t immlo = bits32(inst, 30, 29);
    uint64_t immhi = bits32(inst, 23, 5);
    uint64_t addr;

    if (type == INST_ADR) {
        addr = inst_addr + sign64_extend((immhi << 2u) | immlo, 21u);
    } else {
        addr = (inst_addr + sign64_extend((immhi << 14u) | (immlo << 12u), 33u)) & 0xFFFFFFFFFFFFF000;
        if (is_in_tramp(hook, addr)) return -HOOK_BAD_RELO;
    }
    buf[0] = 0x58000040u | xd; // LDR Xd, #8
    buf[1] = 0x14000003; // B #12
    buf[2] = addr & 0xFFFFFFFF;
    buf[3] = addr >> 32u;
    return HOOK_NO_ERR;

literal LDR/PRFM/向量 LDR :把目标地址算出来后装载,再用普通内存读(必要时保存/恢复寄存器)

hook_err_t relo_ldr(hook_t *hook, uint64_t inst_addr, uint32_t inst, inst_type_t type)
{
    uint32_t *buf = hook->relo_insts + hook->relo_insts_len;

    uint32_t rt = bits32(inst, 4, 0);
    uint64_t imm19 = bits32(inst, 23, 5);
    uint64_t offset = sign64_extend((imm19 << 2u), 21u);
    uint64_t addr = inst_addr + offset;

    if (is_in_tramp(hook, addr) && type != INST_PRFM_LIT) return -HOOK_BAD_RELO;

    addr = relo_in_tramp(hook, addr);

    if (type == INST_LDR_32 || type == INST_LDR_64 || type == INST_LDRSW_LIT) {
        buf[0] = 0x58000060u | rt; // LDR Xt, #12
        if (type == INST_LDR_32) {
            buf[1] = 0xB9400000 | rt | (rt << 5u); // LDR Wt, [Xt]
        } else if (type == INST_LDR_64) {
            buf[1] = 0xF9400000 | rt | (rt << 5u); // LDR Xt, [Xt]
        } else {
            // LDRSW_LIT
            buf[1] = 0xB9800000 | rt | (rt << 5u); // LDRSW Xt, [Xt]
        }
        buf[2] = 0x14000004; // B #16
        buf[3] = ARM64_NOP;
        buf[4] = addr & 0xFFFFFFFF;
        buf[5] = addr >> 32u;
    } else {
        buf[0] = 0xA93F47F0; // STP X16, X17, [SP, -0x10]
        buf[1] = 0x58000091; // LDR X17, #16
        if (type == INST_PRFM_LIT) {
            buf[2] = 0xF9800220 | rt; // PRFM Rt, [X17]
        } else if (type == INST_LDR_SIMD_32) {
            buf[2] = 0xBD400220 | rt; // LDR St, [X17]
        } else if (type == INST_LDR_SIMD_64) {
            buf[2] = 0xFD400220 | rt; // LDR Dt, [X17]
        } else {
            // LDR_SIMD_128
            buf[2] = 0x3DC00220u | rt; // LDR Qt, [X17]
        }
        buf[3] = 0xF85F83F1; // LDR X17, [SP, -0x8]
        buf[4] = 0x14000004; // B #16
        buf[5] = ARM64_NOP;
        buf[6] = addr & 0xFFFFFFFF;
        buf[7] = addr >> 32u;
    }
    return HOOK_NO_ERR;
}

CBZ/CBNZ、TBZ/TBNZ :改写为“短跳过 + 绝对跳目标”

hook_err_t relo_cb(hook_t *hook, uint64_t inst_addr, uint32_t inst, inst_type_t type)
{
    uint32_t *buf = hook->relo_insts + hook->relo_insts_len;

    uint64_t imm19 = bits32(inst, 23, 5);
    uint64_t offset = sign64_extend((imm19 << 2u), 21u);
    uint64_t addr = inst_addr + offset;
    addr = relo_in_tramp(hook, addr);

    buf[0] = (inst & 0xFF00001F) | 0x40u; // CB(N)Z Rt, #8
    buf[1] = 0x14000005; // B #20
    buf[2] = 0x58000051; // LDR X17, #8
    buf[3] = 0xd61f0220; // BR X17
    buf[4] = addr & 0xFFFFFFFF;
    buf[5] = addr >> 32u;
    return HOOK_NO_ERR;
}

其它不需要修的 :原样复制 + NOP 填充

hook_err_t relo_ignore(hook_t *hook, uint64_t inst_addr, uint32_t inst, inst_type_t type)
{
    uint32_t *buf = hook->relo_insts + hook->relo_insts_len;
    buf[0] = inst;
    buf[1] = ARM64_NOP;
    return HOOK_NO_ERR;
}

还有一个非常关键的细节:如果原指令的跳转目标落在“被覆盖的那几条入口指令范围内”,目标地址也要从 origin 映射到 relo 对应位置,否则会跳回已经被 tramp 覆盖的位置。这个映射由:

  • is_in_tramp() 判断是否在覆盖区间
  • relo_in_tramp() 计算“目标指令在 relo_insts 里的新地址”
static int is_in_tramp(hook_t *hook, uint64_t addr)
{
    uint64_t tramp_start = hook->origin_addr;
    uint64_t tramp_end = tramp_start + hook->tramp_insts_len * 4;
    if (addr >= tramp_start && addr < tramp_end) {
        return 1;
    }
    return 0;
}

static uint64_t relo_in_tramp(hook_t *hook, uint64_t addr)
{
    uint64_t tramp_start = hook->origin_addr;
    uint64_t tramp_end = tramp_start + hook->tramp_insts_len * 4;
    if (!(addr >= tramp_start && addr < tramp_end)) return addr;
    uint32_t addr_inst_index = (addr - tramp_start) / 4;
    uint64_t fix_addr = hook->relo_addr;
    for (int i = 0; i < addr_inst_index; i++) {
        inst_type_t inst = hook->origin_insts[i];
        for (int j = 0; j < sizeof(relo_len) / sizeof(relo_len[0]); j++) {
            if ((inst & masks[j]) == types[j]) {
                fix_addr += relo_len[j] * 4;
                break;
            }
        }
    }
    return fix_addr;
}
6.4 relo 最后再拼一个“跳回原函数剩余部分”
  • back_dst_addr = origin_addr + tramp_insts_len*4 (也就是入口覆盖区之后)
  • 在 relo_insts 末尾追加一段 branch_from_to() 回去
    // jump back
    uint64_t back_src_addr = hook->relo_addr + hook->relo_insts_len * 4;
    uint64_t back_dst_addr = hook->origin_addr + hook->tramp_insts_len * 4;
    uint32_t *buf = hook->relo_insts + hook->relo_insts_len;
    hook->relo_insts_len += branch_from_to(buf, back_src_addr, back_dst_addr);
    return HOOK_NO_ERR;

到这里,“原函数入口被偷走的几条指令”就有了一个可执行的替代副本: hook->relo_addr 。


第四步

  1. 准备 transit(中转 wrapper)机器码 链式 hook 不是把入口直接跳到某个 replace 函数,而是跳到 chain->transit 里的一段通用 wrapper,它负责:
  • 调 before 回调

  • 可选执行原函数(通过 hook->relo_addr )

  • 调 after 回调

  • 返回(可改 ret)
    准备过程是:把 _transit0/_transit4/_transit8/_transit12 这些函数的机器码按字拷贝到 chain->transit[] 。

  • hook_chain_prepare(transit, argno) :根据 argno 选择哪段 _transitX ,并在 transit[0] 写入 ARM64_NOP 作为“锚点”

static hook_err_t hook_chain_prepare(uint32_t *transit, int32_t argno)
{
    uint64_t transit_start, transit_end;
    switch (argno) {
    case 0:
        transit_start = (uint64_t)_transit0;
        transit_end = (uint64_t)_transit0_end;
        break;
    case 1:
    case 2:
    case 3:
    case 4:
        transit_start = (uint64_t)_transit4;
        transit_end = (uint64_t)_transit4_end;
        break;
    case 5:
    case 6:
    case 7:
    case 8:
        transit_start = (uint64_t)_transit8;
        transit_end = (uint64_t)_transit8_end;
        break;
    default:
        transit_start = (uint64_t)_transit12;
        transit_end = (uint64_t)_transit12_end;
        break;
    }

    int32_t transit_num = (transit_end - transit_start) / 4;
    // todo:assert
    if (transit_num >= TRANSIT_INST_NUM) return -HOOK_TRANSIT_NO_MEM;

    transit[0] = ARM64_NOP;
    for (int i = 0; i < transit_num; i++) {
        transit[i + 1] = ((uint32_t *)transit_start)[i];
    }
    return HOOK_NO_ERR;
}

第五步

  1. 把 before/after 注册进链,再执行 hook_install 改写入口
  • 注册回调: hook_chain_add() 找空槽,把 befores/afters/udata/states 写进去
hook_err_t hook_chain_add(hook_chain_t *chain, void *before, void *after, void *udata)
{
    for (int i = 0; i < HOOK_CHAIN_NUM; i++) {
        // todo: atomic or lock
        if (chain->states[i] == CHAIN_ITEM_STATE_EMPTY) {
            chain->states[i] = CHAIN_ITEM_STATE_BUSY;
            dsb(ish);
            chain->udata[i] = udata;
            chain->befores[i] = before;
            chain->afters[i] = after;
            if (i + 1 > chain->chain_items_max) {
                chain->chain_items_max = i + 1;
            }
            dsb(ish);
            chain->states[i] = CHAIN_ITEM_STATE_READY;
            logkv("Wrap chain add: %llx, %llx, %llx successed\n", chain->hook.func_addr, before, after);
            return HOOK_NO_ERR;
        }
    }
    logkv("Wrap chain add: %llx, %llx, %llx failed\n", chain->hook.func_addr, before, after);
    return -HOOK_CHAIN_FULL;
}
KP_EXPORT_SYMBOL(hook_chain_add);

改写入口: hook_install() 临时把代码页改可写,写入 tramp_insts ,flush icache,再恢复页表

void hook_install(hook_t *hook)
{
    uint64_t va = hook->origin_addr;
    uint64_t *entry = pgtable_entry_kernel(va);
    uint64_t ori_prot = *entry;
    *entry = (ori_prot | PTE_DBM) & ~PTE_RDONLY & 0xFFFBFFFFFFFFFFFF;
    flush_tlb_kernel_page(va);
    // todo:
    // todo: can use aarch64_insn_patch_text_nosync, aarch64_insn_patch_text directly?
    for (int32_t i = 0; i < hook->tramp_insts_len; i++) {
        *((uint32_t *)hook->origin_addr + i) = hook->tramp_insts[i];
    }
    flush_icache_all();
    *entry = ori_prot;
    flush_tlb_kernel_page(va);
}
KP_EXPORT_SYMBOL(hook_install);

这一步完成后, 函数入口已经不再执行原指令,而是先跳到 chain->transit 。


  1. 运行时真正发生了什么:从函数入口跳到 transit,再回到原函数 以 _transit4 为例(4 个参数以内都是这个版本,demo 的 add(a,b) 实际也会走 _transit4 的参数结构组织方式):
  • _transit4() 先用 adr 拿到当前 PC,然后“往回扫”直到遇到 ARM64_NOP (也就是 transit[0] ),再用 container_of 算回 hook_chain_t*
  • 组装 hook_fargs4_t :
    • skip_origin=0
    • arg0..arg3 赋值
    • chain=hook_chain
  • 正序执行所有 before
  • 如果 skip_origin==0 ,调用 origin_func = (transit4_func_t)hook_chain->hook.relo_addr ,也就是执行“重定位后的原入口 + 跳回原函数剩余”
  • 逆序执行所有 after
  • 返回 fargs.ret (所以 after 可以修改返回值)
uint64_t __attribute__((section(".transit4.text"))) __attribute__((__noinline__))
_transit4(uint64_t arg0, uint64_t arg1, uint64_t arg2, uint64_t arg3)
{
    uint64_t this_va;
    asm volatile("adr %0, ." : "=r"(this_va));
    uint32_t *vptr = (uint32_t *)this_va;
    while (*--vptr != ARM64_NOP) {
    };
    hook_chain_t *hook_chain = local_container_of((uint64_t)vptr, hook_chain_t, transit);
    hook_fargs4_t fargs;
    fargs.skip_origin = 0;
    fargs.arg0 = arg0;
    fargs.arg1 = arg1;
    fargs.arg2 = arg2;
    fargs.arg3 = arg3;
    fargs.chain = hook_chain;
    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]);
    }
    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);
    }
    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;
}

demo 里的 after 就是改 args->ret = 100; :

void after_add(hook_fargs2_t *args, void *udata)
{
    logkd("after add arg0: %d, arg1: %d, ret: %d\n", (int)args->arg0, (int)args->arg1, (int)args->ret);
    args->ret = 100;
}

并且 before/after 的执行顺序是“before 正序 / after 逆序”,这和常见 hook 框架的“栈式嵌套”一致:

  • before: for i=0..max
  • after: for i=max-1..0
    for (int32_t i = 0; i < hook_chain->chain_items_max; i++) {
        if (hook_chain->states[i] != CHAIN_ITEM_STATE_READY) continue;
        hook_chain0_callback func = hook_chain->befores[i];
        if (func) func(&fargs, hook_chain->udata[i]);
    }
    if (!fargs.skip_origin) {
        transit0_func_t origin_func = (transit0_func_t)hook_chain->hook.relo_addr;
        fargs.ret = origin_func();
    }
    for (int32_t i = hook_chain->chain_items_max - 1; i >= 0; i--) {
        if (hook_chain->states[i] != CHAIN_ITEM_STATE_READY) continue;
        hook_chain0_callback func = hook_chain->afters[i];
        if (func) func(&fargs, hook_chain->udata[i]);
    }

  1. 卸载/取消 hook:unhook 与 hook_unwrap_remove
  • demo 里 exit 用的是 unhook((void*)add) :它会定位到 origin 对应的 hook 结构,执行 hook_uninstall() 把入口指令恢复为 origin_insts[] ,再释放 hook 内存。
void unhook(void *func)
{
    uint64_t origin = branch_func_addr((uint64_t)func);
    hook_t *hook = hook_get_mem_from_origin(origin);
    if (!hook) return;
    hook_uninstall(hook);
    hook_mem_free(hook);
    logkv("Unhook func: %llx\n", func);
}
KP_EXPORT_SYMBOL(unhook);
void hook_uninstall(hook_t *hook)
{
    uint64_t va = hook->origin_addr;
    uint64_t *entry = pgtable_entry_kernel(va);
    uint64_t ori_prot = *entry;
    *entry = (ori_prot | PTE_DBM) & ~PTE_RDONLY;
    flush_tlb_kernel_page(va);
    for (int32_t i = 0; i < hook->tramp_insts_len; i++) {
        *((uint32_t *)hook->origin_addr + i) = hook->origin_insts[i];
    }
    flush_icache_all();
    *entry = ori_prot;
    flush_tlb_kernel_page(va);
}
KP_EXPORT_SYMBOL(hook_uninstall);

若你只想从链上删除某个 before/after(不一定卸载整条链),走 hook_unwrap_remove() :删除槽位后,如果链空了才会真正 uninstall+free。

void hook_unwrap_remove(void *func, void *before, void *after, int remove)
{
    if (is_bad_address(func)) return;
    uint64_t faddr = (uint64_t)func;
    uint64_t origin = branch_func_addr(faddr);
    if (is_bad_address(func)) return;
    hook_chain_t *chain = (hook_chain_t *)hook_get_mem_from_origin(origin);
    if (!chain) return;
    hook_chain_remove(chain, before, after);
    if (!remove) return;
    // todo:
    for (int i = 0; i < HOOK_CHAIN_NUM; i++) {
        if (chain->states[i] != CHAIN_ITEM_STATE_EMPTY) return;
    }
    hook_chain_uninstall(chain);
    // todo: unsafe
    hook_mem_free(chain);
    logkv("Unwrap func: %llx\n", func);
}

  1. KPM 层是怎么把 hook 代码“挂进内核”的 你打开的 kpmodule.h 里, KPM_INIT/KPM_CTL0/KPM_EXIT 本质上是把函数指针放进特定段( .kpm.init/.kpm.ctl0/.kpm.exit ),模块加载器会扫描这些段并调用它们:
typedef long (*mod_initcall_t)(const char *args, const char *event, void *reserved);
typedef long (*mod_ctl0call_t)(const char *ctl_args, char *__user out_msg, int outlen);
typedef long (*mod_ctl1call_t)(void *a1, void *a2, void *a3);
typedef long (*mod_exitcall_t)(void *reserved);

#define KPM_INIT(fn) \
    static mod_initcall_t __kpm_initcall_##fn __attribute__((__used__)) __attribute__((__section__(".kpm.init"))) = fn

#define KPM_CTL0(fn) \
    static mod_ctl0call_t __kpm_ctlmodule_##fn __attribute__((__used__)) __attribute__((__section__(".kpm.ctl0"))) = fn

#define KPM_CTL1(fn) \
    static mod_ctl1call_t __kpm_ctlmodule_##fn __attribute__((__used__)) __attribute__((__section__(".kpm.ctl1"))) = fn

#define KPM_EXIT(fn) \
    static mod_exitcall_t __kpm_exitcall_##fn __attribute__((__used__)) __attribute__((__section__(".kpm.exit"))) = fn

demo 的 inline_hook_demo_init() 里调用 hook_wrap2() ,因此 hook 的“安装”发生在模块 init 阶段。

KPM_INIT(inline_hook_demo_init);
KPM_CTL0(inline_hook_control0);
KPM_EXIT(inline_hook_demo_exit);
posted @ 2026-04-08 22:57  L1dk  阅读(57)  评论(0)    收藏  举报