一、问题描述
华为云的一台测试机配置2c4g, 只运行了一个静态网站平常访问量也不大,但是平均负载达到了1.0, 其中一个cpu核心使用率100%, top命令查不到占用cpu的进程基本能够确定中毒了。
由于总体使用率只有50%并没有触发云监控告警, 所有就没怎么关注。
吐槽下华为云的云主机安全防护, 竟然一点异常也没有发现。
如下图: cpu 0使用率99%, top命令却找不到占用cpu的进程, 很明显恶意进程被隐藏了。
二、进程隐藏方式
所谓的隐藏并非真的消失不见, 在linux系统中我们通过top、ps、htop这类工具观察进程, 而恶意进程只需要对这些工具隐藏, 也就达成了目的。
如果要实现进程隐藏通常有两个方向:
- 调整进程的活动频率, 使其不容易被观察
- 修改top,ps等工具使用的动态链接库使其过滤掉恶意进程
2.1、进程活动频率
要控制进程的活动频率,可以通过以下几种方法:
-
调整进程的优先级: 在大多数操作系统中,进程有不同的优先级,优先级高的进程在调度时会更频繁地获得 CPU 时间片,从而更频繁地执行。可以通过调整进程的优先级来控制其活动频率。例如,将进程的优先级调整为较低的值,使其获得较少的 CPU 时间片,从而减缓其执行速度。
-
休眠和延迟: 进程可以通过在关键位置插入休眠或延迟操作来控制其活动频率。通过在执行某些任务后让进程休眠一段时间,可以使其活动频率降低。这样,进程每次执行任务后会等待一段时间再继续执行,从而控制了其活动频率。
-
定时器和计时器: 进程可以利用定时器或计时器来控制自己的活动频率。通过设置定时器,在一定时间间隔后触发某个事件或执行某个任务,可以使进程在固定的时间间隔内执行一次,从而控制其活动频率。
- 资源限制: 操作系统可以对进程的资源使用进行限制,比如限制其使用的 CPU 时间、内存等资源。通过设置资源限制,可以控制进程的活动频率,防止其占用过多的系统资源。
以top为例, top工具默认情况下每3s刷新一次进程信息, 也就是说如果某个进程的活动是在两次刷新之间发生, 并且持续时间非常短, 那么它就可能在top命令中被隐藏。
对于这种情况, 我们只需要调整top命令的刷新间隔即可, 使用-d选项调整刷新频率
top -d 0.5
调整为0.5s刷新一次。
或者在top命令中交互式的调整, 如下:
- 首先运行top命令
- 按下键盘d键
- 输入刷新间隔
可以看到使用0.5s的刷新频率依然找不到恶意进程, 那么它很可能使用的修改动态链接库的方式实现隐藏。
2.2、动态链接库劫持
2.2.1、动态链接库的基本概念
库(Library)是操作系统中一组已编译好的代码和数据,可以被多个程序共享和重用。主要是将一些通用的功能、算法或工具封装为库, 这种方式一方面提供了代码重用的机制,其他开发人员可以直接使用这些库,而无需重新实现相同的功能。另一方面库可以提供标准化的接口和函数,使开发人员能够以一致的方式访问和使用特定功能。这样,开发人员可以专注于解决业务问题,而无需关注底层实现细节。
库可以分为静态库(Static Library)和动态库(Dynamic Library)两种形式。
-
动态链接库(Dynamic Library),也称为共享库(Shared Library),是一种在运行时动态加载的库。它是编译好的二进制文件,但不会被静态地链接到执行文件中,而是在程序运行时通过动态链接器(如ld.so)进行加载和链接。
- 与动态链接库相对的是静态库(Static Library),它在编译时被静态地链接到执行文件中。每个程序在编译时都会包含静态库的一份副本
Linux系统中,常用的动态链接库文件扩展名为.so(Shared Object),而静态库文件扩展名为.a(Archive)。
比如我们熟悉的glibc就是一个共享库(动态链接库),并且在Linux系统中发挥着至关重要的作用,为应用程序提供了必要的运行时支持和系统级功能。
2.2.2、动态链接库的调用机制
动态链接库的加载顺序如下:
-
程序执行:当程序运行时,操作系统加载程序的可执行文件到内存中,并开始执行应用程序代码。
-
动态链接器加载:当应用程序代码中引用了动态链接库的函数或符号时,动态链接器(ld.so)被启动,它负责加载动态链接库。
-
绑定动态链接库:动态链接器根据应用程序中的动态链接库引用,查找并加载相应的动态链接库到内存中。
动态链接器解析应用程序中对动态链接库中函数的调用,将应用程序中的函数调用与动态链接库中的实际函数绑定在一起,建立函数调用关系。
- 执行应用程序:绑定完成后,应用程序可以顺利执行,包括调用动态链接库中的函数来实现所需的功能。
图片来自网络
2.2.3、动态链接器预加载机制
由上可知, 操作系统中应用程序通过动态链接器调用动态链接库, 而在动态链接器中存在一个动态链接器预加载机制。
所谓的预加载机制是指在软件程序引用的动态链接库被加载前, 动态链接器会优先加载某些动态链接库。这种预加载具有更高的优先级, 不管程序是否引用, 这些动态链接库都会被加载。而且应用程序在调用库函数时, 如果这个库函数在多个动态链接库中同时存在, 那么链接器只会保留第一个链接的函数, 这样就可以通过在预加载机制中定义同名库函数来覆盖后加载的动态链接库。
动态连接器预加载机制通过环境变量LD_PRELOAD和默认配置文件/etc/ld.so.preload实现, 动态链接器启动时会先读取LD_PRELOAD变量和/etc/ld.so.preload, 如果这里面指定了动态链接库的路径, 那么这些动态链接库就会被优先加载。
攻击者正是利用这种预加载机制来劫持动态链接库,让其编译的恶意库被优先加载从而篡改监控数据,改变监控结果。
三、清除动态链接库劫持找到隐藏进程
在了解了动态链接库的加载机制后, 可以发现在整个加载流程中有三个点可以被利用, 分别是:
- 修改LD_PRELOAD变量指定恶意动态链接库, 攻击者可以劫持程序中对某个函数的调用,并用自己实现的函数来替代原本的库函数实现。
- 通过/etc/ld.so.preload来指定恶意动态链接库实现劫持。
- 替换动态链接器默认的预加载配置文件/etc/ld.so.preload为其他路径,然后通过替换后的预加载配置文件加载恶意动态链接库。
前两种方式都很容易察觉, 不管是LD_PRELOAD变量还是/etc/ld.so.preload文件默认情况下都是空的, 如果你发现它们有了内容, 那就需要警惕了。
而替换动态链接器默认的预加载配置文件, 这种方式相对隐蔽, 需要校验动态链接器程序并追踪程序执行过程才能发现, 并且修复起来也有些麻烦, 具体见下文。
3.1、劫持LD_PRELOAD环境变量
检测方式
#1.劫持LD_PRELOAD环境变量
[root@gopherabc ~]# echo $LD_PRELOAD
/usr/local/lib/libextrasshd.so #被劫持
[root@gopherabc ~]#
清理方式
#清理LD_PRELOAD变量
[root@gopherabc ~]# unset LD_PRELOAD
[root@gopherabc ~]# echo $LD_PRELOAD
[root@gopherabc ~]# rm -f /usr/local/lib/libextrasshd.so
3.2、劫持/etc/ld.so.preload文件
检测方式
#2.劫持/etc/ld.so.preload文件
[root@gopherabc ~]# cat /etc/ld.so.preload
/usr/local/lib/libextrasshd.so #被劫持了
清理方式
[root@gopherabc ~]# > /etc/ld.so.preload #清理文件并加锁
[root@gopherabc ~]# chattr +i /etc/ld.so.preload
[root@gopherabc ~]# rm -f /usr/local/lib/libextrasshd.so
3.3、清理动态链接器劫持
准备阶段
3.3.1、安装busybox
BusyBox 是一个适用于嵌入式系统和轻量级 Linux 系统的开源工具集合。它是一个单一的可执行文件, 包含了许多常用的命令行工具, 如 shell、文件操作工具、文本处理工具、网络工具等,覆盖了大部分日常使用的命令和功能。最主要的是它不依赖系统库, 可以完全独立提供常用系统命令。
在后面的排查修复中, 攻击者上传的恶意动态链接库可能会对我们的排查产生干扰, 因此需要一个完全独立的工具来提供日常命令。
1.安装busybox
打开官网https://busybox.net/
有两种安装方式, 源码编译个二进制部署, 这里使用部署二进制文件方式安装
找到二进制文件下载
找到新的x86架构的文件
下载这个封装好的工具集
wget https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox
chmod +x busybox
然后测试下
检测方式
1.确认动态链接器是否被修改
#a.对于系统命令通常是ELF可执行文件格式
[root@gopherabc ~]# file /usr/bin/top
/usr/bin/top: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=f067e2a0ca2c4b7ffcea817da3455b9e8a61570a, stripped
[root@gopherabc ~]#
#b.这类ELF可执行文件使用统一的解释器, 解释器信息包含在其文件头部,使用readelf命令找到它的解释器也就是动态链接器
[root@gopherabc ~]# readelf -a /usr/bin/top | grep interpreter
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
#这是一个软连接,指向/lib64/ld-2.17.so
[root@gopherabc ~]# ll /lib64/ld-linux-x86-64.so.2
lrwxrwxrwx 1 root root 10 Feb 14 20:06 /lib64/ld-linux-x86-64.so.2 -> ld-2.17.so
#c.通过rpm工具校验软件包内容是否改变
[root@gopherabc ~]# rpm -Vf /lib64/ld-2.17.so
..5....T. /lib64/ld-2.17.so
[root@gopherabc ~]#
#这里显示的5表示其软件包md5校验值发送变化, T表示创建时间发送变化
这里需要注意的是 rpm -Vf 校验的是整个文件所属的软件包的md5值, 也就是说这里/lib64/ld-2.17.so属于glibc包, 如果glibc软件包中其它文件内容发生了变化就会影响整个glibc包的校验值, 所以这里的校验结果并不能完全证明动态链接器被修改。
我们可以单独校验其他相同版本服务器的同一版本动态链接器来与这个文件进行对比, 如果校验值不同, 则可证明其被修改。
2.确认动态链接器预加载配置文件被替换到了哪里
使用strace
跟踪程序执行过程中与文件操作相关的系统调用。
[root@gopherabc ~]# strace -e trace=file /usr/bin/top
可以看到动态链接器的预加载配置文件被指向了/tmp/Ki8Ds1D
清理方式
3.清理恶意动态链接库
查看前面找到的动态链接器预加载配置文件/tmp/Ki8Ds1D
, 找到其加载的动态链接库文件, 然后删除它们即可。
如果文件内容为空, 使用busybox查看确认。
4.修复动态链接器
修复方式很简单, 只需要使用相同版本的动态链接器替换即可。
但是需要注意的是所有的ELF可执行文件都依赖这个动态链接器, 如果损坏会导致命令行工具无法使用, 因此我们需要提前准备好busybox
。
#a. 首先上传一个正常的动态链接器, 注意是相同版本操作系统的相同版本
b. 添加执行权限, 然回覆盖源文件即可
务必要先添加执行权限, 不然覆盖后ld-2.17.so无法工作, 会导致软件程序无法使用。这时可以使用busybox 进行修复。
c. 检查软连接并重新校验检查即可。
3.4、找到隐藏进程
清理完被劫持的动态链接库后, 我们就可以正常使用top,ps等工具找到异常进程了。
四、使用busybox找到隐藏进程-方便快捷
看到这里想必你已经发现了, 既然busybox
是一个完全独立的工具合集, 那么它是否也包含top, ps等命令呢? 我们是不是就可以直接利用它找到隐藏进程?
答案: 是的。
还是使用我们下载的源程序执行top命令即可。
[root@gopherabc tmp]# /root/busybox top
busybox是静态编译的,不依赖于系统的动态链接库,
wget https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox
五、入侵处理实例
5.1、找到异常进程
如下图: cpu 0使用率99%, top命令却找不到大量占用cpu的进程, 很明显恶意进程被隐藏了。
而且攻击者控制了资源利用率使其保持在50%并未触发告警。
首先通过调整top刷新频率, 依然找不到恶意进程
然后查看LD_PRELOAD变量无异常。
检查动态链接器预加载配置文件, 可以发现不明来源的动态链接库
我们使用ldd
工具检查下top
命令所依赖的动态链接库
此时可以与其它正常服务器对比下, 也可以发现异常。
5.2、清理异常进程
既然找到了异常进程, 我们只需要清理干净就行了。
对于这种病毒程序通常会有一个工作进程和和一个守护进程, 当工作进程被发现清理后守护进程将重新拉起工作进程。
当我kill了13489
进程后, 果然没过久它又出现了。
这里可以看到它的父进程是systemd
, 看到这里我有了两个推测,
- 这个工作进程是由systemd管理的, 攻击者可能安装了一个新的systemd service
- 它原有的父进程被设计成自动消亡的模式, 好让其工作进程被systemd接管, 从而增加隐蔽性
不管怎么样, 我们接下来都要检查下日志
首先看系统日志
发现有服务启动报错myservice.service entered failed state.
, 我并没有安装这种服务, 它很可能就是那个守护进程了, 我们先看一下的服务配置文件, 至于它为什么会报错, 我猜是因为前面我清理完动态链接库后随手上了锁, 导致它没办法继续注入, 所以就这么报错了。
它主要运行/us/bin/player这个程序,
这里保留下程序样本, 然后清理下。
systemctl reset-failed myservice
然后检查计划任务, 看看有没有留下后门
每个计划任务都要检查。
删除掉这个计划任务,
然后检查ssh的密钥认证, 看看有没有插入登录密钥, 修改ssh密码。
病毒清理至此差不多完成了, 接下来是要找到攻击者的入侵方式, 修补上相关漏洞。不然这种问题还会再出现。
5.3、寻找入侵入口
主要是检查登录记录和安全日志, 看看有没有异常登录IP和破解记录, 如果能够排除ssh登录侵入的话, 剩下的可能就是通过对外开放端口的引用侵入的了, 这些就需要检查应用访问日志, 看看有没有留下侵入痕迹。
我们回到前面的动态链接库
注意这个恶意文件的属主是hugo, 这是我之前测试的一个开源静态博客, 对外开放了访问端口, 但是一直没有用都快忘记它了。由于没有开启访问日志, 这里无法进一步确认, 但是我估计就是它了。
这里关闭hugo, 然后观察看看。
最后我们将保留的源程序上传到病毒分析网站看看。
https://www.virustotal.com/gui/home/upload
https://www.hugbg.com/archives/3774.html
评论