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 就能跳过原函数。
- 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;
}
- 从 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() 。
详细步骤
第一步
- 找“真正该改写的入口” 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 ,避免把跳板改坏。
第二步
- 决定是“新建一条 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;
第三步
- 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; ...;
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 。
第四步
- 准备 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;
}
第五步
- 把 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 。
- 运行时真正发生了什么:从函数入口跳到 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]);
}
- 卸载/取消 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);
}
- 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);
浙公网安备 33010602011771号