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下的环境变量,指定程序运行前优先加载的动态链接库
- 通过劫持目标函数,可以在函数调用前执行自定义代码
实现步骤:
- 创建伪造的.so文件,重写目标函数
- 在伪造函数中通过dlopen/dlsym获取原函数地址
- 使用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系统调用附加到目标进程
- 修改进程内存和寄存器实现代码注入
实现步骤:
- 使用ptrace附加目标进程
- 保存原始寄存器状态
- 分配内存(mmap)并写入shellcode
- 修改寄存器使执行流跳转到shellcode
- 恢复原始执行流程
关键函数:
// 获取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
实现步骤:
- 获取目标进程的代码基地址(/proc/pid/maps)
- 定位PLT表位置
- 使用ptrace修改PLT表中的跳转指令
- 插入断点(0xcc)捕获函数调用
- 执行自定义代码后恢复原函数调用
关键代码:
// 获取代码基地址
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地址的方法:
- 通过kallsyms_lookup_name符号查找
- 解析/proc/kallsyms文件
- 修改内核添加EXPORT_SYMBOL(sys_call_table)
- 内存特征码搜索
示例实现:
// 使用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特定实现:
- 通过MSR寄存器获取entry_SYSCALL_64地址
- 搜索call do_syscall_64指令
- 在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 |
防护措施
-
针对用户态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技术实现,包括原理、关键代码示例和防护措施,可作为安全研究和防御开发的参考指南。