从源码分析:Linux共享库安全风险剖析 之 运行时加载顺序风险(linux 共享库原理)

网友投稿 360 2022-10-08


从源码分析:Linux共享库安全风险剖析 之 运行时加载顺序风险(linux 共享库原理)

一:概述在Linux开发过程中,我们会遇到这样的情况,明明可执行程序elf与共享库so在同一个目录,但是进入此目录执行./elf 却会提示so找不到?这种情况对于从Windows平台过渡过来的程序员是比较费解的。这里其实就涉及到Linux上可执行程序搜索需要的库的一个范围和顺序问题。首先,是范围问题,我们可以通过查询ld的说明文档知晓范围。其次,是顺序问题,如果第三方程序制作了一个与系统库同名的so库,并把它放在了优先加载的位置,即优先于系统库目录的位置,定然就会发生劫持,这是一个风险点。网络上比较流行的Linux动态库劫持的方式是通过/etc/ld.so.preload或者环境变量LD_PRELOAD来劫持的,这种方式很容易被发现,因为它会影响所有后续启动的进程,比如有些挖矿进程隐身的时候就使用了此技术,这种方式我们先不谈,本文,我们谈一谈库加载顺序可能导致的风险,这个风险不容易被发现,但值得警惕。

二:so共享库运行时加载顺序验证1)准备Linux共享库的运行时加载顺序为:1:环境变量LD_LIBRARY_PATH指定的路径2:连接时 -rpath指定的共享库查找路径3:ldconfig 配置文件ld.so.conf指定的路径4:/lib5:/usr/lib因为Windows搜索dll的路径会搜索本地目录和PATH路径,我们也捎带测试一下这两种情况。

为了公平验证所有运行时加载顺序,我们在这几个目录都放置好同名但print输出不同的so库文件,-rpath指定好主程序的加载目录,配置好PATH路径,配置好ld.so.conf。运行主程序,查看输出结果,不断删除起作用的项,可逐一验证运行时加载顺序:

编写七个文件如下user@kali:~/pro$ cat a1.c#include

void myprint(){printf("Hello a1[LD_LIBRARY_PATH]\n");}user@kali:~/pro$ cat a2.c#include

void myprint(){printf("Hello a2[rpath]\n");}user@kali:~/pro$ cat a3.c#include

void myprint(){printf("Hello a3[ld.so.conf]\n");}user@kali:~/pro$ cat a4.c#include

void myprint(){printf("Hello a4[/lib--/usr/lib]\n");}user@kali:~/pro$ cat a5.c#include

void myprint(){printf("Hello a5 PATH\n");}user@kali:~/pro$ cat a6.c#include

void myprint(){printf("Hello a6 local\n");}user@kali:~/pro$ cat main.c#include

extern void myprint();int main(){myprint();return 0;}编译和设置,步骤如下:// 创建文件夹user@kali:~/pro$ mkdir dir_ld_conf dir_path dir_ld_path dir_rpath其中:dir_ld_conf 用来验证ld.so.conf的作用dir_ld_path 用来验证LD_LIBRARY_PATH的作用dir_path 用来验证环境变量PATH的作用dir_rpath 用来验证链接时rpath的作用

// a1.c 验证LD_LIBRARY_PATHuser@kali:~/pro$ gcc -shared -o ./dir_ld_path/liba.so a1.cuser@kali:~/pro$ export LD_LIBRARY_PATH=/home/user/pro/dir_ld_pathuser@kali:~/pro$ echo $LD_LIBRARY_PATH/home/user/pro/dir_ld_path

// a2.c 验证rpathuser@kali:~/pro$ gcc -shared -o ./dir_rpath/liba.so a2.c

// a5.c 验证PATHuser@kali:~/pro$ gcc -shared -o ./dir_path/liba.so a5.cuser@kali:~/pro$ export PATH=/home/user/pro/dir_path:$PATHuser@kali:~/pro$ echo $PATH/home/user/pro/dir_path:/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games

// a6.c 验证本地user@kali:~/pro$ gcc -shared -o liba.so a6.c

// 编译主程序[带rpath]user@kali:~/pro$ gcc -o main main.c -L. -la -Wl,-rpath,dir_rpath

可见rpath指定的路径已经被写入了可执行文件中。

2)手工验证一切就绪,我们开始验证:user@kali:~/pro$ ./mainHello a1[LD_LIBRARY_PATH]可见 LD_LIBRARY_PATH第一个起作用删除dir_ld_path之后user@kali:~/pro$ ./mainHello a2[rpath]可见-rpath连接选项第二个起作用删除dir_rpath之后user@kali:~/pro$ ./mainHello a3[ld.so.conf]可见ld.so.conf配置文件第三个起作用删除 dir_ld_conf之后user@kali:~/pro$ ./mainHello a4[/lib--/usr/lib]可见 /lib 目录生效,第四个起作用删除/lib/liba.so之后user@kali:~/pro$ ./main./main: error while loading shared libraries: liba.so: cannot open shared object file: No such file or directory可见只有这四个可以起作用,我们设置的PATH中的路径和本地路径都没有起作用。如果感兴趣的话,也可以用strace追踪一下main的系统调用过程,也能发现调用过程是这样的顺序。至此,可以得出结论:Linux的共享库so的寻找顺序为:1:LD_LIBRARY_PATH2:-rpath连接选项3:ld.so.conf配置文件4:/lib和/usr/lib

最开始是RPATH与RUNPATH同时存在时的处理方式,RPATH是旧式的编译器用的方式,RUNPATH是最新的编译器支持的方式,这两种方式可以通过在链接时指定–enable-new-dtags/–disable-new-dtags来控制。总体的处理逻辑为:if(对象没有RUNPATH) {if(对象有RPATH){使用RPATH} else {递归查找加载者(loader)的RPATH(或者有RUNPATH退出)}

if(可执行程序没有RUNPATH) {使用可执行程序的RPATH}}查找LD_LIBRARY_PATH查找正被加载对象的RUNPATH查找ld.so.cache查找默认路径

作者的编译器是最新的,默认就会使用RUNPATH。从源码也可以看出,我们之前的验证是正确的。使用–disable-new-dtags或旧式编译器的人可能会发现有时候-rpath优先于LD_LIBRARY_PATH,原因就在于程序进入了RUNPATH与RPATH的处理逻辑。可以使用readelf -d 查看动态节到底有没有RPATH或RUNPATH来进行分析

三:运行时加载顺序可能的风险分析明白了这些运行时加载顺序,最后简单概括一下:1:LD_LIBRARY_PATH的环境变量的影响范围是全局的,同LD_PRELOAD影响一样,会有风险点,过多的使用可能会影响到其他应用程序的运行,所以多用于调试模式。2:-rpath链接选项是程序生成时指定的,一般程序运行前都已经生成了,所以这项暂时构成不了威胁。3:ld.so.conf配置文件与LD_LIBRARY_PATH一样,都有同样的风险4:/lib和/usr/lib 是系统文件,所属权限属于root,因为每个so库在各Linux系统中的位置有差异,要在这个位置预防so动态库劫持,就需要对库的位置进行精确定位,精准拦截。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:线上jenkins服务器中挖坑病毒解决方案
下一篇:SpringBoot设置静态资源访问控制和封装集成方案
相关文章

 发表评论

暂时没有评论,来抢沙发吧~