LD_PRELOAD在setuid程序中被glibc主动清空,唯一全局生效的是需root权限的/etc/ld.so.preload;LD_LIBRARY_PATH不安全,应优先使用/etc/ld.so.conf.d/并执行ldconfig;dlopen需用RTLD_GLOBAL且确保符号显式导出;链接器问题可用--verify模式诊断。

LD_PRELOAD 被绕过?检查 /etc/ld.so.preload 和 setuid 状态
很多以为设了 LD_PRELOAD 就能拦截所有函数调用,结果在 setuid 程序(比如 sudo、passwd)里完全失效——不是配置错了,是内核和 glibc 主动丢弃了它。只要目标进程有 setuid/setgid 位,glibc 会直接清空 LD_PRELOAD、LD_LIBRARY_PATH,连日志都不留。
真正能持久生效的只有 /etc/ld.so.preload,但它要求 root 权限写入,且对所有动态链接程序全局生效(包括 init 进程启动的守护进程)。更隐蔽的风险是:如果攻击者已拿到 root,ta 可以直接改这个文件,你的 preload 防御反而成了后门入口。
- 确认目标进程是否 setuid:
ls -l /usr/bin/sudo→ 看权限位里有没有s - 检查 preload 文件是否被篡改:
stat /etc/ld.so.preload+lsattr /etc/ld.so.preload - 加固建议:用
chattr +i /etc/ld.so.preload锁定(但得先停掉所有可能写它的服务)
系统库路径被恶意覆盖?优先用 /etc/ld.so.conf.d/ 而非 LD_LIBRARY_PATH
LD_LIBRARY_PATH 看似方便,实则是最不安全的路径控制方式:它可被任意用户进程设置,且优先级高于系统配置,容易被注入或污染。而 /etc/ld.so.conf.d/ 下的文件由管理员维护,经 ldconfig 编译进二进制缓存,加载时不可被运行时环境变量覆盖。
但注意:ldconfig 不会自动重载,改完配置必须手动执行,否则新路径根本不会生效;另外,32/64 位库要分开放(/usr/lib vs /usr/lib64),混放会导致 ldconfig -p 显示不全甚至跳过整个目录。
- 添加可信路径:
echo "/opt/mysec/lib" > /etc/ld.so.conf.d/mysec.conf - 立即生效:
ldconfig(非ldconfig -v,后者只打印不更新缓存) - 验证是否加载:
ldconfig -p | grep mysec,不是ldd某个二进制
dlopen 加载失败却没报错?检查 RTLD_GLOBAL 和符号可见性
用 dlopen 手动加载 so 时,常见现象是返回非 NULL 句柄,但后续 dlsym 找不到函数——问题往往不在路径或符号名,而在加载标志。默认 RTLD_LOCAL 会让符号仅在当前 dlopen 的模块内可见,无法被后续其他模块(包括主程序)解析。
另一个坑是编译时加了 -fvisibility=hidden 却没显式用 __attribute__((visibility("default"))) 标出导出函数,导致 dlsym 查不到任何符号,连 nm -D 都看不到。
- 确保导出函数可见:
nm -D yourlib.so | grep your_func,无输出就说明没导出 - 加载时用
RTLD_GLOBAL:dlopen("libfoo.so", RTLD_LAZY | RTLD_GLOBAL) - 避免在同一个进程中反复
dlopen/dlclose同一库:glibc 不保证 dlclose 后符号表清理干净,可能引发undefined symbol
加固后程序启动失败?检查 /lib64/ld-linux-x86-64.so.2 的 --verify 模式
系统级加固常涉及替换或包装动态链接器(如用自定义 ld-linux-x86-64.so.2 插入校验逻辑),但一旦出错,程序会静默退出,strace 里只看到 execve 返回 -ENOENT 或直接 segfault——因为链接器自己挂了,连 libc 都没机会初始化。
真正能定位问题的是链接器内置的 --verify 模式:它不执行程序,只做依赖解析和符号检查,能快速暴露路径缺失、ABI 不匹配或 preload 库版本冲突。
- 验证链接器行为:
/lib64/ld-linux-x86-64.so.2 --verify ./myapp - 若提示
cannot load library,重点查ldd ./myapp输出中第一行的 interpreter 路径是否真实存在 - 不要用
patchelf直接改 interpreter 路径到未签名的自定义链接器——内核的CONFIG_SECURITY_LOAD_UBSAN或 SELinux 策略可能直接拒载
真正的难点从来不是加什么,而是删什么:你得清楚哪些路径、哪些预加载项、哪些链接器特性,在特定上下文里是冗余甚至危险的。删错一个,整个信任链就断了。










