Linux下Hook方式汇总
字数 1836 2025-08-24 20:49:31

Linux下Hook方式汇总

导语

本文详细总结了Linux系统下从用户态(Ring3)到内核态(Ring0)的各种Hook技术实现方式,包括LD_PRELOAD劫持、ptrace API调试技术、PLT劫持以及内核级别的系统调用Hook等。所有代码示例均在Linux 5.0.3 x86_64内核环境下测试通过。

用户态(Ring3) Hook技术

1. LD_PRELOAD劫持.so

原理

  • LD_PRELOAD是Linux下的环境变量,指定程序运行前优先加载的动态链接库
  • 通过劫持目标函数,可以在函数调用前执行自定义代码

实现步骤

  1. 创建伪造的.so文件,重写目标函数
  2. 在伪造函数中通过dlopen/dlsym获取原函数地址
  3. 使用LD_PRELOAD加载伪造的.so文件

示例代码

// preload.c
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>

typedef int(*Strcmp)(const char*, const char*);

int strcmp(const char* s1, const char* s2)
{
  static void* handle = NULL;
  static Strcmp org_strcmp = NULL;

  if(!handle) {
      handle = dlopen("libc.so.6", RTLD_LAZY);
      org_strcmp = (Strcmp)dlsym(handle, "strcmp");
  }
  
  printf("Hacked by way of ld_preload\n");
  return org_strcmp(s1, s2);  
}

编译命令

gcc -fPIC preload.c -shared -o preload.so -ldl

使用方法

LD_PRELOAD=./preload.so ./target

限制条件

  • 目标程序必须为动态链接
  • 无法Hook静态链接的程序

防护措施

  • 关闭LD_PRELOAD环境变量

2. ptrace API调试技术Hook

原理

  • 利用ptrace系统调用附加到目标进程
  • 修改进程内存和寄存器实现代码注入

实现步骤

  1. 使用ptrace附加目标进程
  2. 保存原始寄存器状态
  3. 分配内存(mmap)并写入shellcode
  4. 修改寄存器使执行流跳转到shellcode
  5. 恢复原始执行流程

关键函数

// 获取libc基地址
size_t getLibcbase(int pid) {
    // 读取/proc/pid/maps获取libc映射地址
}

// 获取函数地址
size_t getFuncAddr(int pid, char* funcName) {
    // 通过dlsym获取函数偏移并计算实际地址
}

// 向目标进程写入代码
void copy_code(size_t addr, char* shellcode, int len) {
    // 使用PTRACE_POKETEXT逐字节写入
}

注意事项

  • 一个进程只能被一个调试器附加
  • 需要考虑内存权限问题
  • 正确处理信号和异常

3. PLT重定向劫持Hook

原理

  • 利用ELF文件的PLT/GOT机制
  • 修改PLT表中的跳转地址实现Hook

实现步骤

  1. 获取目标进程的代码基地址(/proc/pid/maps)
  2. 定位PLT表位置
  3. 使用ptrace修改PLT表中的跳转指令
  4. 插入断点(0xcc)捕获函数调用
  5. 执行自定义代码后恢复原函数调用

关键代码

// 获取代码基地址
size_t getCodebase(pid_t pid) {
    // 读取/proc/pid/maps获取.text段地址
}

// 修改PLT表
void mod_handle(pid_t tracee, void* addr1, void* addr2) {
    // 使用PTRACE_POKEDATA修改内存
}

特点

  • 需要结合ptrace技术
  • 适用于函数第一次调用后的Hook
  • 可以绕过某些简单的反调试措施

内核态(Ring0) Hook技术

1. 系统调用表Hook

前置知识

  • 系统调用通过sys_call_table分发
  • x86_64下使用syscall指令进入内核
  • 需要获取sys_call_table地址

获取sys_call_table地址的方法

  1. 通过kallsyms_lookup_name符号查找
  2. 解析/proc/kallsyms文件
  3. 修改内核添加EXPORT_SYMBOL(sys_call_table)
  4. 内存特征码搜索

示例实现

// 使用kallsyms_lookup_name获取sys_call_table
static int get_sys_call_table(void) {
    unsigned long tmp_sys_call_table = kallsyms_lookup_name("sys_call_table");
    if(tmp_sys_call_table != 0) {
        _sys_call_table = (sys_call_ptr_t *)tmp_sys_call_table;
        printk("[+] find sys_call_table: 0x%lx\n", tmp_sys_call_table);
        return 1;
    }
    return 0;
}

// Hook系统调用示例
asmlinkage long hooked_mkdir(const char __user *pathname, umode_t mode) {
    printk("hooked sys_mkdir(), mkdir name: ");
    printk(pathname);
    return old_mkdir(pathname, mode);
}

// 修改内存页权限
pte = lookup_address((unsigned long)_sys_call_table, &level);
set_pte_atomic(pte, pte_mkwrite(*pte));

// 替换系统调用
_sys_call_table[__NR_mkdir] = (sys_call_ptr_t)hooked_mkdir;

2. 通过system_call函数搜索sys_call_table

x86_64特定实现

  1. 通过MSR寄存器获取entry_SYSCALL_64地址
  2. 搜索call do_syscall_64指令
  3. 在do_syscall_64函数中搜索sys_call_table引用

关键代码

// 获取do_syscall_64地址
static void* get_lstar_dosys(void) {
    unsigned long lstar;
    rdmsrl(MSR_LSTAR, lstar);
    
    // 搜索特征码: 48 89 e6 e8 (mov rsi,rsp; call ...)
    for(i = 0; i <= PAGE_SIZE; i++) {
        off = (char*)lstar + i;
        if(*(off) == 0x48 && *(off+1) == 0x89 && *(off+2) == 0xe6) {
            return (off + 3); // call do_syscall_64
        }
    }
    return NULL;
}

