小记 LD_PRELOAD劫持
LD_PRELOAD 劫持(LD_PRELOAD Hooking)是 Linux/Unix 系统中一种非常著名且强大的技术。它利用了动态链接器(Dynamic Linker/Loader)的工作机制,允许用户在程序加载前加载自定义的共享库,从而覆盖(Override)系统标准库中的函数。
简单来说,就是“狸猫换太子”。
以下是关于 LD_PRELOAD 劫持的详细讲解,包括原理、实战示例、用途以及防御方法。
核心原理
在 Linux 中,运行动态链接的程序时,系统需要由动态链接器(通常是 /lib64/ld-linux-x86-64.so.2)将程序所需的共享库(如 libc.so)加载到内存中。
加载库的顺序通常遵循以下规则:
LD_PRELOAD环境变量指定的路径(优先级最高)。LD_LIBRARY_PATH环境变量指定的路径。/etc/ld.so.conf配置文件中的路径。- 默认系统路径(如
/lib,/usr/lib)。
劫持的关键点:
由于 LD_PRELOAD 的优先级最高,如果你编写了一个与标准库函数(例如 printf, strcmp, malloc)同名的函数,并将其编译为共享库(.so 文件),然后在运行目标程序前设置 LD_PRELOAD,链接器就会优先使用你的函数,而不是系统原来的函数。
实战演示:破解密码验证
为了演示,我们写一个简单的 C 程序,它要求用户输入密码,并使用 strcmp 进行比对。
第一步:受害者程序 (victim.c)
1 |
|
编译并运行:
1 | gcc victim.c -o victim |
第二步:攻击者代码 (hack.c)
我们要劫持 strcmp 函数,让它永远返回 0(表示两个字符串相等),无论输入什么。
1 |
|
第三步:编译攻击库
我们需要将其编译为共享对象(Shared Object, .so):
1 | gcc -shared -fPIC hack.c -o hack.so |
-shared: 生成共享库。-fPIC: 生成位置无关代码(Position Independent Code)。
第四步:实施劫持
使用 LD_PRELOAD 加载我们的 hack.so 运行受害者程序:
1 | LD_PRELOAD=./hack.so ./victim wrongpassword |
输出结果:
1 | [+] strcmp 被劫持了! 你的输入: wrongpassword, 目标: secret123 |
即使输入了错误的密码,程序也认为密码正确,因为我们将判断逻辑劫持了。
如何调用原函数?
在很多场景下(如编写外挂、监控软件),我们不仅想拦截函数,还想在做完自己的操作后,继续调用系统的原始函数。这需要使用 dlsym。
例如,劫持 puts 但仍让它输出内容:
1 |
|
编译时需要链接 dl 库:gcc -shared -fPIC hook.c -o hook.so -ldl
LD_PRELOAD 的应用场景
A. 正面用途 (White Hat / Ops)
- Hotfix (热修复): 线上服务某个库函数有 Bug,但源码丢失或无法立即重编译,可以写一个
LD_PRELOAD补丁临时替换该函数。 - Mocking/Testing: 在单元测试中模拟系统调用(如模拟
time返回固定时间,模拟read返回特定数据)。 - 性能分析: 替换
malloc和free来追踪内存泄漏(例如TCMalloc或jemalloc有时就是这样注入的)。 - Fakeroot: Linux 下的
fakeroot工具就是利用这个原理,劫持文件操作函数,让程序以为自己是 root 权限(常用于打包)。 - 加速器: 一些网络加速库通过劫持
socket相关函数,将 TCP 调用改为用户态协议栈。
B. 负面用途 (Black Hat)
- 用户态 Rootkit:
- 劫持
readdir:过滤掉含有特定名字的文件,隐藏木马文件。 - 劫持
open:阻止查看特定目录(如/proc下的恶意进程信息),隐藏进程。
- 劫持
- 后门 (Backdoor):
- 劫持 SSH 服务的加解密函数或验证函数,记录管理员密码。
- 破解软件:
- 劫持验证 License 的函数(如检测加密狗或本地时间的函数),绕过商业软件验证。
局限性与防御
虽然 LD_PRELOAD 很强大,但它不是万能的。
局限性
- 静态链接程序无效: 如果程序编译时使用了
-static(静态链接),所有库函数都打包进了一个二进制文件,没有动态链接的过程,LD_PRELOAD自然失效。 - Go 语言程序: Go 程序(默认配置下)通常不使用 libc,而是直接调用 syscall,或者使用自己的链接机制,导致很难通过这种方式劫持。
- SUID/SGID 程序限制:
- 这是 Linux 的安全机制。如果一个二进制文件设置了 SUID 位(如
sudo,passwd),为了防止普通用户通过LD_PRELOAD劫持 root 权限的程序来提权,动态链接器会忽略LD_PRELOAD变量,除非预加载的库位于系统信任的目录(如/lib)且有相应的权限。
- 这是 Linux 的安全机制。如果一个二进制文件设置了 SUID 位(如
防御与检测
- 检查环境变量: 在服务器排查入侵时,检查
env中是否有异常的LD_PRELOAD。 - 检查
/etc/ld.so.preload: 这是一个全局配置文件,里面列出的库会被所有程序预加载。这是 Rootkit 常驻的地点。 - 查看进程内存映射:
1 | cat /proc/<PID>/maps | grep .so |
如果在输出中看到了可疑路径的 .so 文件,可能就是被劫持了。
- 静态链接关键工具: 对于安全敏感的工具(如入侵检测 agent),尽可能采用静态链接,防止被轻易 Hook。
总结
LD_PRELOAD 是 Linux 下一把双刃剑。它既是开发者调试、修补、扩展功能的利器,也是黑客隐藏踪迹、窃取数据的常用手段。理解它的原理对于深入掌握 Linux 系统编程和安全攻防至关重要。
