Linux逆向之hook&注入
字数 1089 2025-08-24 20:49:31

Linux逆向工程:Hook与注入技术详解

1. Hook与注入基础概念

1.1 Hook技术

  • 定义:通过替换方式改变程序中原有函数功能的技术
  • 特点:稳定持久,通常用于长期修改函数行为
  • 实现方式:函数地址替换、代码修改等

1.2 注入技术

  • 定义:向目标进程插入自定义函数/代码的技术
  • 特点:一般是一次性操作,更偏向于临时性修改
  • 实现方式:动态库注入、代码注入等

2. LD_PRELOAD Hook技术

2.1 基本原理

  • LD_PRELOAD是Linux环境变量,指定共享库路径
  • 加载器会先于C语言运行库之前载入指定的共享库
  • 通过预加载包含同名函数的库实现函数替换

2.2 基本示例

目标程序(target.c)

#include <stdio.h>
#include <string.h>

int main(int argc, char const *argv[]) {
    puts("welcome!");
    sleep(1);
    char *ptr = malloc(0x100);
    puts("what's your name:");
    read(0, ptr, 0x20);
    printf("nice to meet you,%s\n", ptr);
    return 0;
}

Hook库(hook1.c)

#include <stdio.h>

int sleep(int t) {
    puts("your sleep is hook by me!");
}

编译与使用

# 编译目标程序
gcc ./target.c -o target

# 编译hook库
gcc -fPIC --shared hook1.c -o hook1.so

# 使用hook
LD_PRELOAD=./hook1.so ./target

2.3 实用Hook示例:函数调用统计

改进的目标程序

#include <stdio.h>
#include <string.h>

void function() {
    for(int i=0; i<10; ++i) {
        sleep(1);
    }
    puts("good bye~");
}

int main(int argc, char const *argv[]) {
    puts("welcome!");
    sleep(1);
    char *ptr = malloc(0x100);
    puts("what's your name:");
    read(0, ptr, 0x20);
    printf("nice to meet you,%s\n", ptr);
    function();
    return 0;
}

统计型Hook库(hook2.c)

#include <stdio.h>
#include <string.h>
#include <dlfcn.h>

typedef int (*SLEEP)(unsigned int t);
static int sleep_times = 0;

int sleep(unsigned int t) {
    static void *handle = NULL;
    static SLEEP true_sleep = NULL;
    sleep_times++;
    
    if(!handle) {
        handle = dlopen("libc.so.6", RTLD_LAZY);
        true_sleep = (SLEEP)dlsym(handle, "sleep");
    }
    
    printf("sleep has been called for %d times!\n", sleep_times);
    return true_sleep(t);
}

编译与使用

gcc -fPIC -shared -o hook2.so hook2.c -ldl
LD_PRELOAD=./hook2.so ./target

2.4 通用Hook宏定义

#include <sys/types.h>
#include <dlfcn.h>

#if defined(RTLD_NEXT)
# define REAL_LIBC RTLD_NEXT
#else
# define REAL_LIBC ((void *) -1L)
#endif

#define FN(ptr,type,name,args) ptr = (type (*)args)dlsym(REAL_LIBC, name)

使用示例:hook execve

int execve(const char *filename, char *const argv[], char *const envp[]) {
    static int (*func)(const char *, char **, char **);
    FN(func, int, "execve", (const char *, char **const, char **const));
    printf("execve has been called!");
    return (*func)(filename, (char **)argv, (char **)envp);
}

3. Ptrace Hook技术

3.1 基本原理

  • 使用ptrace系统调用附加到目标进程
  • 通过修改内存和寄存器实现hook
  • 适用于已运行进程的hook

3.2 实现步骤

  1. 附加目标程序,保存现场
  2. 查找函数真实地址
  3. 调用dlopen加载hook库
  4. 修改GOT表实现hook
  5. 恢复现场,退出ptrace

3.3 关键实现

3.3.1 获取link_map结构

