2024年3月29日,Openwall OSS安全邮件列表上的一条消息“炸醒”了整个信息安全、开源和Linux社区:XZ出现了一个CVSS评分10.0的恶意后门。
这个后门库的特殊危险在于OpenSSH服务器进程sshd使用它。在多个基于systemd的发行版上(包括Ubuntu、Debian和RedHat/Fedora Linux),OpenSSH会以一种方式与systemd通信,因此依赖于这个库(注意Arch Linux和Gentoo不受影响)。攻击者的最终目标很可能是为sshd引入其他人无法使用的远程代码执行功能。
不同于在Node.js、PyPI、FDroid和Linux内核中看到的其他供应链攻击,这次事件是一个多阶段的操作,几乎成功地在全球范围内破坏了SSH服务器。
Liblzma(XZ包的一部分)库中的后门分为两个阶段。构建基础架构的源代码被稍微修改了一下(通过引入一个额外的文件build-to-host.m4),以提取隐藏在测试用例文件(bad-3-corrupt_lzma2.xz)中的下一阶段脚本。这些脚本依次从另一个测试用例文件(good-large_compressed.lzma)中提取恶意二进制组件,该文件在编译过程中与合法库链接,并将其发送到Linux存储库。
事件时间轴
2024.01.19:XZ网站被新的维护者(jiaT75)移至GitHub页面;
2024.02.15:“build-to-host. m4”被添加到.gitignore;
2024.02.23:引入了两个包含恶意脚本阶段的“测试文件”;
2024.02.24:XZ 5.6.0版本发布;
2024.02.26:提交入CMakeLists.txt破坏Landlock安全特性;
2024.03.04:后门导致Valgrind出现问题;
2024.03.09:更新两个“测试文件”,修改CRC功能,Valgrind问题被“修复”;
2024.03.09:XZ 5.6.1版本发布;
2024.03.28:漏洞被发现,通知Debian和RedHat;
2024.03.28:Debian回退XZ 5.6.1到5.4.5-0.2版本;
2024.03.29:一封电子邮件在OSS-security邮件列表中发布;
2024.03.29:RedHat宣布发布的Fedora Rawhide和Fedora Linux 40测试版中有植入后门的XZ;
2024.03.30:Debian关闭构建并启动重建进程;
存在后门的发行版
xz-5.6.0
MD5 |
|
SHA1 |
e7bbec6f99b6b06c46420d4b6e5b6daa86948d3b |
SHA256 |
0f5c81f14171b74fcc9777d302304d964e63ffc2d7b634ef023a7249d9b5d875 |
xz-5.6.1
MD5 |
|
SHA1 |
675fd58f48dba5eceaf8bfc259d0ea1aab7ad0a7 |
SHA256 |
2398f4a8e53345325f44bdd9f0cc7401bd9025d736c6d43b372f4dea77bf75b8 |
初始感染分析
XZ git存储库包含一组测试文件,用于测试压缩/解压缩代码以验证其是否正常工作。这个名为“Jia Tan”或“jiaT75”的账户提交了两个初看起来无害的测试文件,但这些文件最终却成为了植入后门程序的引导程序。
两个相关文件分别为:
bad-3-corrupt_lzma2.xz (86fc2c94f8fa3938e3261d0b9eb4836be289f8ae)
good-large_compressed.lzma (50941ad9fd99db6fca5debc3c89b3e899a9527d7)
这些文件旨在包含shell脚本和后门二进制对象本身。但是,它们隐藏在格式不正确的数据中,只有攻击者知道如何在需要时正确地提取它们。
阶段1:修改的build-to-host脚本
当XZ版本准备好后,官方Github存储库会发布项目的源文件。最初,存储库上的这些版本除了包含恶意测试文件之外,是无害的,因为它们没有机会执行。然而,攻击者似乎只在发行版来自https://xz[.]tukaani.org(由Jia Tan控制)时,添加了引导感染的恶意代码。
不幸的是,大多数发行版都使用这个URL。如此一来,在下载时就会附带一个包含恶意代码的“build-to-host.m4”文件。
build-to-host.M4(c86c8f8a69c07fbec8dd650c6604bf0c9876261f)在构建过程中执行,并通过执行下述代码来修复并解压缩添加到测试文件夹的第一个文件。
【build-to-host.m4中去混淆的一行代码】
这行代码使用tr命令替换了bad-3-corrupt_lzma2.xz中的“损坏”数据,并将输出传送到Xz -d命令,以便对数据进行解压缩。解压后的数据包含一个shell脚本,该脚本稍后将通过/bin/bash执行,并由这个.m4文件触发。
阶段2:shell脚本注入
恶意.m4文件注入的恶意脚本会验证它是否正在Linux机器上运行,并且是否正在预期的构建过程中运行。
【注入的脚本内容】
为了执行下一阶段,它使用了good-large_compressed.lzma,该文件确实用XZ正确地压缩了,但在解压缩的数据中包含垃圾数据。
垃圾数据清除过程如下:eval函数执行head pipeline,每个head命令要么忽略下一个1024字节,要么提取下一个2048或724字节。
这些命令总共提取了33492字节(2048*16 + 724字节)。然后,tail命令保留文件的最后31265字节,并忽略其余字节。
之后,tr命令对输出应用一个基本替换来消除混淆。第二个XZ命令将转换后的字节解压缩为原始的lzma流,然后将结果传输到shell中。
第三阶段:后门提取
最后一个阶段的shell脚本会执行许多检查,以确保它在预期的环境中运行,例如是否将项目配置为使用IFUNC(这将在下一节中讨论)。此阶段执行的许多其他检查包括确定GCC是否用于编译,或者项目是否包含稍后脚本将使用的特定文件。
在这个阶段,它会提取后门二进制代码本身——一个对象文件,当前隐藏在相同的good-large_compressed.Lzma文件但不同的offset函数中。
下述代码处理的就是这个问题:
【最后一个脚本阶段使用的部分命令】
提取过程通过一系列命令进行操作,每个命令的结果作为下一个命令的输入。格式化后的一行代码如下所示:
【格式化后的一行代码】
最初,文件good-large_compressed.lzma是使用XZ工具本身提取的。接下来的步骤包括使用“eval $i”函数调用一连串的head calls(与阶段3提取相同)。
然后使用自定义的类RC4算法来解密二进制数据,其中包含另一个压缩文件。这个压缩文件也可以使用XZ实用程序提取。随后,该脚本使用预定义的值从解压缩数据的开头删除一些字节,并将结果保存为liblzma_la-crc64-fast.O(这是链接过程中使用的后门文件)至磁盘。
最后,该脚本修改liblzma中crc_x86_clmull .h文件的is_arch_extension_supported函数,用_get_cpuid替换对__get_cpuid函数的调用(去掉了一个下划线字符)。这个修改允许将它链接到库中(我们将在下一节中更详细地讨论这个问题)。整个构建感染链可以总结为以下方案:
二进制后门分析
隐蔽加载场景
在原始的XZ代码中,有两个特殊的函数用于计算给定数据的CRC:lzma_crc32和lzma_crc64。这两个函数都以IFUNC类型存储在ELF符号表中,这是GNU C库(GLIBC)提供的一个特性。IFUNC允许开发人员动态选择要使用的正确函数。此选择发生在动态链接器加载共享库时。
XZ使用这种方法的原因是,它允许确定是否应该使用lzma_crcX函数的优化版本。优化的版本需要来自现代处理器(CLMUL、SSSE3、SSE4.1)的特殊功能。这些特殊功能需要通过发出cpuid指令来验证,这是使用GLIBC提供的__get_cpuid wrapper/intrinsic来调用的,在这一点上后门利用了加载自身的优势。
该后门程序被存储为一个对象文件,它的主要目标是在编译期间链接到主可执行文件。对象文件包含_get_cpuid符号,因为注入的shell脚本从原始源代码中删除了一个下划线符号,这意味着当代码调用_get_cpuid时,它实际上调用了它的后门版本。
【后门代码入口点】
后门代码分析
初始后门代码被调用了两次,因为lzma_crc32和lzma_crc64都使用相同的修改函数(_get_cpuid)。为了确保对此进行控制,开发人员又创建了一个简单的计数器来验证代码是否已经执行。当lzma_crc64 IFUNC调用_get_cpuid,看到指示函数已被访问的计数器值为1,并启动最后一步重定向到此恶意软件的真正入口点时,实际的恶意活动就开始了。
【后门构造过程】
要构造恶意代码,后门程序首先需要构造两个结构,这些结构包含有关当前运行进程的核心信息。首先,它使用硬编码的偏移量来定位全局偏移表(GOT)地址,并使用该信息来查找其中的cpuid指针。
【GOT修改代码】
GOT包含cpuid wrapper等的偏移量。然后,后门会交换指向主恶意软件函数的指针,并像调用cpuid一样调用它。
核心行为分析
该后门程序的主要目标是成功地钩住(hook)特定的功能,使其能够监视到受感染机器的每个连接。目标函数如下所示:
目标函数 |
描述 |
RSA_public_decrypt |
被libcrypto用于解密由私钥签名的密文 |
EVP_PKEY_set1_RSA |
被libcrypto用于设置给定上下文的RSA密钥 |
RSA_get0_key |
被libcrypto用于检索RSA密钥 |
然而,对于后门程序来说,钩住所有这些函数并不是一项简单直接的任务。它涉及访问许多内部结构并操纵特定于进程的结构,直至达到目标,并且它还需要尽可能地隐蔽,这意味着恶意代码包含多个检查以避免被分析。
执行环境检查
它首先解析ld-linux(动态链接器),其主要负责动态解析内存中的符号地址。它还提取有关环境的各种信息,并验证后门正在运行的当前进程是否符合下述执行标准:
- 当前进程为/usr/bin/sshd;
- 检查终止开关是否存在;
它从argv[0]中提取当前进程的名称,并解析所有进程环境变量,以查看其内部字符串表中是否存在这些变量。如果找到匹配项,或者进程不是sshd,恶意软件就会停止执行。
【流程环境检查】
它自身的表中唯一的变量是yolAbejyiejuvnup=Evjtgvsh5okmkAvj,它在此上下文中充当终止开关。
trie结构
该后门的一个显著特征是使用单一的trie结构进行字符串操作。代码没有直接比较字符串或使用字符串散列来匹配特定的常量(例如,库函数的名称),而是执行一个trie查找,并检查结果是否等于某个常量。例如,ELF文件头的magic value导致trie返回0x300,而系统函数的名称与返回值0x9F8匹配。Trie不仅用于比较:某些使用指向字符串的指针的函数(例如,ssh-2.0)也使用Trie在主机二进制文件中搜索这些字符串,因此在后门的主体中不会有可疑的数据。
trie的实现使用16字节的位掩码,每一半对应于字节输入范围0x00-0x3F和0x40-0x7F,以及2字节的trie叶子节点(leaf node),其中3 bits是标志(方向,终止),其余的留作值(或下一个节点的位置)。
【执行位图(bitmap)匹配的trie查找函数的一部分】
符号解析器
至少有三个与符号解析器相关的例程被该后门用于定位ELF符号结构,后者保存着诸如符号名称及其偏移量之类的信息。所有符号解析器函数都接收要在trie中搜索的密钥。
【符号解析器示例】
其中一个后门解析器函数遍历所有符号并验证哪个符号具有所需的密钥。如果找到了,它将返回Elf64_Sym结构体,该结构体稍后将用于填充保存所有必要函数指针的后门的内部结构。这个过程类似于在带有API散列例程的Windows威胁中常见的过程。
该后门从libcrypto(OpenSSL)库中搜索许多函数,并将其用于后续加密例程。它还会跟踪它能够找到和解决多少函数;这决定了它是正确执行还是应该停止。
另一个有趣的符号解析器滥用lzma_alloc函数,该函数是liblzma库本身的一部分。这个函数可以帮助开发人员使用默认分配器(malloc)或自定义分配器有效地分配内存。在XZ后门的情况下,这个函数被滥用来利用一个假的分配器。实际上,它是另一个符号解析器。用于“分配大小”的参数实际上是trie中的符号键。这个技巧是为了使后门分析复杂化。
【使用假分配器结构的符号解析器】
该后门程序会在执行时动态解析其符号;它不一定要一次完成这些操作,或者只在需要使用它们时才这样做。解析的符号/函数范围从合法的OpenSSL函数到用于在机器上执行命令的system等函数。
Symbind钩子
如前所述,后门初始化的主要目标是成功地挂钩函数。为此,该后门程序使用了rtdl-audit,这是动态链接器的一个特性,它允许在链接器中发生某些事件(如符号解析)时通知自定义共享库的创建。在一个典型的场景中,开发人员将按照rddl -audit手册创建一个共享库。但是,XZ后门选择对加载在内存中的已注册(默认)接口执行运行时补丁,从而劫持符号解析例程。
【Dl-audit运行时补丁】
该恶意构造的结构audit_iface存储在动态链接器内存区域内的dl_audit全局变量中,它包含由动态链接器调用的symbind64回调地址。它将所有符号信息发送给后门控件,后门控件随后会被用于获取目标函数的恶意地址,从而实现hook。
【已修改的Symbind回调中的钩子位置】
通过反汇编dl_main和dl_audit_symbind_alt函数,可以获得dl_audit和dl_naudit的地址(其中保存了可用的审计接口数量)。该后门还包含一个用于指令解码的内部极简反汇编器。它广泛使用它,特别是在寻找特定值(如 *audit地址)时。
【Dl_naudit搜索代码】
dl_naudit地址由访问它的dl_main函数代码中的mov指令之一找到。有了这些信息,后门程序就会寻找对内存地址的访问并保存它。
它还会验证获取的内存地址是否与dl_audit_symbind_alt函数在给定偏移量中访问的地址相同。这允许它安全地假设它确实找到了正确的地址。在找到dl_naudit地址之后,它可以很轻松地计算出dl_audit在哪里,因为这两个地址在内存中是紧挨着存储的。
结语
在本文中,我们介绍了libzma(XZ)后门的整个过程。很明显,这个后门非常复杂,采用了复杂的方法来逃避检测。这包括XZ存储库中的多阶段植入,以及二进制文件本身中包含的复杂代码。
后期,我们将发布更多关于该后门的内部探索,敬请期待。
原文链接:
https://securelist.com/xz-backdoor-story-part-1/112354/
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)