“本文不揣测 xz 后门背后的团伙与动机,只为学习目的,以便吸取教训。
2024 年 3 月 29 日,Openwall OSS-security 邮件列表上的一条信息对于信息安全、开源和Linux社区来说标志着一个重要的发现:在 XZ 中发现了一个恶意后门。XZ 是集成在许多流行的 Linux 发行版中的压缩实用工具。
“XZ Utils 几乎在 Linux 上无处不在。它在几乎所有类 Unix 操作系统上提供无损数据压缩,包括 Linux。XZ Utils 在各种操作中提供了压缩和解压数据的关键功能。XZ Utils 还支持传统的
.lzma
格式。.lzma
格式因其高压缩率和可选的高压缩设置而受到青睐,尤其适用于大文件的压缩。.lzma
常用于操作系统镜像、大型游戏或应用程序的安装包、数据备份等场景。
下面是来自卡巴斯基[1]关于 xz 后门事件的时间线。
正所谓:
黑客潜藏计中计,暗渡陈仓码未宁。
人算终难如天算,竹篮打水空归行。
技艺虽能迷目眼,漏洞反被 Bug 误。
尘世安有尽全策,百密一疏漏自明。
这个后门与常规的供应链投毒完全不同,如果它成功实施攻击,那么全球范围内的 SSH 服务器都将遭到控制。攻击者可以通过该后门将任意代码注入到最终进入敏感进程地址空间的库中。
但,
““攻击非常隐秘,只有在使用amd64(英特尔x86 64位)构建库并构建Debian或RPM软件包(而不是用于本地安装)时,才会执行后门的最后步骤。”
为什么 xz 后门如此有针对性? (此处为 ChatGPT 的分析)
xz 后门展现出如此高度的针对性,主要是为了确保其行动的隐蔽性和效率。这种针对特定平台和构建环境的设计允许攻击者更精确地控制后门的激活,减少被发现的可能性,并提高攻击的成功率。以下是一些可能的原因和目的分析:
这样做的危害如下:
看得出来,xz 后门如果成功以后,绝对算得上是「核弹级」后门了。XZ Utils 的后门被分配漏洞编号是 CVE-2024-3094[13],并具有CVSS最高的严重性评分 10 分。
从互联网上披露的各种关于 xz 后门的资料总结,xz 后门的实施包含四个关键阶段:
.so
共享文件:攻击者通过修改动态链接库(如 liblzma.so
),在这些广泛使用的库中植入后门。这使得所有依赖这些库的应用程序都可能在运行时无意中执行恶意代码。看得出来,这是一次经过精心策划的供应链攻击。涉及从项目参与、代码修改、编译过程控制到最终的后门植入与利用。这种攻击不仅要求攻击者具备深厚的技术知识和对目标项目的深入理解,还涉及社会工程学,使其能在不被发现的情况下持续操作较长时间。
这是攻击的第一步,也是最重要的一步。
一句话总结信任建立的基础:原维护者疲惫不堪,只有攻击者提供帮助。
网上有人找到了一个包含攻击者 Jia Tan 和 xz 作者,以及一些用户交流的电子邮件线程的存档[14] ,记录了这一切的开始。
“
看完这份摘要,我感觉这像是一场隐藏在 xz 邮件列表中对作者的一场"网暴"。
我有理由怀疑,JiaTan 不是一个人在战斗。要实施这场攻击,并想提升 xz 作者对他的信任的概率,那应该有一些“团伙”。JiaTan 唱红脸,团伙唱白脸,这种左右夹攻下让 xz 作者交出了项目的维护权。
就这样 JiaTan 的第一步攻击完成,开始了他长达一年的潜伏。
“2023 年 1 月, JiaTan 首次向 XZ Utils 提交了代码。在接下来的几个月里,使用 Jia Tan 这个名字越来越多地参与了 XZ Utils 的事务。例如,Tan 在 oss-fuzz 上用自己的联系信息替换了 Collins (xz 作者)的信息,oss-fuzz 是一个扫描开源软件漏洞的项目。Tan 还要求 oss-fuzz 在测试期间禁用 ifunc 函数,这个改变阻止了它检测到 Tan 不久后对 XZ Utils 进行的恶意修改。
今年 2 月,Tan 发布了 XZ Utils 5.6.0 和 5.6.1 版本的提交。这些更新中实施了后门。
这个后门分为下面几个组件:
build-to-host.m4
版本与 GitHub 上游的版本差异很大。tests/
文件夹中有一些精心制作的测试文件(tests/files/bad-3-corrupt_lzma2.xz
和 tests/files/good-large_compressed.lzma
)。(这些文件包含 shell 脚本和后门二进制对象自身)。build-to-host.m4
调用的脚本,解压这个恶意测试数据并使用它来修改构建过程。只要满足了特定的条件,比如 amd64/x86_64
架构上构建 Debian
或 Red Hat
软件包时,就能激活攻击 Payload 。目前安全研究人员并不确定这个 payload 的目的是什么,还在调查中。
“xz 的作者 Lasse Collin 正在对 xz.git 进行审计[15]。(Collin 有定期的「离线隐居」的习惯,在 xz 后门被发现那几天,他正在隐居)
payload 被间接加载到 sshd
中。
任何库都可以篡改与其链接的任何可执行文件的内部工作方式。OpenSSH 是最流行的 sshd 实现,它不链接 liblzma
库,但 Debian 和许多其他 Linux 发行版会添加一个补丁,将 sshd
链接到 systemd
,这是一个在系统启动期间加载各种服务的程序。而 systemd
又链接到 liblzma
,这使得 XZ Utils 可以对 sshd
施加控制。
“最新:OpenSSH 开发人员已经添加了systemd-notify 协议的非库集成[16],因此发行版将不再通过
libsystemd
支持进行修补。这个改变已经提交,并将在OpenSSH-9.8中实现,预计在2024年6月/7月发布。
如果此 payload 被成功加载到 openssh sshd
中, RSA_public_decrypt
函数将被重定向到恶意实现。攻击者必须提供一个由 pyaload 验证的密钥,然后将攻击者的输入传递给 system()
,从而实现远程代码执行(RCE)。
在现代软件开发中,尤其是在 Unix-like 系统中,构建和编译过程通常涉及多种工具和脚本,以实现软件的自动化构建和配置。xz
作为一个流行的压缩工具,其编译过程也使用了这些常见的工具,包括 Bash 脚本和 m4 宏。
autoconf
工具一起使用,来生成可移植的shell脚本(configure
脚本),这些脚本随后用于生成适合特定系统的Makefile。m4 宏是一种广泛使用的宏处理语言,其功能包括定义宏、展开宏、条件测试、包含文件等。m4 特别适用于生成文本文件和自动化脚本,因此它被广泛用于软件开发中,尤其是与autoconf
一起用于生成配置脚本。这些配置脚本可以检测主机环境的各种特性,如操作系统类型、编译器选项、外部库等,以确保软件能在特定环境下正确编译和运行。
在xz
的编译过程中,autoconf
和 m4 宏用于生成configure
脚本。这个脚本是一个 shell 脚本,它在编译前运行,用于检查系统特性和配置参数。一旦configure
脚本运行完毕,它会生成适合当前系统的 Makefile,然后开发者可以运行make
命令来编译和链接最终的程序。整个过程中,Bash 脚本可能用于自动化执行这些命令,处理编译后的任务,或进行清理工作。
xz 后门通过在 Bash 脚本中使用多阶段的混淆和加密技术来隐藏其行为。在 安全工程师 Gynvael Coldwind [17] 的文章中对这几个阶段进行了分析(细节可以查看原文)。
阶段 0 : 修改脚本
m4/build-to-host.m4
文件中的代码。这段代码在构建过程中被执行。sed
和tr
:这两个命令被用来处理和修改从测试文件中读取的数据。sed
用于流编辑,而tr
用于字符转换。tr "A-Z" "N-ZA-M"
这是一个简单的字符替换例子,其中将所有英文字母替换为其对应的字母(ROT13加密)。在这个阶段,混淆主要通过字符映射(使用tr
命令)来实现,目的是将损坏的数据“修复”为可执行的代码。即,这实际上“修复”了测试数据 bad-3-corrupt_lzma2.xz
,并将输出导入到 xz -d
命令中,该命令用于解压数据。解压后的数据包含一个 shell 脚本,之后将使用 /bin/bash
执行,由该 .m4
文件触发。
阶段 1 :注入 shell
head
命令来选择性地读取或忽略数据流中的特定部分。这些命令提取了 33,492
字节(2048 * 16 + 724
字节)。然后,tail 命令保留文件的最后 31,265 字节,并忽略其余部分。这一阶段的关键在于对数据进行精确的处理,以提取隐藏在数据中的第二阶段脚本。之后,tr 命令对输出进行基本替换以解密。第二个 XZ 命令将转换后的字节作为原始 lzma 流进行解压缩,然后将结果传送到 shell。
阶段 2 :提取后门
最后一个阶段的 shell 脚本执行许多检查,以确保它在预期的环境中运行,例如项目是否配置为使用 IFUNC(将在下一节中讨论)。
.o
文件( 隐藏于 good-large_compressed.lzma
中)被提取出来并将其编织到后续的编译/链接过程中。第二阶段整个过程基本上看起来像是一个“扩展/修补”系统,允许添加将在第二阶段上下文中运行的未来脚本,而无需修改原始的携带测试文件的有效负载。这应该是精心设计的,因为一遍又一遍地修改“坏”和“好”的测试文件非常可疑。因此,计划似乎是只添加新的测试文件,这些文件将被捕获、解密和执行。
“还可能有后面的阶段,但是 Gynvael 认为后面的阶段并没有实施完成。
总的来说,攻击者为了使其看起来相当无害和相当隐蔽而付出了很多努力。从用于存储 payload 的二进制测试文件,到文件切割、替代密码和在 AWK 中实现的 RC4 变体,所有这些都只使用了标准命令行工具。而且所有这些都在三个执行阶段完成,并且还有一个“扩展”系统,以免再次更改二进制测试文件。
如果这个后门是被偶然发现的,还有多少事情仍然未被发现?
.so
共享文件“卡巴斯基[18]对此有详细的总结,另外 binarly-io/binary-risk-intelligence[19] 对
liblzma.so
也有更详细的分析。
在原始的 XZ 代码中,有两个特殊的函数用于计算给定数据的 CRC:lzma_crc32
和 lzma_crc64
。这两个函数都以 IFUNC
类型存储在 ELF
符号表中,这是 GNU C
库(GLIBC)提供的一个特性。IFUNC
允许开发人员在动态链接器加载共享库时动态选择正确的函数使用。
XZ 使用这个的原因是它允许确定是否应该使用lzma_crcX
函数的优化版本。优化版本需要现代处理器的特殊功能(CLMUL、SSSE3、SSE4.1)。这些特殊功能需要通过发出 cpuid
指令来验证,该指令使用 GLIBC 提供的 __get_cpuid
包装器/内嵌函数调用,而后门利用这一点来加载自身。
后门被存储为一个目标文件,其主要目标是在编译过程中与主可执行文件链接。目标文件包含 _get_cpuid
符号,因为注入的 shell 脚本从原始源代码中删除了一个下划线符号,这意味着当代码调用 _get_cpuid
时,实际上调用的是后门的版本。
初始后门代码被调用两次,因为 lzma_crc32
和 lzma_crc64
都使用了相同的修改函数(_get_cpuid
)。为了确保对此的控制,创建了一个简单的计数器来验证代码是否已经执行过。当 lzma_crc64
IFUNC 调用_get_cpuid
时,看到计数器值为 1,表示该函数已经被访问过,并启动最后一步将重定向到此恶意软件的真正入口点。
为了初始化恶意代码,后门首先初始化了一些保存当前运行进程核心信息的结构。主要是使用硬编码的偏移量定位全局偏移表(GOT)地址,并利用这些信息在其中找到 cpuid 指针。
GOT 包含符号的偏移量,包括 cpuid 包装器。然后,后门交换指向主要恶意软件函数的指针,并将其调用,就像调用 cpuid 一样。
后门的主要目标是成功挂钩特定函数,以允许其监视对受感染机器的每个连接。目标函数包括:
目标函数 | 描述 |
RSA_public_decrypt | 由 libcrypto 使用,用于解密由私钥签名的密文 |
EVP_PKEY_set1_RSA | 由libcrypto用于设置给定上下文的RSA密钥 |
RSA_get0_key | 由 libcrypto 用于检索 RSA 密钥 |
然而,对于后门来说,挂钩所有这些函数并不是一项简单直接的任务。它涉及访问许多内部结构并操纵进程特定的结构,直到达到其目标,并且还需要尽可能地隐蔽,这意味着恶意代码包含多个检查以避免被分析,比如检查是不是 sshd 进程,否则就停止执行等。
另外还使用了特别的字典树来进行字符串操作等技巧来让后门分析变得更加复杂,有利于隐藏自身。xz 后门还包含一个内部的简化反汇编器,用于指令解码。它在寻找特定值(如 *audit
地址)时广泛使用。
看得出来,构建和感染该后门的手段设计的非常复杂。
那如此精心设计的 xz 后门是如何被发现的呢?完全始于一次偶然。
一位名为 Andres Freund[20] 的 Linux 数据库 PostgreSQL 的“维护者”在 Openwall 的邮件中首次披露:xz/liblzma
中的后门导致 ssh 服务器受损。
Andres 在 Debian sid 安装中观察到 liblzma
(xz 包的一部分)出现了一些奇怪的症状,比如使用 ssh 登录会占用大量 CPU,以及 PostgreSQL 用户发现一个Valgrind 错误。经过一番调查,他发现了 xz 的后门。
这个后门占用 CPU 的原因可能如下:
Valgrind 错误可能的原因:无效的内存写入。valgrind 错误通过禁用 ifunc 被修复了,因此也禁用了后门,因此错误消失了。
“我对 C 的质量非常满意,但对 M4 不太满意。在供应链攻击方面,Cargo 是我见过的最糟糕的” —— 某位 hackernews 用户。
他是在说反话吗? 我认为是的,和 C 比应该不是最糟糕的 。
这次 xz 后门攻击的主要技术手段是在于构建系统。Cargo 是否能够防止这种攻击 ?不一定,因为 Cargo 支持 build.rs
构建脚本,可以干任何事情。所以像在 Google Android / 华为鸿蒙 等操作系统里引入 Rust 都已禁用了 build.rs
来保障安全。 Rust 的过程宏也是帮助构建后门的一种手段,同样可以做任意事情。
然而,Rust 官方在供应链安全上也在发力,进一步增强 Security 安全保障。关于 Rust 供应链安全机制我在之前的公众号文章《一篇文章带你全面了解 Rust 与 安全》里有介绍。
但是对于 xz 后门这种综合了各种手段,甚至社会工程学,并且面向二进制安全的攻击,无论 Rust 再怎么安全,也早已脱离其安全保障层面了。并且 xz 后门是针对底层操作系统诸如 Linux/Unix 的供应链攻击,Rust 开发者再怎么审查依赖的 crate 也不可能识别出这种后门。
虽然 Rust 语言对防范 xz 这类直接修改 .so
二进制文件的后门攻击有限,但 Rust 语言特性至少也能提升一下攻击者的门槛:
unsafe
代码块使用裸指针和直接内存操作,但它需要操作 C-ABI 时应该明确标记这些块为不安全,从而促使开发者在使用时更加谨慎,并进行更多的代码审查。xz 后门背后所用的技术表明,攻击者肯定是一个黑帽经验非常丰富的人或团伙。多亏了 Andres 这位英雄,才阻止了这次“核弹级”后门攻击。
可能有 75% 的人习惯使用curl > bash
来安装最新和最好的开发工具。之前有 curl 曝出 0day (2023年10月),现在又遇到 xz 后门 bash 混淆技术提取后门 。我不禁感慨,这个世界真是太危险了。
我们该如何应对这类「核弹级」攻击?
xz
问题本质上是Linux和BSD发行商面临的一个运营问题,而不是开发问题。“一位 FFmpeg 开源媒体软件包的开发者在推特上强调了这个问题,他说:“xz 事件显示了对无偿志愿者的依赖可能会导致重大问题。万亿美元的公司期望从志愿者那里获得免费和紧急的支持”,他们还提供了证据,指出他们如何处理影响微软 Teams 的“高优先级”错误。
虽然技术的黑暗面也有充满了智慧与技巧的一面,但我们不应该拿它来做恶。感谢阅读。
参考资料
[1]
卡巴斯基: https://securelist.com/xz-backdoor-story-part-1/112354/
[2]
jiaT75: https://github.com/JiaT75
[3]
添加: https://git.tukaani.org/?p=xz.git;a=commitdiff;h=4323bc3e0c1e1d2037d5e670a3bf6633e8a3031e
[4]
引入: https://git.tukaani.org/?p=xz.git;a=commit;h=cf44e4b7f5dfdbf8c78aef377c10f71e274f63c0
[5]
commit: https://git.tukaani.org/?p=xz.git;a=commitdiff;h=328c52da8a2bbb81307644efdb58db2c422d9ba7
[6]
Landlock: https://man7.org/linux/man-pages/man7/landlock.7.html
[7]
问题: https://bugzilla.redhat.com/show_bug.cgi?id=2267598
[8]
rolls back: https://tracker.debian.org/news/1515519/accepted-xz-utils-561really545-1-source-into-unstable/
[9]
发布: https://www.openwall.com/lists/oss-security/2024/03/29/4
[10]
发布: https://www.redhat.com/en/blog/urgent-security-alert-fedora-41-and-rawhide-users
[11]
关闭: https://fulda.social/@Ganneff/112184975950858403
[12]
承认: https://tukaani.org/xz-backdoor/
[13]
CVE-2024-3094: https://nvd.nist.gov/vuln/detail/CVE-2024-3094
[14]
包含攻击者 Jia Tan 和 xz 作者,以及一些用户交流的电子邮件线程的存档: https://www.mail-archive.com/xz-devel@tukaani.org/msg00562.html
[15]
xz.git 进行审计: https://tukaani.org/xz-backdoor/
[16]
非库集成: https://bugzilla.mindrot.org/show_bug.cgi?id=2641#c33
[17]
安全工程师 Gynvael Coldwind : https://gynvael.coldwind.pl/?lang=en&id=782
[18]
卡巴斯基: https://securelist.com/xz-backdoor-story-part-1/112354/
[19]
binarly-io/binary-risk-intelligence: https://github.com/binarly-io/binary-risk-intelligence/blob/master/xz-backdoor/readme.md
[20]
Andres Freund: https://www.openwall.com/lists/oss-security/2024/03/29/4
[21]
使用重定位只读 (RELRO) 来加固 ELF 二进制文件: https://www.redhat.com/en/blog/hardening-elf-binaries-using-relocation-read-only-relro
[22]
我们从 xz 后门事件能获得什么教训: https://www.reddit.com/r/linux/comments/1c29ptf/what_we_need_to_take_away_from_the_xz_backdoor/