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 实现步骤
- 附加目标程序,保存现场
- 查找函数真实地址
- 调用dlopen加载hook库
- 修改GOT表实现hook
- 恢复现场,退出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, ®s);
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, ®s, 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, ®s);
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)