简单来说,内核 Hook(钩子) 是内核提供的 "接入点",允许开发者在不修改内核源码的前提下,将自定义代码 "插入" 到内核的关键执行流程中(比如系统调用、网络数据包处理、进程调度等),实现对内核行为的监控、拦截、修改或扩展。
网络 Hook(Netfilter)
Netfilter 是 Linux 内核内置的网络钩子框架,也是 iptables/ip6tables 的底层实现,是最成熟、最安全的内核 Hook 之一(无需修改内核源码,通过内核模块实现)。
核心原理
Netfilter 在网络协议栈的 5 个关键节点(PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING)预留了钩子点,开发者可以注册自定义回调函数,数据包经过这些节点时会触发回调,实现对数据包的处理。
系统调用 Hook
系统调用是用户态程序与内核交互的入口(比如 open() 最终会调用内核的 sys_open()),Hook 系统调用可监控 / 拦截进程的核心操作。
核心原理
内核维护了一张 "系统调用表"(sys_call_table),表中存储了每个系统调用对应的内核函数地址。Hook 的本质是修改该表中的地址,将其替换为自定义函数地址,自定义函数执行完后可选择调用原系统调用,或直接返回。
关键要点
sys_call_table在内核中是只读的,需先修改内存页属性为可写;- 不同内核版本的
sys_call_table导出方式不同(部分版本未导出,需手动查找); - 操作不当易导致内核崩溃,生产环境慎用(推荐用 seccomp 替代)。
内核函数通用 Hook(Kprobe)
Kprobe(Kernel Probe)是 Linux 内核提供的通用、无侵入式的内核调试 / 监控框架 ,简单说就是:你可以在任意内核函数的 "执行前""执行中""执行后" 插入自定义回调函数(探针),实现对内核函数的参数查看、返回值捕获、执行流程监控,甚至临时修改参数 / 返回值(谨慎使用)。
和直接修改内核函数地址的 Hook 不同,Kprobe 是内核官方提供的标准化接口,无需修改内核源码、不破坏原有函数逻辑,安全性和兼容性远高于手动 Hook,是内核开发、运维、调试的标配工具。
可以把 Kprobe 比作内核函数的 "监控摄像头":
- 前置摄像头(pre_handler):函数执行前触发,能看参数、改参数;
- 后置摄像头(post_handler):函数执行后触发,能看返回值;
- 异常摄像头(fault_handler):函数执行出错时触发,能捕获内核异常。
Kprobe 包含三种核心探针类型,覆盖不同监控场景:
| 探针类型 | 触发时机 | 核心能力 | 适用场景 |
|---|---|---|---|
| Kprobe(基础) | 目标函数执行前 | 查看 / 修改函数参数,获取执行上下文 | 监控函数调用、参数审计 |
| Kretprobe | 目标函数执行后 | 捕获函数返回值,统计函数执行耗时 | 性能分析、返回值校验 |
| Jprobe | 模拟函数调用(已废弃) | 以函数自身的栈帧执行回调,参数获取更直观 | (推荐用 Kprobe 替代) |
注:Jprobe 在 Linux 4.15+ 内核中已被标记为废弃,核心能力可通过 Kprobe 实现,本文重点讲 Kprobe 和 Kretprobe。
核心原理
Kprobe 的底层实现很巧妙,核心步骤:
- 断点插入 :Kprobe 会在目标函数的入口处,将第一个指令替换为
int3(x86)或bkpt(ARM)断点指令; - 触发回调 :当 CPU 执行到该断点时,内核会触发陷阱,转而执行 Kprobe 注册的
pre_handler; - 恢复执行 :
pre_handler执行完后,内核会恢复原指令,让函数正常执行; - 后置回调 (Kretprobe):Kretprobe 会在函数入口处替换返回地址,函数执行完返回时,触发
post_handler。
整个过程对内核函数是 "透明" 的,无需修改函数逻辑,也不会影响函数的正常执行(除非回调函数主动干预)。
关键要点
- 架构差异 :函数参数 / 返回值的存储位置和架构强相关(x86_64 用
regs->di/si/dx/ax,ARM64 用regs->regs[0-7]),开发前需确认目标架构; - 函数名准确性 :内核函数名可能随版本变化(如
sys_open在部分内核中叫do_sys_open),可通过cat /proc/kallsyms | grep open查看真实函数名; - 并发控制 :
maxactive参数需合理设置(默认 10),若监控的函数调用并发高(如sys_open),需调大(如 20),否则会丢失探针事件; - 性能影响:Kprobe 会轻微增加内核开销(每调用一次函数触发一次回调),避免在回调中做复杂操作(如磁盘 IO、循环);
- 权限与协议 :模块必须声明
MODULE_LICENSE("GPL"),否则内核会拒绝注册 Kprobe(因 Kprobe 使用了 GPL 内核符号); - 禁止递归探针 :不要在 Kprobe 回调中调用被监控的函数(如监控
sys_open却在回调中调用printk(内部会调用sys_open)),会导致死循环; - 内核版本兼容 :Kprobe 接口在 2.6.16+ 内核基本稳定,但部分细节(如
struct pt_regs结构)可能变化,需适配目标内核版本。
典型应用场景
- 内核调试:定位内核函数的参数异常、返回值错误,无需重新编译内核;
- 性能分析:统计函数执行耗时(Kretprobe 记录开始 / 结束时间),找出性能瓶颈;
- 安全审计 :监控敏感内核函数(如
sys_execve、sys_socket),记录进程的关键操作; - 故障定位:捕获内核函数的异常执行(如空指针、权限错误),快速定位宕机 / 卡死原因;
- 动态追踪:SystemTap、bpftrace 等工具的底层核心就是 Kprobe(无需写内核模块,更易用)。
LSM Hook
LSM(Linux Security Module)是 Linux 内核的通用安全框架 ,而 LSM Hook 就是这个框架在 kernel 关键操作节点预留的 "安全检查钩子"------ 它允许开发者通过注册自定义的安全策略函数,对内核的核心操作(如进程创建、文件访问、网络连接等)进行前置安全校验,只有通过校验的操作才能继续执行。
可以把 LSM Hook 理解为内核的 "安全门卫":任何进程想执行敏感操作(比如读写文件、创建新进程),都必须先经过 LSM Hook 注册的安全策略检查,门卫(Hook 函数)允许了,操作才能继续;拒绝了,操作就会被阻断。
LSM Hook 是专为安全场景设计的,接口标准化、版本兼容性更好,且不会破坏内核原有逻辑,是 SELinux、AppArmor、Tomoyo 等主流 Linux 安全模块的底层实现基础。
核心特性
- 标准化接口:LSM 定义了一套统一的 Hook 接口,所有安全模块都基于这套接口开发,无需直接修改内核核心代码;
- 优先级机制:支持多个安全模块同时注册 Hook,内核会按优先级依次调用,满足复杂的安全策略叠加需求;
- 最小权限原则:Hook 仅负责 "检查 / 拦截",不参与操作的实际执行,只做安全决策,不干扰内核原有流程;
- 内核态实现:LSM Hook 运行在内核空间,无法被用户态程序绕过,安全等级远高于用户态的权限管控。
- 核心能力:对进程、文件、网络等敏感操作进行前置安全校验,实现内核级的权限管控,无法被用户态绕过;
关键要点
- 内核版本兼容性 :LSM Hook 的接口在不同内核版本中略有调整(如
security_hook_list结构体、Hook 函数参数),开发时需适配目标内核版本; - 无直接注销接口:LSM Hook 注册后,内核未提供单个 Hook 的注销函数,只能通过卸载整个模块来移除;
- 返回值规则 :Hook 函数返回
0表示 "允许操作",返回负数(如-EACCES、-EPERM)表示 "拒绝操作",需严格遵循; - 性能影响:LSM Hook 会在每个敏感操作时触发,需保证 Hook 函数逻辑极简(避免循环、复杂计算),否则会显著影响系统性能;
- 权限要求 :加载 LSM 模块需要 root 权限,且模块必须声明
MODULE_LICENSE("GPL"),否则内核会拒绝加载(因 LSM 使用了 GPL 内核符号); - 避免冲突:若系统已启用 SELinux/AppArmor 等安全模块,自定义 LSM Hook 会与这些模块的策略叠加(按优先级执行),需注意策略冲突。
典型应用场景
- SELinux/AppArmor:这两个主流 Linux 安全模块完全基于 LSM Hook 实现,提供细粒度的强制访问控制(MAC);
- 容器 / 云原生安全:Docker、Kubernetes 通过 LSM Hook 实现容器的隔离策略(如禁止容器访问主机敏感文件);
- 终端安全软件:企业级安全工具通过 LSM Hook 实现进程白名单、文件访问控制、恶意行为拦截;
- 嵌入式系统安全:嵌入式 Linux 设备(如物联网设备)通过 LSM Hook 精简安全策略,防止未授权操作;
- 审计与合规:通过 LSM Hook 记录敏感操作(如文件修改、进程创建),满足合规审计要求。
ftrace Hook
ftrace Hook 是基于 ftrace 框架实现的内核函数级钩子 ------ 无需修改内核源码、无需编写复杂内核模块,只需通过 ftrace 提供的内核接口(或用户态工具),在目标内核函数的入口 / 出口注册自定义处理函数(Hook),当函数被调用 / 返回时,ftrace 会自动触发该 Hook,实现对函数的参数查看、返回值捕获、执行耗时统计等功能。
和 Kprobe 相比,ftrace Hook 的核心特点:
- 更低开销:ftrace 基于内核静态 / 动态追踪点实现,无需像 Kprobe 那样插入断点指令,性能损耗更小;
- 更易用 :用户态可直接通过
/sys/kernel/debug/tracing接口操作,无需编写内核模块(也支持内核模块方式注册 Hook); - 更安全:ftrace 是内核原生框架,Hook 逻辑受内核管控,不会因错误代码导致内核崩溃(除非 Hook 本身有严重 bug)。
- 核心定位:Linux 内核原生的无侵入式函数追踪 Hook,无需修改内核源码,支持用户态 / 内核模块两种使用方式;
- 核心用法 :用户态通过
/sys/kernel/debug/tracing接口快速配置,内核模块方式可自定义 Hook 逻辑; - 适用场景:内核函数调用监控、性能瓶颈分析、简单的安全审计,是日常内核调试的首选工具。
核心设计原理
ftrace 本质是内核内置的 "动态插桩 + 事件回调" 框架,核心目标是:在不修改内核函数原有逻辑、不影响正常执行的前提下,对目标函数插入 "追踪点",当函数执行到追踪点时触发预设的回调逻辑(如记录日志、统计耗时)。
其核心设计遵循 3 个原则:
- 无侵入:不修改内核函数的二进制代码(或仅做极轻量、可恢复的修改);
- 低开销:默认关闭时几乎无性能损耗,开启后开销可控;
- 可扩展:支持多种追踪器(function、function_graph、sched_switch 等),适配不同场景。
ftrace 能实现无侵入追踪,核心靠插桩机制------ 向目标函数插入 "追踪点",目前主流内核(3.10+)主要用两种插桩方式:
静态插桩(Compile-Time Instrumentation)
- 原理 :编译内核时,给所有标记为
__tracepoint或FTRACE的函数头部插入一段空的跳转指令 (如 x86 的nop指令),预留追踪点; - 特点 :
- 内核编译时决定,无法动态新增(只能追踪编译时标记的函数);
- 开销极低(空指令几乎不影响执行);
- 主流发行版内核默认开启(CONFIG_FUNCTION_TRACER=y)。
动态插桩(Dynamic Instrumentation)
- 原理 :运行时通过修改内核函数的第一条指令,将其替换为跳转指令,跳转到 ftrace 的通用处理逻辑,执行完回调后再跳回原函数继续执行;
- 核心流程 (以 x86 架构为例):
- 未开启追踪时:函数第一条指令是正常执行指令(如
push %rbp); - 开启追踪时:内核将第一条指令替换为
jmp ftrace_caller(跳转到 ftrace 处理函数); ftrace_caller执行回调函数(如记录日志、统计耗时);- 回调执行完后,跳回原函数的第二条指令,继续执行原有逻辑;
- 关闭追踪时:恢复原函数的第一条指令,完全无残留。
- 未开启追踪时:函数第一条指令是正常执行指令(如
- 特点 :
- 运行时动态配置,可追踪任意内核函数(无需编译时标记);
- 开销略高于静态插桩,但仍远低于 Kprobe(无需触发断点陷阱);
- 依赖内核配置(CONFIG_DYNAMIC_FTRACE=y),主流内核默认开启。
关键补充:动态插桩时,内核会先将函数所在的内存页设置为 "可写"(修改指令),执行完后恢复为 "只读",保证内存安全。
核心组件
ftrace 的实现依赖内核的几个关键组件,先理清这些组件的作用:
| 组件 | 核心作用 |
|---|---|
| debugfs 接口 | 用户态操作入口(/sys/kernel/debug/tracing),用于配置追踪规则、开关、查看结果 |
| 追踪器(Tracer) | 不同类型的追踪逻辑实现(如 function 追踪函数调用、function_graph 追踪调用栈) |
| 插桩机制(Instrumentation) | 向内核函数插入追踪点的核心手段(静态插桩 / 动态插桩) |
| 环形缓冲区(Ring Buffer) | 存储追踪结果的内核缓冲区,用户态通过 trace/trace_pipe 读取,避免频繁拷贝 |
| ftrace_ops 结构体 | 关联 "目标函数" 和 "回调函数" 的核心结构,是 Hook 的核心载体 |
核心用法
用户态文件接口(快速实现 ftrace Hook)
这是最常用的方式,通过操作 /sys/kernel/debug/tracing 下的文件,直接注册 Hook 追踪内核函数,无需编写代码。
核心文件说明
| 文件路径 | 作用 |
|---|---|
current_tracer |
设置追踪器类型(如 function 追踪函数调用,function_graph 追踪函数调用图) |
set_ftrace_filter |
指定要 Hook 的内核函数名(白名单) |
set_ftrace_notrace |
指定不追踪的函数名(黑名单) |
tracing_on |
开关:1 开启追踪,0 关闭追踪 |
trace |
查看追踪结果(实时输出) |
trace_pipe |
实时输出追踪日志(适合持续 |
Hook 内核 sys_open 函数(追踪调用)

方式 2:内核模块方式(自定义 ftrace Hook 逻辑)
若需要自定义 Hook 行为(如解析函数参数、统计执行耗时),可通过内核模块注册 ftrace Hook 回调函数,灵活性更高
ftrace Hook 的进阶用法
追踪函数返回值(function_graph 追踪器)
用户态方式可通过 function_graph 追踪器捕获函数返回值和执行耗时:

批量 Hook 多个函数
在 set_ftrace_filter 中写入多个函数名(换行分隔),即可同时 Hook 多个函数:
sudo echo -e "sys_open\nsys_close\nsys_read" > set_ftrace_filter
关键要点
- 架构兼容性 :函数参数的寄存器位置(如
regs->di)与架构强相关(x86_64/ARM64 不同),开发时需适配目标架构; - 函数名准确性 :内核函数名可能随版本变化(如
sys_open部分内核为__x64_sys_open),可通过cat /proc/kallsyms | grep open确认; - 性能影响 :Hook 高频调用的函数(如
sys_open)会增加内核开销,建议仅在调试 / 分析时启用,生产环境慎用; - 权限要求 :操作
/sys/kernel/debug/tracing需 root 权限,内核模块需声明MODULE_LICENSE("GPL"); - 避免递归 Hook :不要在 Hook 回调中调用被 Hook 的函数(如 Hook
sys_open却在回调中用printk,其底层会调用sys_open),会导致死循环; - 内核版本要求:ftrace Hook 核心接口在 3.10+ 内核稳定,老版本(2.6.x)部分功能可能缺失。
- 环形缓冲区 :追踪结果不会直接写入磁盘,而是先存入内核环形缓冲区(大小可通过
buffer_size_kb配置),用户态读取时才拷贝到用户空间,大幅降低 IO 开销; - 回调逻辑隔离 :ftrace 的回调函数运行在 "原子上下文"(部分场景),禁止睡眠、禁止调用复杂函数(如
kmalloc),避免影响内核稳定性; - 多 CPU 同步:若目标函数在多 CPU 上被调用,ftrace 会通过 percpu 变量保证追踪逻辑的线程安全,避免竞态问题。
- 指令原子性:动态插桩时修改函数第一条指令是 "原子操作",避免函数执行到一半指令被修改导致崩溃;
- 不可追踪的函数:部分内核核心函数(如 ftrace 自身的处理函数、中断处理函数)被标记为 "不可追踪",防止递归调用导致死循环;
- 架构适配:插桩的指令格式(如跳转指令)与 CPU 架构强相关(x86/ARM/RISC-V 不同),ftrace 内核代码会针对不同架构做适配;
- 内存保护 :修改函数指令前,内核会关闭 MMU(内存管理单元)的写保护(x86 上通过
cr0寄存器),修改后恢复,保证操作合法; - 开销控制 :ftrace 支持
max_graph_depth(限制函数调用图深度)、ftrace_thresh(过滤耗时低于阈值的函数)等参数,可按需降低开销。 - 核心插桩:通过 "静态预留追踪点" 或 "动态替换函数第一条指令" 实现无侵入式插桩,是追踪的基础;
- 事件回调 :基于
ftrace_ops结构体关联目标函数和回调逻辑,函数执行时触发回调,实现追踪 / 统计; - 用户态交互:通过 debugfs 暴露配置接口,环形缓冲区存储结果,兼顾易用性和性能。