struct link_map *get_linkmap(int pid) {
    // 读取文件头
    ptrace_getdata(pid, IMAGE_ADDR, ehdr, sizeof(Elf_Ehdr));
    
    // 获取program headers table地址
    phdr_addr = IMAGE_ADDR + ehdr->e_phoff;
    
    // 遍历找到.dynamic节
    for(i=0; i<ehdr->e_phnum; i++) {
        ptrace_getdata(pid, phdr_addr+i*sizeof(Elf_Phdr), phdr, sizeof(Elf_Phdr));
        if(phdr->p_type == PT_DYNAMIC) {
            dyn_addr = phdr->p_vaddr;
            break;
        }
    }
    
    // 遍历.dynamic找到.got.plt
    for(i=0; i*sizeof(Elf_Dyn)<=phdr->p_memsz; i++) {
        ptrace_getdata(pid, dyn_addr+i*sizeof(Elf_Dyn), dyn, sizeof(Elf_Dyn));
        if(dyn->d_tag == DT_PLTGOT) {
            gotplt = (Elf_Addr *)(dyn->d_un.d_ptr);
            break;
        }
    }
    
    // 获取link_map地址(GOT[1])
    ptrace_getdata(pid, (Elf_Addr)(gotplt+1), &lmap_addr, sizeof(Elf_Addr));
    
    return (struct link_map *)lmap_addr;
}

3.3.2 查找符号地址

Elf_Addr find_symbol(int pid, Elf_Addr lm_addr, char *sym_name) {
    while(lm_addr) {
        // 读取link_map结构
        ptrace_getdata(pid, lm_addr, &lmap, sizeof(struct link_map));
        lm_addr = (Elf_Addr)(lmap.l_next);
        
        // 读取so名称
        nlen = ptrace_getstr(pid, (Elf_Addr)lmap.l_name, buf, 128);
        
        // 在当前so中查找符号
        Elf_Addr sym_addr = find_symbol_in_linkmap(pid, &lmap, sym_name);
        if(sym_addr) {
            return sym_addr;
        }
    }
    return 0;
}

3.3.3 注入代码调用dlopen

int inject_code(pid_t pid, unsigned long dlopen_addr, char *libc_path) {
    // 保存寄存器状态和栈数据
    ptrace_getregs(pid, &regs);
    ptrace_getdata(pid, regs.rsp+STRLEN, sbuf1, sizeof(sbuf1));
    ptrace_getdata(pid, regs.rsp, sbuf2, sizeof(sbuf2));
    
    // 设置调用dlopen的参数
    unsigned long ret_addr = 0x666;  // 用于触发SIGSEGV
    ptrace_setdata(pid, regs.rsp, (char *)&ret_addr, sizeof(ret_addr));
    ptrace_setdata(pid, regs.rsp+STRLEN, libc_path, strlen(libc_path)+1);
    
    // 修改寄存器准备调用dlopen
    memcpy(&saved_regs, &regs, sizeof(regs));
    regs.rdi = regs.rsp + STRLEN;    // 第一个参数: lib路径
    regs.rsi = RTLD_NOW | RTLD_GLOBAL | RTLD_NODELETE; // 第二个参数: 标志
    regs.rip = dlopen_addr + 2;      // 调用地址
    
    // 设置寄存器并继续执行
    ptrace(PTRACE_SETREGS, pid, NULL, &regs);
    ptrace(PTRACE_CONT, pid, NULL, NULL);
    
    // 等待执行完成
    waitpid(pid, &status, 0);
    
    // 恢复现场
    ptrace(PTRACE_SETREGS, pid, 0, &saved_regs);
    ptrace_setdata(pid, saved_regs.rsp+STRLEN, sbuf1, sizeof(sbuf1));
    ptrace_setdata(pid, saved_regs.rsp, sbuf2, sizeof(sbuf2));
    
    return 0;
}

3.3.4 修改GOT表实现hook

// 找到旧函数在重定向表的地址
old_rel_addr = find_sym_in_rel(pid, oldfunname);

