在Unix操作系统的动态链接库的世界中,LD_PRELOAD就是这样一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。 这个功能主要就是用来有选择性的载入Unix操作系统不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入恶意程序,从而达到那不可告人的罪恶的目的。 我们知道,Linux的用的都是glibc,有一个叫libc.so.6的文件,这是几乎所有Linux下命令的动态链接中,其中有标准C的各种函数。Unix操作系统中对于GCC而言,默认情况下,所编译的程序中对标准C函数的链接,都是通过动态链接方式来链接libc.so.6这个函数库的。 OK。还是让我用一个例子来看一下用LD_PRELOAD来hack别人的程序。 Unix操作系统LD_PRELOAD示例一 我们写下面一段例程:
- #include <stdio.h>
- #include <string.h>
- int main(int argc, char **argv)
- {
- char passwd[] = "password";
- if (argc < 2) {
- printf("usage: %s <password>\n", argv[0]);
- return;
- }
- if (!strcmp(passwd, argv[1])) {
- printf("Correct Password!\n");
- return;
- }
- printf("Invalid Password!\n");
- }
在上面这段Unix操作系统程序中,我们使用了strcmp函数来判断两个字符串是否相等。下面,我们使用一个动态函数库来重载strcmp函数:
-
- #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);
-
- return 0;
- }
编译程序:
- $ gcc -o verifypasswd verifypasswd.c
- $ gcc -shared -o hack.so hack.c
测试一下程序:(得到正确结果)
- $ ./verifypasswd asdf
- Invalid Password!
设置LD_PRELOAD变量:(使我们重写过的strcmp函数的hack.so成为优先载入链接库) $ export LD_PRELOAD="./hack.so" 再次运行程序:
- $ ./verifypasswd asdf
- hack function invoked. s1=<password> s2=<asdf>
- Correct Password!
1)我们的hack.so中的strcmp被调用了。 2)主程序中运行结果被影响了。 如果这是一个Unix操作系统登录程序,那么这也就意味着我们用任意口令都可以进入Unix操作系统了。
(3) ldconfig命令详解,linux动态链接库
http://hi.baidu.com/ostech/blog/item/d4a40b99f0846ab9c8eaf43b
.html
动态链接库管理命令 为了让动态链接库为系统所共享,还需运行动态链接库的管理命令--ldconfig.此执行程序存放在/sbin目录下. ldconfig命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为/etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表. ldconfig通常在系统启动时运行,而当用户安装了一个新的动态链接库时,就需要手工运行这个命令. ldconfig命令行用法如下: ldconfig [-v|--verbose] [-n] [-N] [-X] [-f CONF] [-C CACHE] [-r ROOT] [-l] [-p|--print-cache] [-c FORMAT] [--format=FORMAT] [-V] [-?|--help|--usage] path... ldconfig可用的选项说明如下: (1) -v或--verbose : 用此选项时,ldconfig将显示正在扫描的目录及搜索到的动态链接库,还有它所创建的连接的名字. (2) -n : 用此选项时,ldconfig仅扫描命令行指定的目录,不扫描默认目录(/lib,/usr/lib),也不扫描配置文件/etc/ld.so.conf所列的目录. (3) -N : 此选项指示ldconfig不重建缓存文件(/etc/ld.so.cache).若未用-X选项,ldconfig照常更新文件的连接. (4) -X : 此选项指示ldconfig不更新文件的连接.若未用-N选项,则缓存文件正常更新. (5) -f CONF : 此选项指定动态链接库的配置文件为CONF,系统默认为/etc/ld.so.conf. (6) -C CACHE : 此选项指定生成的缓存文件为CACHE,系统默认的是/etc/ld.so.cache,此文件存放已排好序的可共享的动态链接库的列表. (7) -r ROOT : 此选项改变应用程序的根目录为ROOT(是调用chroot函数实现的).选择此项时,系统默认的配置文件/etc/ld.so.conf,实际对应的为ROOT/etc/ld.so.conf.如用-r /usr/zzz时,打开配置文件/etc/ld.so.conf时,实际打开的是/usr/zzz/etc/ld.so.conf文件.用此选项,可以大大增加动态链接库管理的灵活性. (8) -l : 通常情况下,ldconfig搜索动态链接库时将自动建立动态链接库的连接.选择此项时,将进入专家模式,需要手工设置连接.一般用户不用此项. (9) -p或--print-cache : 此选项指示ldconfig打印出当前缓存文件所保存的所有共享库的名字. (10) -c FORMAT 或 --format=FORMAT : 此选项用于指定缓存文件所使用的格式,共有三种:old(老格式),new(新格式)和compat(兼容格式,此为默认格式). (11) -V : 此选项打印出ldconfig的版本信息,而后退出. (12) -? 或 --help 或 --usage : 这三个选项作用相同,都是让ldconfig打印出其帮助信息,而后退出. 举三个例子: 例1: # ldconfig -p 793 libs found in cache `/etc/ld.so.cache' libzvt.so.2 (libc6) =>; /usr/lib/libzvt.so.2 libzvt.so (libc6) =>; /usr/lib/libzvt.so libz.so.1.1.3 (libc6) =>; /usr/lib/libz.so.1.1.3 libz.so.1 (libc6) =>; /lib/libz.so.1 ...... # 注: 有时候用户想知道系统中有哪些动态链接库,或者想知道系统中有没有某个动态链接库,这时,可用-p选项让ldconfig输出缓存文件中的动态链接库列表,从而查询得到.例子中,ldconfig命令的输出结果第1行表明在缓存文件/etc/ld.so.cache中找到793个共享库,第2行开始便是一系列共享库的名字及其全名(绝对路径).因为实际输出结果太多,为节省篇幅,以......表示省略的部分. 例2: # ldconfig -v /lib: liby.so.1 ->; liby.so.1 libnss_wins.so ->; libnss_wins.so ...... /usr/lib: libjscript.so.2 ->; libjscript.so.2.0.0 libkspell.so.2 ->; libkspell.so.2.0.0 ...... /usr/X11R6/lib: libmej-0.8.10.so ->; libmej-0.8.10.so libXaw3d.so.7 ->; libXaw3d.so.7.0 ...... # 注: ldconfig命令在运行正常的情况下,默认不输出什么东西.本例中用了-v选项,以使ldconfig在运行时输出正在扫描的目录及搜索到的共享库,用户可以清楚地看到运行的结果.执行结束后,ldconfig将刷新缓存文件/etc/ld.so.cache. 例3: # ldconfig /usr/zhsoft/lib | (4)ldd命令 http://www.xker.com/page/e2007/0927/35035.html 首先ldd不是一个可执行程序,而只是一个shell脚本 2、ldd能够显示可执行模块的dependency,其原理是通过设置一系列的环境变量,如下:LD_TRACE_LOADED_OBJECTS、LD_WARN、LD_BIND_NOW、LD_LIBRARY_VERSION、LD_VERBOSE等。当LD_TRACE_LOADED_OBJECTS环境变量不为空时,任何可执行程序在运行时,它都会只显示模块的dependency,而程序并不真正执行。要不你可以在shell终端测试一下,如下: (1) export LD_TRACE_LOADED_OBJECTS=1 (2) 再执行任何的程序,如ls等,看看程序的运行结果 3、ldd显示可执行模块的dependency的工作原理,其实质是通过ld-linux.so(elf动态库的装载 器)来实现的。我们知道,ld-linux.so模块会先于executable模块程序工作,并获得控制权,因此当上述的那些环境变量被设置时,ld-linux.so选择了显示可执行模块的dependency。 4、实际上可以直接执行ld-linux.so模块,如:/lib/ld-linux.so.2 --list program(这相当于ldd program) ldd命令使用方法(摘自ldd --help) 名称 ldd - 打印共享库的依赖关系 大纲 ldd [选项]... 文件... 描述 ldd 输出在命令行上指定的每个程序或共享库需要的共享库。 选项 --version 打印ldd的版本号 -v --verbose 打印所有信息,例如包括符号的版本信息 -d --data-relocs 执行符号重部署,并报告缺少的目标对象(只对ELF格式适用) -r --function-relocs 对目标对象和函数执行重新部署,并报告缺少的目标对象和函数(只对ELF格式适用) --help 用法信息 注意: ldd的标准版本与glibc2一起提供。Libc5与老版本以前提供,在一些系统中还存在。在libc5版本中长选项不支持。另一方面,glibc2版本不支持-V选项,只提供等价的--version选项。 如果命令行中给定的库名字包含'/',这个程序的libc5版本将使用它作为库名字;否则它将在标准位置搜索库。运行一个当前目录下的共享库,加前缀"./"。 错误: ldd不能工作在a.out格式的共享库上。 ldd不能工作在一些非常老的a.out程序上,这些程序在支持ldd的编译器发行前已经创建。如果你在这种类型的程序上使用ldd,程序将尝试argc = 0的运行方式,其结果不可预知。 (5)LD_LIBRARY_PATH http://skatings.blogbus.com/logs/50437681.html Linux 运行的时候,是如何管理共享库(*.so)的?在 Linux 下面,共享库的寻找和加载是由 /lib/ld.so 实现的。 ld.so 在标准路经(/lib, /usr/lib) 中寻找应用程序用到的共享库。 但是,如果需要用到的共享库在非标准路经,ld.so 怎么找到它呢? 目前,Linux 通用的做法是将非标准路经加入 /etc/ld.so.conf,然后运行 ldconfig 生成 /etc/ld.so.cache。 ld.so 加载共享库的时候,会从 ld.so.cache 查找。 传统上,Linux 的先辈 Unix 还有一个环境变量:LD_LIBRARY_PATH 来处理非标准路经的共享库。ld.so 加载共享库的时候,也会查找这个变量所设置的路经。 LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./lib export LD_LIBRARY_PATH 但是,有不少声音主张要避免使用 LD_LIBRARY_PATH 变量,尤其是作为全局变量。这些声音是: * LD_LIBRARY_PATH is not the answer - http://prefetch.net/articles/linkers.badldlibrary.html * Why LD_LIBRARY_PATH is bad - http://xahlee.org/UnixResource_dir/_/ldpath.html * LD_LIBRARY_PATH - just say no - http://blogs.sun.com/rie/date/20040710 解决这一问题的另一方法是在编译的时候通过 -R<path> 选项指定 run-time path。 1. 往/lib和/usr/lib里面加东西,是不用修改/etc/ld.so.conf的,但是完了之后要调一下ldconfig,不然这个library会找不到 2. 想往上面两个目录以外加东西的时候,一定要修改/etc/ld.so.conf,然后再调用ldconfig,不然也会找不到。 比如安装了一个mysql到/usr/local/mysql,mysql有一大堆library在/usr/local/mysql/lib下面,这时就需要在/etc/ld.so.conf下面加一行/usr/local/mysql/lib,保存过后ldconfig一下,新的library才能在程序运行时被找到。 3. 如果想在这两个目录以外放lib,但是又不想在/etc/ld.so.conf中加东西(或者是没有权限加东西)。那也可以,就是export一个全局变量LD_LIBRARY_PATH,然后运行程序的时候就会去这个目录中找library。一般来讲这只是一种临时的解决方案,在没有权限或临时需要的时候使用。 4. ldconfig做的这些东西都与运行程序时有关,跟编译时一点关系都没有。编译的时候还是该加-L就得加,不要混淆了。 5. 总之,就是不管做了什么关于library的变动后,最好都ldconfig一下,不然会出现一些意想不到的结果。不会花太多的时间,但是会省很多的事。 LD_LIBRARY_PATH 这个环境变量是大家最为熟悉的,它告诉loader:在哪些目录中可以找到共享库。可以设置多个搜索目录,这些目录之间用冒号分隔开。在linux下,还提供了另外一种方式来完成同样的功能,你可以把这些目录加到/etc/ld.so.conf中,然后调用ldconfig。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。按照惯例,除非你用上述方式指明,loader是不会在当前目录下去找共享库的,正如shell不会在当前目前找可执行文件一样。 ================================================================================================ 在shell下尝试设置LD_LIBRARY_PATH,以下面这种形式设置,老是报错bash: LD_LIBRARY_PATH: command not found, LD_LIBRARY_PATH=/usr/local/lib LD_LIBRARY_PATH = $ LD_LIBRARY_PATH:/usr/local/lib 可能是因为系统之前没有设置过LD_LIBRARY_PATH,于是改成这样: export LD_LIBRARY_PATH=/usr/local/lib 然后用 echo $LD_LIBRARY_PATH检查一下是否真的设置成功,发现可以。 接着在该shell下运行eclipse生成的可执行文件,没有错误。 另外,如果不想每次新启一个shell都设置LD_LIBRARY_PATH,可以编辑~/.bash_profile文件: $ vi ~/.bash_profile 添加: LD_LIBRARY_PATH=/usr/local/lib export LD_LIBRARY_PATH 这两行,完成之后.bash_profile如下所示: # .bash_profile # Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi # User specific environment and startup programs PATH=$PATH:$HOME/bin LD_LIBRARY_PATH=/usr/local/lib export PATH export LD_LIBRARY_PATH
然后运行
$ source ~/.bash_profile 就行了。
但是这种方法只能用在shell下,想在eclipse里面运行,还是不行: 尝试了eclipse项目properties里面的各种设置都不起作用。 用“eclipse LD_LIBRARY_PATH”作为关键字(可见关键字多么重要)才搜到这么篇文章 《eclipse+cdt+gcc编译选项控制》 http://hi.baidu.com/zsffei/blog/item/7b17c043ceb51e1772f05de1.html 才知道应该在eclipse的项目属性-->C/C++ Build-->Settings-->Tool settings-->GCC C++ Linker-->Miscellaneous的Other options (-Xlinker [option])添加 -R/usr/local/lib (6) 动态链接库的搜索优先级 http://hi.baidu.com/ilonng/blog/item/f851a0e9048d1231b80e2db8.html http://blog.csdn.net/gogdizzy/article/details/6591267
首先回答前面的问题,一共有多少种方法来指定告诉linux共享库链接器ld.so已经编译好的库libbase.so的位置呢?答案是一共有五种,它们都可以通知ld.so去哪些地方找下已经编译好的c语言函数动态库,它们是: 1)ELF可执行文件中动态段中DT_RPATH所指定的路径。即在编译目标代码时, 对gcc加入链接参数“-Wl,-rpath”指定动态库搜索路径,eg:gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/lib test.c 2)环境变量LD_LIBRARY_PATH 指定的动态库搜索路径 3)/etc/ld.so.cache中所缓存的动态库路径,这个可以通过先修改配置文件/etc/ld.so.conf中指定的动态库搜索路径,然后执行ldconfig命令来改变。 4)默认的动态库搜索路径/lib 5)默认的动态库搜索路径/usr/lib 另外:在嵌入式Linux系统的实际应用中,1和2被经常使用,也有一些相对简单的的嵌入式系统会采用4或5的路径来规范动态库,3在嵌入式系统中使用的比较少, 因为有很多系统根本就不支持ld.so.cache。 那么,动态链接器ld.so在这五种路径中,是按照什么样的顺序来搜索需要的动态共享库呢?答案这里先告知就是按照上面的顺序来得,即优先级是:1-->2-->3-->4-->5。我们可以写简单的程序来证明这个结论。 首先,写成5个函数,这5个函数名称都叫pt,但是里面的内容不一样: pt1.c #include <stdio.h> void pt(){ printf("1 path on the gcc give \n"); } pt2.c #include <stdio.h> void pt(){ printf("2 path on the LD_LIBRARY_PATH \n"); } pt3.c #include <stdio.h> void pt(){ printf("3 path on the /etc/ld.so.conf \n"); } pt4.c #include <stdio.h> void pt(){ printf("4 path on the /lib \n"); } pt5.c #include <stdio.h> void pt(){ printf("5 path on the /usr/lib \n"); } 然后,分别编译这5个函数,然后将它们分别移到上面5种情况对应的5个不同目录下: gcc -fPIC -c pt1.c -o pt.o gcc -shared pt.o -o libpt.so mv libpt.so /tmp/st/1/ gcc -fPIC -c pt2.c -o pt.o gcc -shared pt.o -o libpt.so mv libpt.so /tmp/st/2/ gcc -fPIC -c pt3.c -o pt.o gcc -shared pt.o -o libpt.so mv libpt.so /tmp/st/3/ gcc -fPIC -c pt4.c -o pt.o gcc -shared pt.o -o libpt.so mv libpt.so /lib/ gcc -fPIC -c pt5.c -o pt.o gcc -shared pt.o -o libpt.so mv libpt.so /usr/lib/ 再次,编写一个main函数m,让它来调用函数pt: m.c #include <stdio.h> int main(){ printf("start....\n"); pt(); printf("......end\n"); return 0; } 最后,准备环境,让ld都知道这5个路径: (a) 往/etc/ld.so.conf总增加一行,内容:/tmp/st/3,然后执行 ldconfig 命令 (b) export LD_LIBRARY_PATH=/tmp/st/2 另外3中路径,ld都可以得到,请接着看下面。 之后测试: gcc m.c -o m1 -L/tmp/st/1 -lpt -Wl,-rpath,/tmp/st/1 ./m1 start.... 1 path on the gcc give ......end 这里在可执行文件中动态段中DT_RPATH所指定的路径,因此需要在编译m.c的时候就指定路径,由于其他路径都也告诉了ld,很明显,此种方法优先级最高了。 gcc m.c -o m -L/tmp/st/1 -lpt ./m start.... 2 path on the LD_LIBRARY_PATH ......end 这里很显然调用了LD_LIBRARY_PATH指定了路径中的共享库,因此此种情况优先级第二。 mv /tmp/st/2/libpt.so /tmp/st/2/libpt2.so /m start.... 3 path on the /etc/ld.so.conf ......end 这里是调用了/etc/ld.so.cache中所缓存的动态库路径中的共享库,因此此种情况优先级第三。 mv /tmp/st/3/libpt.so /tmp/st/3/libpt3.so ./m start.... 4 path on the /lib ......end 这里是调用/lib中的共享库,优先级第四。 rm /lib/libpt.so ./m start.... 5 path on the /usr/lib ......end 这里是调用/lib中的共享库,优先级第五。 故证明这五种路径指定方法的优先级是1-->2-->3-->4-->5! | |