// 获取sys_call_table地址
static void* get_sys_sct(unsigned long* do_syscall_64_addr) {
    // 搜索特征码: 48 8b 04 fd (mov rax [])
    for(i = 0; i <= PAGE_SIZE; i++) {
        off = (char*)do_syscall_64_addr + i;
        if(*(off) == 0x48 && *(off+1) == 0x8b && *(off+2) == 0x04 && *(off+3) == 0xfd) {
            return (off+4);
        }
    }
    return NULL;
}

总结对比

Hook技术 层级 复杂度 隐蔽性 适用场景
LD_PRELOAD Ring3 动态链接程序
ptrace Ring3 进程注入、调试
PLT劫持 Ring3 函数调用拦截
系统调用表Hook Ring0 内核监控
内联Hook Ring0 最高 最高 内核rootkit

防护措施

  1. 针对用户态Hook:

    • 使用静态链接编译关键程序
    • 检查LD_PRELOAD环境变量
    • 实现反调试检测(ptrace)
  2. 针对内核态Hook:

    • 禁用内核模块加载
    • 保护/proc/kallsyms访问
    • 监控sys_call_table修改
    • 使用内核完整性检查工具

参考资源

  1. LINUX的LD_PRELOAD相关知识学习
  2. Intercepting and Emulating Linux System Calls with Ptrace
  3. Linux内核模块编译指南
  4. 获取Linux内核未导出符号的几种方式

以上内容涵盖了Linux系统下从用户态到内核态的主要Hook技术实现,包括原理、关键代码示例和防护措施,可作为安全研究和防御开发的参考指南。

Linux下Hook方式汇总 导语 本文详细总结了Linux系统下从用户态(Ring3)到内核态(Ring0)的各种Hook技术实现方式,包括LD_ PRELOAD劫持、ptrace API调试技术、PLT劫持以及内核级别的系统调用Hook等。所有代码示例均在Linux 5.0.3 x86_ 64内核环境下测试通过。 用户态(Ring3) Hook技术 1. LD_ PRELOAD劫持.so 原理 : LD_ PRELOAD是Linux下的环境变量,指定程序运行前优先加载的动态链接库 通过劫持目标函数,可以在函数调用前执行自定义代码 实现步骤 : 创建伪造的.so文件,重写目标函数 在伪造函数中通过dlopen/dlsym获取原函数地址 使用LD_ PRELOAD加载伪造的.so文件 示例代码 : 编译命令 : 使用方法 : 限制条件 : 目标程序必须为动态链接 无法Hook静态链接的程序 防护措施 : 关闭LD_ PRELOAD环境变量 2. ptrace API调试技术Hook 原理 : 利用ptrace系统调用附加到目标进程 修改进程内存和寄存器实现代码注入 实现步骤 : 使用ptrace附加目标进程 保存原始寄存器状态 分配内存(mmap)并写入shellcode 修改寄存器使执行流跳转到shellcode 恢复原始执行流程 关键函数 : 注意事项 : 一个进程只能被一个调试器附加 需要考虑内存权限问题 正确处理信号和异常 3. PLT重定向劫持Hook 原理 : 利用ELF文件的PLT/GOT机制 修改PLT表中的跳转地址实现Hook 实现步骤 : 获取目标进程的代码基地址(/proc/pid/maps) 定位PLT表位置 使用ptrace修改PLT表中的跳转指令 插入断点(0xcc)捕获函数调用 执行自定义代码后恢复原函数调用 关键代码 : 特点 : 需要结合ptrace技术 适用于函数第一次调用后的Hook 可以绕过某些简单的反调试措施 内核态(Ring0) Hook技术 1. 系统调用表Hook 前置知识 : 系统调用通过sys_ call_ table分发 x86_ 64下使用syscall指令进入内核 需要获取sys_ call_ table地址 获取sys_ call_ table地址的方法 : 通过kallsyms_ lookup_ name符号查找 解析/proc/kallsyms文件 修改内核添加EXPORT_ SYMBOL(sys_ call_ table) 内存特征码搜索 示例实现 : 2. 通过system_ call函数搜索sys_ call_ table x86_ 64特定实现 : 通过MSR寄存器获取entry_ SYSCALL_ 64地址 搜索call do_ syscall_ 64指令 在do_ syscall_ 64函数中搜索sys_ call_ table引用 关键代码 : 总结对比 | Hook技术 | 层级 | 复杂度 | 隐蔽性 | 适用场景 | |---------|------|-------|-------|---------| | LD_ PRELOAD | Ring3 | 低 | 低 | 动态链接程序 | | ptrace | Ring3 | 中 | 中 | 进程注入、调试 | | PLT劫持 | Ring3 | 高 | 中 | 函数调用拦截 | | 系统调用表Hook | Ring0 | 高 | 高 | 内核监控 | | 内联Hook | Ring0 | 最高 | 最高 | 内核rootkit | 防护措施 针对用户态Hook: 使用静态链接编译关键程序 检查LD_ PRELOAD环境变量 实现反调试检测(ptrace) 针对内核态Hook: 禁用内核模块加载 保护/proc/kallsyms访问 监控sys_ call_ table修改 使用内核完整性检查工具 参考资源 LINUX的LD_ PRELOAD相关知识学习 Intercepting and Emulating Linux System Calls with Ptrace Linux内核模块编译指南 获取Linux内核未导出符号的几种方式 以上内容涵盖了Linux系统下从用户态到内核态的主要Hook技术实现,包括原理、关键代码示例和防护措施,可作为安全研究和防御开发的参考指南。