预加载so的两种方式:
- 修改/etc/ld.so.preload配置文件,这种方法对配置修改之后运行的进程生效,而无法影响已经在运行的进程;
- 启动进程前设置LDPRELOAD变量(如shell中执行LD_PREOAD=/lib64/inject.so ./myprocess),则只对当前进程生效。
默认情况下进程创建的子进程也会继承Preload环境变量,而父进程可以在子进程初始化前修改子进程的环境变量,从而避免往子代传播。
LD_PRELOAD可以影响程序的运行时的链接,它允许你定义在程序运行前优先加载的动态链接库,这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。
通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数。
进程在启动后,会按照一定顺序加载动态库:
- 加载环境变量LD_PRELOAD指定的动态库
- 加载文件/etc/ld.so.preload指定的动态库
- 搜索环境变量LD_LIBRARY_PATH指定的动态库搜索路径
- 搜索路径/lib64下的动态库文件
因此可以使用预加载实现hook功能,一个简单例子,捕获所有socket函数:
// inject.c #include <stdio.h> #include <sys/socket.h> __attribute__ ((visibility("default"))) int socket(int family, int type, int protocol) { printf("detect socket call "); return -1; } __attribute__((constructor)) void main() { printf("module inject success ");
}
然后在shell环境中执行以下命令:
gcc inject.c --shared -fPIC -o inject.so # 生成so库(*)
LD_PRELOAD=$PWD/inject.so ping 127.0.0.1 # 使用LD_PRELOAD预加载
# 输出内容如下
# module inject success
# detect socket call
不可否认,LD_PRELOAD是一个很难缠的问题。目前来说,要解决这个问题,只能想方设法让LD_PRELOAD失效。目前而言,有以下面两种方法可以让LD_PRELOAD失效。
- 通过静态链接。使用gcc的-static参数可以把libc.so.6静态链入执行程序中。但这也就意味着你的程序不再支持动态链接。
- 通过设置执行文件的setgid/setuid标志。在有SUID权限的执行文件,系统会忽略LD_PRELOAD环境变量。也就是说,如果你有以root方式运行的程序,最好设置上SUID权限。
在一些UNIX版本上,如果要使用LD_PRELOAD环境变量,需要有root权限。但不管怎么说,这些个方法目前来看并不是一个彻底的解决方案,为了安全,只能禁用LD_PRELOAD。