XZ是一个使用LZMA压缩算法的无损数据压缩文件格式,XZ-Utils是Linux、Unix等系统中用于处理.xz文件的套件,包含liblzma、xz等组件。XZ工具已集成在debian、ubuntu、centos等多个发行版中,用来处理xz格式的压缩数据。近日有安全研究人员发现在xz软件包中发现了涉及恶意代码混淆的供应链攻击,影响版本为5.6.0、5.6.1,该软件包被植入恶意代码,由于SSH服务使用了liblzma库,因此该代码会影响SSH Server进程,劫持SSH认证函数,攻击者通过后门版本可以在未授权的情况下获取系统的控制权。
据未然实验室研究团队追踪,攻击者于2021年注册GitHub账号,并积极参与xz项目的维护。逐渐获取committer的信任后,攻击者最终获得了直接commit代码的权限。在最近的代码commit中,攻击者植入了两份测试用的二进制数据,并在软件编译脚本中设定条件,以修改编译结果,导致与公开源代码不一致。
具体时间如图所示
初步研究结果显示,XZ库的编译脚本会在特定条件下从bad-3-corrput_lzma2.xz、good-large_compressed.lzma文件中读取恶意payload对编译结果进行修改,其利用glibc的IFUNC特性劫持系统OpenSSH 服务的RSA_public_decrypt函数,使得认证过程中当RSA公钥n满足一定条件时触发恶意代码,解密流量里的C2命令并执行,需要特定私钥才能触发,但由于私钥未公开,目前无法获取后门利用的流量。该后门漏洞已被CVE漏洞库收录,编号为:CVE-2024-3094。
xz == 5.6.0
xz == 5.6.1
liblzma== 5.6.0
liblzma== 5.6.1
目前使用了受影响版本XZ的操作系统包括openSUSE、Fedora 41、Debian等非稳定的测试版 5.5.1alpha-0.1 到 5.6.1-1,具体受影响的系统参见:
https://repology.org/project/xz/versions
通过版本判断
运行 xz --version,如果是5.6.0或者5.6.1,那么就可能包含后门。
通过脚本判断
通过脚本判断就比较准确,因为如果恶意.o文件被链接到lzma.so中,那么就一定会包含恶意代码的字节流。
#! /bin/bash
set -eu
# find path to liblzma used by sshd
path="$(ldd $(which sshd) | grep liblzma | grep -o '/[^ ]*')"
# does it even exist?
if [ "$path" == "" ]
then
echo probably not vulnerable
exit
fi
# check for function signature
if hexdump -ve '1/1 "%.2x"' "$path" | grep -q f30f1efa554889f54c89ce5389fb81e7000000804883ec28488954241848894c2410
then
echo probably vulnerable
else
echo probably not vulnerable
fi
降级到5.6.0以下版本或升级到官方最新版5.6.4。
攻击者整个后门的编译植入流程如下,主要分为三个阶段:
恶意构建过程从m4/build-to-host.m4脚本开始。
脚本通过字符串替换恢复bad-3-corrupt_lzma2.xz为格式合法的文件,然后使用xz进行解压。替换规则为:
t 替换为 0x20
0x20替换为0x09
-替换为0x5f
_替换为0x2d
bad-3-corrupt_lzma2.xz解压之后如下。
这段代码的目的是从good-large_compressed.lzma中利用head命令提取其中的部分内容,然后使用tr进行字符替换,最后使用xz进行解压,解压之后的结果输出给sh执行。
解压之后的代码进行了很多检查,检查之后的一段代码行为十分可疑,它把good-large_compressed.lzma进行一系列的字符替换、解密、xz解压之后写入到了liblzma_la-crc64_fast.o中,这个文件就是最终的后门文件。然后用这个后门文件替换了源码编译的liblzma_la-crc64-fast.o文件。这两个文件名仅有一个下划线和减号的区别,可以看到开发者十分的谨慎。
笔者从debian仓库下载xz源码编译,并且从github镜像仓库下载编译之后。使用nm查看liblzma_la-crc64_fast.o的符号信息。上面的是正常的.o文件的符号,仅有几个函数,下面的是后门.o文件,可以包含了很多的符号。
从文件大小上来后门里面的代码量也很多。
xz的后门是在lzma_crc64的IFUNC函数中实现的。IFUNC函数类似于用户自定义的解析函数。
对比源码和后门对象文件。源码中仅调用__get_cpuid来判断,然后根据不同的cpu特征返回不同的处理函数。
但是在后门文件中,还调用了sub_4C90这个函数。
sub_4090函数第一次运行的时候直接调用cpuid,然后返回,但是第二次的时候就会调用sub_4D04函数。
sub_4D04函数通过传递的a2参数(其实是最开始的栈上的某个地址),然后通过计算得到旧的rbp的地址。然后通过一堆无意义的offset加减得到v5的值,即0x3cff0。然后把0x3cff0赋值给v5。可以看到v5是cpuid在GOT表中的地址。后面的给v5指针指向的地址赋的值通过计算为0x223f0,这里是真正的后门执行函数。上面说的这一堆操作目的就是为了把cpuid的GOT表改成0x223f0函数,劫持cpuid的流程到恶意代码。
深入分析0x223f0函数,可以看到恶意代码的作者还放了很多看似正常lzma逻辑代码调用,用来迷惑开发者和逆向分析员。后面的主要逻辑在sub_22f50中,代码非常长,而且很乱。
下面我们直接看被劫持的函数的逻辑。根据openwall的分析,恶意代码更改了sshd的RSA_public_decrypt的GOT表地址,笔者直接通过gdb动态调试查看改之后的地址。
查看对应的函数,发现调用了sub_17390函数,恶意代码主要在这个函数里面。其中第二个参数3D020是比较重要的一个全局指针变量,恶意代码的所需要的libclibcrypt的函数指针,以及一些全局的数据都存放在这个全局指针里面。
sub_17390函数首先从RSA公钥中取n,n其实就是payload。然后进行一系列的校验,包含长度等等。
然后对n进行解密。这里的n前16个字节为iv,后面的为加密数据。这里的iv其实还作为opcode,后面使用。计算方式为opcode = ((QWORD*)iv)[1] + ((DWORD*)iv)[0] * ((DWORD*)iv)[0]。
其中的解密的密钥是动态计算出来的,其实是固定的。将全为0的key和全为0的iv作为参数,使用chacha20解密,解密后的密文作为临时密钥K1。使用K1再次对a2->field_108解密,解密之后获得的密钥K2。之后使用K2对真正的payload进行解密。
a2->field_108的值如下。
接着对消息进行签名校验,密钥依然使用之前的动态计算方式。
check_sig函数里面首先会把后门里面的一个rsa公钥进行sha256计算,返回添加到解密之后的真正的payload后面。
然后使用ED448签名算法进行签名校验,这里的公钥和前面动态计算出来的对称密钥K2是一致的。这里我们没有私钥,所以不能伪造合法的签名。
从这里可以看出消息结构大概如下。
后面一堆用来混淆的代码,检查opcode,field_0,field_1。但是很多if条件不可能满足。例如opcode必须等于2,否则函数一开始就直接返回了。恶意代码最后的核心目的如下,首先调用setresgid, setresuid,然后调用system执行任意代码,其中的参数就来自于消息中的body字段。
github已有公开的demo exp https://github.com/amlweems/xzbot,这个exp通过patch后门中的公钥为自己的私钥来验证漏洞的存在。
笔者下载了debian官方编译的deb包。安装之后使用patch.py脚本手动patch。
然后启动sshd进程,使其监听在2222端口。
之后运行exploit,执行的命令为id > /tmp/.xz。
运行之后可以看到命令成功被执行,并且命令执行的权限为root。
总之,这是一个非常典型的供应链攻击,攻击者潜伏多年,通过多次提交维护代码,以避免被发现,最终取得社区信任。但是,由于本次事件被及时发现,所以并没有影响到生产环境中的 GNU/Linux 系统,只影响到部分测试版本,但如果后门未被及时发现,后果将是灾难性的。
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...