// 读取原函数地址
ptrace_getdata(pid, old_rel_addr, &target_addr, sizeof(Elf_Addr));

// 修改为hook函数地址
ptrace_setdata(pid, old_rel_addr, &new_sym_addr, sizeof(Elf_Addr));

4. 高级注入技术

4.1 构造函数/析构函数注入

#include <stdio.h>

int newputs(const char *str) {
    write(1, "hook puts! ", 11);
    puts(str);
    return 0;
}

__attribute__((constructor)) void loadMsg() {
    puts("hook.so has been injected!");
    puts("now let's do somesthing...");
    printf("->pid:%d\n\n", getpid());
}

__attribute__((destructor)) void eixtMsg() {
    puts("bye bye~");
}

4.2 注入后操作

  • 读取目标进程信息文件
  • 新开子进程执行execve
  • 创建线程执行额外代码
  • 持久化hook状态

5. 技术对比

技术 适用场景 优点 缺点
LD_PRELOAD 程序启动前hook 简单易用 不能hook已运行进程
Ptrace 已运行进程hook 功能强大 实现复杂
代码注入 临时性修改 灵活 稳定性较差

6. 防御措施

  • 检查LD_PRELOAD环境变量
  • 防止ptrace附加(PR_SET_DUMPABLE)
  • 函数地址校验
  • GOT表保护

7. 参考链接

  • https://jmpews.github.io/2016/12/27/pwn/linux进程动态so注入/
  • Linux手册页:ld-linux(8), ptrace(2), dlopen(3)
Linux逆向工程:Hook与注入技术详解 1. Hook与注入基础概念 1.1 Hook技术 定义 :通过替换方式改变程序中原有函数功能的技术 特点 :稳定持久,通常用于长期修改函数行为 实现方式 :函数地址替换、代码修改等 1.2 注入技术 定义 :向目标进程插入自定义函数/代码的技术 特点 :一般是一次性操作,更偏向于临时性修改 实现方式 :动态库注入、代码注入等 2. LD_ PRELOAD Hook技术 2.1 基本原理 LD_PRELOAD 是Linux环境变量,指定共享库路径 加载器会先于C语言运行库之前载入指定的共享库 通过预加载包含同名函数的库实现函数替换 2.2 基本示例 目标程序(target.c) Hook库(hook1.c) 编译与使用 2.3 实用Hook示例:函数调用统计 改进的目标程序 统计型Hook库(hook2.c) 编译与使用 2.4 通用Hook宏定义 使用示例:hook execve 3. Ptrace Hook技术 3.1 基本原理 使用ptrace系统调用附加到目标进程 通过修改内存和寄存器实现hook 适用于已运行进程的hook 3.2 实现步骤 附加目标程序,保存现场 查找函数真实地址 调用dlopen加载hook库 修改GOT表实现hook 恢复现场,退出ptrace 3.3 关键实现 3.3.1 获取link_ map结构 3.3.2 查找符号地址 3.3.3 注入代码调用dlopen 3.3.4 修改GOT表实现hook 4. 高级注入技术 4.1 构造函数/析构函数注入 4.2 注入后操作 读取目标进程信息文件 新开子进程执行execve 创建线程执行额外代码 持久化hook状态 5. 技术对比 | 技术 | 适用场景 | 优点 | 缺点 | |------|----------|------|------| | LD_ PRELOAD | 程序启动前hook | 简单易用 | 不能hook已运行进程 | | Ptrace | 已运行进程hook | 功能强大 | 实现复杂 | | 代码注入 | 临时性修改 | 灵活 | 稳定性较差 | 6. 防御措施 检查LD_ PRELOAD环境变量 防止ptrace附加(PR_ SET_ DUMPABLE) 函数地址校验 GOT表保护 7. 参考链接 https://jmpews.github.io/2016/12/27/pwn/linux进程动态so注入/ Linux手册页:ld-linux(8), ptrace(2), dlopen(3)