LD_PRELOAD只能劫持动态链接且全局可见的函数,如malloc、open、read等libc函数;无法劫持main、__libc_start_main或静态链接函数。编译劫持库必须使用-fPIC和-shared,函数签名须与glibc完全一致,并通过dlsym(RTLD_NEXT, "func")调用原函数。

LD_PRELOAD 能劫持哪些函数
只能劫持通过动态链接调用的函数,且该函数符号必须是全局可见(default visibility)、未被 static 或 hidden 修饰、未被编译器内联或优化掉。常见可劫持目标包括:malloc、open、read、write、getenv、printf 等 libc 函数;但无法劫持 main、__libc_start_main(除非配合 __attribute__((constructor)))、或静态链接进二进制的函数(如用 -static-libgcc 或全静态编译的程序)。
编译劫持库必须加 -fPIC 和 -shared
不满足这两个标志,LD_PRELOAD 会静默失败或直接报 cannot dynamically load executable 错误。典型编译命令是:
g++ -fPIC -shared -o hook_open.so hook_open.cpp -ldl
其中 -ldl 是为了后续调用 dlsym(RTLD_NEXT, ...) 做准备。漏掉 -fPIC 会导致 relocation 错误;漏掉 -shared 生成的是普通可执行目标文件,不是合法的共享对象,ld-linux.so 拒绝加载。
函数签名必须与 glibc 完全一致
哪怕参数类型差一个 const、返回值多一个 __attribute__,都会导致符号不匹配,劫持失效——程序仍调用原函数,且无任何提示。验证方法不是靠“看着像”,而是查系统头文件或用 nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep open 确认符号名和绑定类型,再严格对照 man 手册中的原型。例如:
Linux 安全审计工具,检查 SSH 配置、开放/监听端口、防火墙规则(ufw/iptables/nftables)、失败登录尝试、sudoers 权限...
-
int open(const char *pathname, int flags, mode_t mode);—— 三参数版本必须显式声明mode_t,不能写成int -
ssize_t read(int fd, void *buf, size_t count);—— 返回类型是ssize_t,不是int;size_t不能替换成unsigned long - 若劫持
fopen,注意它在不同 GLIBC 版本中可能有多个 symbol version(如fopen@@GLIBC_2.2.5),但你只需定义一个全局符号fopen,链接器自动处理版本选择
调用原函数要用 RTLD_NEXT,别硬编码 libc 路径
想在劫持函数里转发调用原始实现,必须用 dlsym(RTLD_NEXT, "open") 获取地址,而不是 dlopen("/lib/x86_64-linux-gnu/libc.so.6", ...) 再 dlsym。原因有三:
-
RTLD_NEXT是动态链接器内置机制,指向“下一个同名符号”,天然兼容不同 libc 路径(如 Alpine 的/lib/ld-musl-x86_64.so.1)和容器环境 - 硬编码路径在非标准系统(如 musl、CentOS Stream、chroot)下直接失败
- 多次
dlopen同一 libc 可能引发引用计数异常或段错误
同时,dlsym 返回的函数指针类型必须显式转换,例如:auto real_open = reinterpret_cast —— 缺少类型转换会导致调用时栈错乱。
最容易被忽略的一点:劫持库本身不能依赖被劫持的函数初始化(比如在 __attribute__((constructor)) 里调用 printf),否则会触发无限递归崩溃。所有前置逻辑应避开劫持目标,或改用 write(2) 这类更底层、不易被连带劫持的系统调用。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










