Hooking linux内核函数(二):如何使用Ftrace hook函数
字数 1494 2025-08-19 12:40:55
Linux内核函数Hook技术:基于Ftrace的实现
1. 概述
Ftrace是Linux内核提供的一个用于跟踪内核函数的框架,但本文介绍了一种创新性的使用方法——利用Ftrace来实现Linux内核函数的Hook。这种方法允许通过可加载的GPL模块安装钩子,而无需重建内核。
适用环境
- 架构:x86_64
- 内核版本:3.19及以上
2. Ftrace基础
Ftrace框架自2008年开始开发,具有以下功能特性:
- 显示函数调用图
- 跟踪函数调用的频率和持续时间
- 按模板过滤特定函数
实现原理:
- 基于编译器选项
-pg和-mfentry - 在每个函数开头插入特殊跟踪函数调用(
mcount()或fentry()) - 通过动态ftrace优化性能:未使用时用nop指令替换
3. Hook实现方法
3.1 数据结构
定义ftrace_hook结构体来描述每个被Hook的函数:
struct ftrace_hook {
const char *name; // 被Hook函数名
void *function; // 包装函数地址
void *original; // 存储原始函数地址的指针
unsigned long address; // 被Hook函数地址(实现细节)
struct ftrace_ops ops; // ftrace服务信息(实现细节)
};
使用宏简化Hook定义:
#define HOOK(_name, _function, _original) \
{ \
.name = (_name), \
.function = (_function), \
.original = (_original), \
}
static struct ftrace_hook hooked_functions[] = {
HOOK("sys_clone", fh_sys_clone, &real_sys_clone),
HOOK("sys_execve", fh_sys_execve, &real_sys_execve),
};
3.2 包装函数示例
// 原始函数指针
static asmlinkage long (*real_sys_execve)(const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp);
// 包装函数
static asmlinkage long fh_sys_execve(const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp)
{
long ret;
pr_debug("execve() called: filename=%p argv=%p envp=%p\n",
filename, argv, envp);
ret = real_sys_execve(filename, argv, envp);
pr_debug("execve() returns: %ld\n", ret);
return ret;
}
重要注意事项:
- 函数签名必须完全一致
- 参数顺序、类型、返回值和ABI说明符都不能改变
- 对于系统调用,
asmlinkage修饰符特别重要
4. 实现步骤详解
4.1 获取函数地址
通过kallsyms获取被Hook函数的地址:
static int resolve_hook_address(struct ftrace_hook *hook)
{
hook->address = kallsyms_lookup_name(hook->name);
if (!hook->address) {
pr_debug("unresolved symbol: %s\n", hook->name);
return -ENOENT;
}
*((unsigned long*)hook->original) = hook->address;
return 0;
}
4.2 初始化Ftrace
初始化ftrace_ops结构并设置关键标志:
int fh_install_hook(struct ftrace_hook *hook)
{
int err;
err = resolve_hook_address(hook);
if (err)
return err;
hook->ops.func = fh_ftrace_thunk;
hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_IPMODIFY;
// 后续步骤...
}
关键标志说明:
FTRACE_OPS_FL_SAVE_REGS: 保存和恢复处理器寄存器FTRACE_OPS_FL_IPMODIFY: 允许修改指令指针寄存器
4.3 安装Hook
int fh_install_hook(struct ftrace_hook *hook)
{
// 前面的初始化代码...
err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
if (err) {
pr_debug("ftrace_set_filter_ip() failed: %d\n", err);
return err;
}
err = register_ftrace_function(&hook->ops);
if (err) {
pr_debug("register_ftrace_function() failed: %d\n", err);
ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
return err;
}
return 0;
}
4.4 移除Hook
void fh_remove_hook(struct ftrace_hook *hook)
{
int err;
err = unregister_ftrace_function(&hook->ops);
if (err)
pr_debug("unregister_ftrace_function() failed: %d\n", err);
err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
if (err)
pr_debug("ftrace_set_filter_ip() failed: %d\n", err);
}
5. Ftrace回调函数
5.1 基本实现
static void notrace fh_ftrace_thunk(unsigned long ip, unsigned long parent_ip,
struct ftrace_ops *ops, struct pt_regs *regs)
{
struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);
regs->ip = (unsigned long)hook->function;
}
关键点:
notrace说明符防止递归跟踪- 修改
pt_regs结构中的ip(指令指针)寄存器实现跳转 - 对于x86_64外的架构,可能需要修改其他寄存器(如PC)
5.2 防止递归调用
改进版回调,避免递归:
static void notrace fh_ftrace_thunk(unsigned long ip, unsigned long parent_ip,
struct ftrace_ops *ops, struct pt_regs *regs)
{
struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);
/* 跳过来自当前模块的函数调用 */
if (!within_module(parent_ip, THIS_MODULE))
regs->ip = (unsigned long)hook->function;
}
优点:
- 低开销:只需几个比较和减法操作
- 非全局性:与抢占兼容,可跟踪中断处理程序
- 无函数限制:支持任意数量的跟踪函数激活(包括递归)
6. Hook过程详解
以execve()系统调用为例的完整Hook流程:
- 用户进程执行
SYSCALL指令切换到内核模式 - 低级处理程序
entry_SYSCALL_64()接管控制权 - 控制权转移到
do_syscall_64()函数 - 通过系统调用表调用
sys_execve()处理程序 - Ftrace框架调用注册的回调函数
- 回调函数检查
parent_ip并决定是否Hook - 修改
pt_regs中的%rip寄存器,将控制权转移到包装函数 - 包装函数执行自定义逻辑后调用原始函数
- 原始函数执行完成后控制权返回包装函数
- 包装函数返回结果给内核
- 内核通过
IRET指令返回用户模式
7. 注意事项
- 函数签名一致性:包装函数必须与被Hook函数具有完全相同的签名
- 递归处理:必须正确处理递归调用情况
- 上下文限制:包装函数继承原始函数的执行上下文限制
- 模块边界检查:防止模块内部调用被错误Hook
- 并发安全:确保Hook操作在多核环境下的安全性
8. 总结
基于Ftrace的Linux内核函数Hook技术提供了一种无需重建内核的灵活方法,具有以下特点:
-
优点:
- 不需要重建内核
- 相对较低的性能开销
- 支持多种内核函数类型
- 良好的递归处理能力
-
限制:
- 仅支持x86_64架构
- 需要内核版本3.19+
- 必须是GPL兼容的模块
这种方法特别适合需要监控或修改内核行为的场景,如安全监控、性能分析等。