原理
linux在进程启动后,和windows加载dll一样会按照一定的顺序加载动态链接库,相关顺序如下:
加载环境变量LD_PRELOAD指定的动态库
加载文件/etc/ld.so.preload指定的动态库
搜索环境变量LD_LIBRARY_PATH指定的动态库搜索路径
搜索路径/lib64下的动态库文件
攻击者常见使用的劫持方式大致存在以下三种:
1、可以通过LD_PRELOAD 最先被加载的特征,攻击者写一些so文件,在这个so文件里面实现一些本来对应命令要使用的函数,运行相应命令会先从该环境变量中加载我们自定义的so文件,从而劫持相应命令对应的函数,实现恶意的逻辑;
2、利用/etc/ld.so.preload是系统的默认ld预加载路径,攻击者可以写一些so文件,在这个so文件里面实现一些本来对应命令要使用的函数,然后把恶意so文件的路径写入该文件内容中,从而劫持相应命令对应的函数,实现恶意的逻辑;
3、利用linux基本都是基于glibc的特征,大部分的动态连接的基础文件都是基于几个常见的so文件,比如libc.so.6,Linux下命令的动态链接中基本上都会使用这个so文件,因为这个so文件实现了各种标准C的各种函数。对于GCC而言,默认情况下,所编译的程序中对标准C函数的链接,都是通过动态链接方式来链接libc.so.6这个函数库的;所以这里攻击者可以替换劫持该so文件,从而实现对linux的几乎所有依靠动态连接的命令的劫持;
拿第二种方式举例:
回到这个进程pid的隐藏,ps\top\netstat等命令是通过读取遍历/proc/pid内容来返回对应的pid等相关值的,读取文件目录底层是通过 readdir/readdir64 实现,这里我们可以利用ld预加载特性,编写恶意的so文件,在相关文件里面重写上面两个函数,在相关函数中当特殊名称的进程出现的时候,相关函数不做返回,或者返回为空跳过即可,并将路径添加到/etc/ld.so.preload中;
该操作对ps的隐藏效果最好,因为ps的所有结果都是完全依赖于 /proc/pid 来获取内容;netstat的话是部分依赖,仅仅获取不到pid和pname(这也是一般netstat能看到网络连接,但是看不到对应的pid和进程名的原因),其他的能拿到的;
参考项目:(https://github.com/gianlucaborello/libprocesshider)
实现:
拿第二种方式举例:
使用上面项目编译生成的.so文件放入受害机器;
修改processhider.c文件里面的process_to_filter 参数,后面修改成要隐藏的进程:
这个想要通过preload加载,也有好几种方式(1、修改$LD_PRELOAD 环境变量,添加so文件路径;2、创建/etc/ld.so.preload文件并写如对应so文件路径;3、修改动态链接器,一般来说动态链接器里面默认使用的路径是/etc/ld.so.preload,这里可以通过篡改动态连接器,修改文件路径,从而系统就会去新文件路径里面去找so文件加载),这里我们选择在受害机器的/etx/ld.so.preload文件中添加对应路径,如下:
然后这里我们模拟一个叫1234567.py的进程(这个在编译上面.so文件的时候要把这个名称写入),该进程源码如下:就是发起socket连接101.1.1.1:43端口:
import socket
def send\_tcp\_request(ip, port, message):
try:
\# 创建一个TCP/IP socket
sock = socket.socket(socket.AF\_INET, socket.SOCK\_STREAM)
\# 连接到指定的IP和端口
sock.connect((ip, port))
sock.settimeout(100)
sock.sendall(message.encode())
\# 接收服务器返回的数据
received\_data = sock.recv(1024)
print("Received:", received\_data.decode())
except socket.error as e:
print("Socket error:", e)
finally:
\# 关闭socket连接
sock.close()
\# 测试代码
if \_\_name\_\_ == "\_\_main\_\_":
ip = '101.1.1.1' \# 要连接的IP地址
port = 43 \# 要连接的端口号
message = "Hello, server!" \# 要发送的消息
send\_tcp\_request(ip, port, message)
运行进程:
现象
使用netstat -pantu
,如下可以看到这里是发现了网络连接,但是没有pid和pname:
使用ps命令,即使是在知道了被隐藏了进程的名称情况下,也查不到对应的进程:
针对原理的排查方法
1、echo $LD_PRELOAD
(排查上述原理中第一种实现方式)
查看环境变量是否被劫持,如果存在劫持情况,unset LD_PRELOAD
,并且删除查出来的对应so文件
2、cat /etc/ld.so.preload
文件,一般情况下是没有这个文件,或者是有这个文件但是文件内容为空,如果出现内容要重点排查;(排查上述原理中第二种实现方式)
如下图是被劫持的情况:
3、排查系统的动态连接器是否被劫持,也就是最后不会去/etc/ld.so.preload加载,而是去指定的地方加载(排查上述原理中类似第三种实现方式,替换libc.so.6,但是仅仅是修改了里面默认的内置ld连接文件的位置(这点笔者没有去核实,该路径可能不是在libc.so.6里面的,是其他通用so里面,但是排查方式都是校验完整性和hash))
下图先找到命令的二进制文件,然后通过readelf读取其文件头中设置的链接器,然后判断链接器是否被改动
通过时间判断是否动态连接器是否有问题:
通过rpm来校验是否有问题,这个就是通过hash去判断的,如果前面几个字符中没有出现5 就说明md5没有变动,如下图是未发现 ld-2.17.so文件发生了变动
快速排查的思路:
当发现有问题,进程被隐藏了,建议可以直接使用如下方法快速排查:
1、排查指定命令的动态链接库依赖,从上到下逐一排查so文件是否有问题
ldd /usr/bin/ps
如下:
2、直接使用 strace 命令 追踪 相关命令对文件的操作,ps进程执行的所有操作都会被记录,然后再去看是否存在可疑操作,打开了可疑的so文件等,从而判断问题出在哪个so文件
strace -o result.txt -e trace=file -f ps
效果如下:
解除方法
1、如果是环境变量劫持LD_PRELOAD 那就清空LD_PRELOAD,删除劫持的恶意so文件;
unset LD_PRELOAD
2、如果是ld.so.preload劫持,删除 /etc/ld.so.preload里面的劫持内容,删除劫持的恶意so
文件;
直接删除ld.so.preload
文件也可以
3、如果链接器被篡改了,那就重下载,替换回来;
快速排查获取隐藏的pid方法:
1、以其人之道还治其人之身,劫持攻击者的劫持,那么程序就会调用我们的劫持,通过强行指定一个LD_PRELOAD环境变量 去执行对应的命令,如果我们怀疑readdir这个函数被劫持了,那么只要我们指定的LD_PRELOAD实现了readdir这个函数那么就能解除劫持,需要注意的是先要校验我们指定的so有没有被劫持,
上面使用的是/lib64/libc.so.6,如下在其导出表里面可以看到其实现了readdir64,所以可以解除劫持
2、上传一个busybox,busybox是使用静态连接编译而成的,不会被动态链接相关机制劫持;所以直接使用busybox,可以绕过动态链接机制,拿到pid和pname;
标签:preload,文件,劫持,用户,rootkit,so,ld,隐藏,加载 From: https://www.cnblogs.com/o-O-oO/p/18461280