β

Linux的LD_PRELOAD相关知识学习

ASPIRE 19 阅读

=Start=

缘由:

一提起LD_PRELOAD就会让我想起之前犯的一个错误——直接替换系统的glibc动态链接库导致系统无法正常登录。在那个时候才开始认识以及了解LD_PRELOAD还有它身后的相关Linux底层知识。

正文:

参考解答:
0.预备知识

编辑——(预编译)编译(汇编)——链接——运行

静态链接:在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。

动态链接:在静态情况下,它把库直接加载到程序里,而在动态链接的时候,它只是保留接口,将动态库与程序代码独立。

1.什么是LD_PRELOAD?

在UNIX的动态链接库的世界中,LD_PRELOAD就是这样一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。

我们知道,Linux的用的都是glibc,有一个叫libc.so.6的文件,这是几乎所有Linux下命令的动态链接中,其中有标准C的各种函数。对于GCC而言,默认情况下,所编译的程序中对标准C函数的链接,都是通过动态链接方式来链接libc.so.6这个函数库的。

loader在进行动态链接的时候,会将有相同符号名的符号覆盖成LD_PRELOAD指定的so文件中的符号。换句话说,可以用我们自己的so库中的函数替换原来库里有的函数,从而达到hook的目的。这和Windows下通过修改import table来hook API很类似。相比较之下,LD_PRELOAD更方便了,都不用自己写代码了,系统的loader会帮我们搞定。但是LD_PRELOAD有个限制:只能hook动态链接的库,对静态链接的库无效,因为静态链接的代码都写到可执行文件里了嘛,没有坑让你填。

2.它能起到什么作用?

它的主要功能就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入恶意程序,从而达到那不可告人的罪恶的目的。

3.该如何使用它?
main.c
#include <stdio.h>
#include <string.h>
int main( int argc, char *argv[])
{
if ( strcmp(argv[ 1 ], "test" ) )
{
printf( "Incorrect password\n" );
}
else
{
printf( "Correct password\n" );
}
return 0 ;
}

&

hook.c
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
typedef int (*STRCMP)( const char *, const char *);
int strcmp( const char *s1, const char *s2)
{
static void *handle = NULL;
static STRCMP old_strcmp = NULL;
if ( !handle )
{
handle = dlopen( "libc.so.6" , RTLD_LAZY);
old_strcmp = (STRCMP)dlsym(handle, "strcmp" );
}
printf( "hack function invoked. s1=<%s> s2=<%s>\n" , s1, s2);
return old_strcmp(s1, s2);
}

因为hook的目标是strcmp,所以typedef了一个STRCMP函数指针。由于hook的目的是要控制函数行为,所以需要从原库libc.so.6中拿到“正版”strcmp指针,保存成old_strcmp以备调用。

&

$ gcc -o test main.c
$ gcc -fPIC -shared -o hook.so hook.c -ldl
$ LD_PRELOAD=./hook.so ./test 123
hack function invoked. s1=< 123 > s2=<test>
Incorrect password
$ LD_PRELOAD=./hook.so ./test test
hack function invoked. s1=<test> s2=<test>
Correct password

其中有一点不理解的是,dlopen打开libc.so.6能拿到“正版”strcmp地址,打开libc.so就是hook后的地址。照理说libc.so不是libc.so.6的一个软链吗?为什么结果会不一样呢?

&

hook2.c
#include <stdio.h>
#include <string.h>
int strcmp( const char *s1, const char *s2)
{
printf( "hack function invoked. s1=<%s> s2=<%s>\n" , s1, s2);
/* 永远返回0,表示两个字符串相等 */
return 0 ;
}

&

$ gcc -o test main.c
$ gcc -fPIC -shared -o hook2.so hook2.c -ldl
$ ./test 123
hack function invoked. s1=< 123 > s2=<test>
Correct password
$ LD_PRELOAD=./hook2.so ./test test
hack function invoked. s1=<test> s2=<test>
Correct password
4.总结

在我们编程时,我们要随时警惕着LD_PRELOAD。

如何避免

不可否认,LD_PRELOAD是一个很难缠的问题。目前来说,要解决这个问题,只能想方设法让LD_PRELOAD失效。目前而言,有以下面两种方法可以让LD_PRELOAD失效。

1)通过静态链接。使用gcc的-static参数可以把 libc.so .6静态链入执行程序中。但这也就意味着你的程序不再支持动态链接。

2)通过设置执行文件的setgid / setuid标志。在有SUID权限的执行文件,系统会忽略LD_PRELOAD环境变量。也就是说,如果你有以root方式运行的程序,最好设置上SUID权限。(如:chmod 4755 daemon)

在一些UNIX版本上,如果你想要使用LD_PRELOAD环境变量,你需要有root权限。但不管怎么说,这些个方法目前来看并不是一个彻底的解决方案,只是一个Workaround的方法,是一种因噎废食的做法,为了安全,只能禁用。

参考链接:

==

=END=

作者:ASPIRE
Read more, write more, know more.
原文地址:Linux的LD_PRELOAD相关知识学习, 感谢原作者分享。

发表评论