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;
}

优点:

  1. 低开销:只需几个比较和减法操作
  2. 非全局性:与抢占兼容,可跟踪中断处理程序
  3. 无函数限制:支持任意数量的跟踪函数激活(包括递归)

6. Hook过程详解

execve()系统调用为例的完整Hook流程:

  1. 用户进程执行SYSCALL指令切换到内核模式
  2. 低级处理程序entry_SYSCALL_64()接管控制权
  3. 控制权转移到do_syscall_64()函数
  4. 通过系统调用表调用sys_execve()处理程序
  5. Ftrace框架调用注册的回调函数
  6. 回调函数检查parent_ip并决定是否Hook
  7. 修改pt_regs中的%rip寄存器,将控制权转移到包装函数
  8. 包装函数执行自定义逻辑后调用原始函数
  9. 原始函数执行完成后控制权返回包装函数
  10. 包装函数返回结果给内核
  11. 内核通过IRET指令返回用户模式

7. 注意事项

  1. 函数签名一致性:包装函数必须与被Hook函数具有完全相同的签名
  2. 递归处理:必须正确处理递归调用情况
  3. 上下文限制:包装函数继承原始函数的执行上下文限制
  4. 模块边界检查:防止模块内部调用被错误Hook
  5. 并发安全:确保Hook操作在多核环境下的安全性

8. 总结

基于Ftrace的Linux内核函数Hook技术提供了一种无需重建内核的灵活方法,具有以下特点:

  • 优点

    • 不需要重建内核
    • 相对较低的性能开销
    • 支持多种内核函数类型
    • 良好的递归处理能力
  • 限制

    • 仅支持x86_64架构
    • 需要内核版本3.19+
    • 必须是GPL兼容的模块

这种方法特别适合需要监控或修改内核行为的场景,如安全监控、性能分析等。

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的函数: 使用宏简化Hook定义: 3.2 包装函数示例 重要注意事项 : 函数签名必须完全一致 参数顺序、类型、返回值和ABI说明符都不能改变 对于系统调用, asmlinkage 修饰符特别重要 4. 实现步骤详解 4.1 获取函数地址 通过 kallsyms 获取被Hook函数的地址: 4.2 初始化Ftrace 初始化 ftrace_ops 结构并设置关键标志: 关键标志说明: FTRACE_OPS_FL_SAVE_REGS : 保存和恢复处理器寄存器 FTRACE_OPS_FL_IPMODIFY : 允许修改指令指针寄存器 4.3 安装Hook 4.4 移除Hook 5. Ftrace回调函数 5.1 基本实现 关键点: notrace 说明符防止递归跟踪 修改 pt_regs 结构中的 ip (指令指针)寄存器实现跳转 对于x86_ 64外的架构,可能需要修改其他寄存器(如PC) 5.2 防止递归调用 改进版回调,避免递归: 优点: 低开销:只需几个比较和减法操作 非全局性:与抢占兼容,可跟踪中断处理程序 无函数限制:支持任意数量的跟踪函数激活(包括递归) 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兼容的模块 这种方法特别适合需要监控或修改内核行为的场景,如安全监控、性能分析等。