Hook 机制详解
概述
KernelPatch 的 Hook 机制是整个项目最核心的功能之一。它实现了对内核函数的完全透明拦截,支持 before/after 回调和参数修改。
核心文件:kernel/base/hook.c
支持的 Hook 类型:
- Inline Hook:直接修改函数入口,跳转到 trampoline
- 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 机制的核心特点:
- 完全透明:原始函数的调用者无感知
- 参数可修改:before 回调可以修改参数
- 返回值可修改:after 回调可以修改返回值
- 可跳过原函数:设置
skip_origin = 1 - 支持链式:多个回调按顺序执行,最多 16 个
- 指令重定位:正确处理 PC 相对寻址
- PAC 感知:正确处理 PACIASP/PACIBSP 函数签名
- 原子化修补:通过 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
评论
- 还没有评论,来说点什么吧。