无需LD_PRELOAD覆写C语言open系统调用的可行性及覆写后无open/open64输出的修复问询
解答你的两个问题
1. 不用LD_PRELOAD也能覆写open系统调用吗?
当然可以!LD_PRELOAD只是用户空间动态劫持的常用方式,还有不少其他方案可选:
- 静态编译替换:如果目标程序是静态链接的,你可以在编译时直接用自定义的
open实现覆盖标准库版本,或者替换静态库中的对应符号。 - 内核层拦截:比如用kprobes、ftrace这类内核工具,或者编写内核模块(LKM),直接在内核层面hook系统调用入口,适合全局监控场景。
- 动态插桩框架:像Frida这种工具,能在运行时注入代码替换函数实现,不需要修改程序本身或依赖环境变量。
- ELF文件修改:通过编辑目标程序的ELF重定位表,把
open的调用指向你自己的函数,不过这需要你对ELF格式有一定了解。
2. 修复你的dlhook代码无日志输出的问题
你的代码有几个关键问题,导致没打出预期的open:/open64:日志,我来帮你梳理并修复:
问题出在哪?
- 函数查找时机不对:你在每个hook函数内部调用
dlsym,不仅效率低,还会引发递归问题——比如printf内部可能调用open,导致反复进入你的hook函数,甚至让dlsym无法正确拿到原始函数地址。 - 运行方式错误:你把代码编译成了可执行文件直接运行,此时程序自己定义的
open会覆盖标准库的实现,RTLD_NEXT找不到“下一个”符号(因为当前程序已经是第一个定义open的了),自然没法调用原始的open,日志也打不出来。 - 没处理
dlsym错误:如果dlsym查找失败,你的代码会直接调用空指针,可能导致崩溃,而你完全没做错误检查。 fopen实际调用的是openat:现代glibc里fopen内部是调用openat而非open/open64,但你的openat函数也存在同样的问题。
修复后的代码
我把原始函数的查找移到了程序启动时(用__attribute__((constructor))标记的函数会在main之前执行),同时改成编译成共享库的形式,还加上了错误检查:
#define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <dlfcn.h> #include <fcntl.h> #include <stdarg.h> // 全局保存原始函数指针,只查找一次 typedef int (*orig_open_func_type)(const char *__file, int flags, ...); typedef int (*orig_openat_func_type)(int dirfd, const char *__file, int flags, ...); static orig_open_func_type orig_open; static orig_open_func_type orig_open64; static orig_openat_func_type orig_openat; // 构造函数:在main执行前初始化原始函数指针 __attribute__((constructor)) void init_hooks() { orig_open = (orig_open_func_type)dlsym(RTLD_NEXT, "open"); if (!orig_open) { fprintf(stderr, "Failed to get original open: %s\n", dlerror()); } orig_open64 = (orig_open_func_type)dlsym(RTLD_NEXT, "open64"); if (!orig_open64) { fprintf(stderr, "Failed to get original open64: %s\n", dlerror()); } orig_openat = (orig_openat_func_type)dlsym(RTLD_NEXT, "openat"); if (!orig_openat) { fprintf(stderr, "Failed to get original openat: %s\n", dlerror()); } } int open(const char *__file, int __oflag, ...) { if (!orig_open) return -1; int res; if (__oflag & O_CREAT) { va_list ap; va_start(ap, __oflag); unsigned mode = va_arg(ap, unsigned); res = orig_open(__file, __oflag, mode); va_end(ap); } else { res = orig_open(__file, __oflag); } printf("open: %d (%s)\n", res, __file); return res; } int open64(const char *__file, int __oflag, ...) { if (!orig_open64) return -1; int res; if (__oflag & O_CREAT) { va_list ap; va_start(ap, __oflag); unsigned mode = va_arg(ap, unsigned); res = orig_open64(__file, __oflag, mode); va_end(ap); } else { res = orig_open64(__file, __oflag); } printf("open64: %d (%s)\n", res, __file); return res; } int openat(int dirfd, const char *__file, int __oflag, ...) { if (!orig_openat) return -1; int res; if (__oflag & O_CREAT) { va_list ap; va_start(ap, __oflag); unsigned mode = va_arg(ap, unsigned); res = orig_openat(dirfd, __file, __oflag, mode); va_end(ap); } else { res = orig_openat(dirfd, __file, __oflag); } printf("openat: %d (%s)\n", res, __file); return res; }
正确的使用步骤
- 把代码编译成共享库:
gcc -shared -fPIC -o emload.so emload.c -ldl
- 用
LD_PRELOAD加载这个库,监控任意调用open的程序,比如查看你自己的代码文件:
LD_PRELOAD=./emload.so cat emload.c
这时你就能看到所有open/open64/openat的调用日志了。
为什么之前直接运行不行?因为当你把hook代码编译成可执行文件时,程序自己定义的open函数会被优先使用,动态链接器不会去加载标准库的open,RTLD_NEXT找不到后续的符号,所以原始函数指针是空的,自然不会有日志输出。
内容的提问来源于stack exchange,提问作者Vadim Kantorov
相关产品推荐
相关产品推荐

