This project tries to minimize the number of steps necessary to trigger the xz backdoor to aid in dynamic program analysis.
In its most basic form you will need to build liblzma.so
which you can do with:
make all
However, the backdoor needs sshd
compiled with libsystemd support, which
requires a patch most distributions don't ship.
To help testing, a script to build sshd
with the libsystemd patch is included:
make sd_sshd
Then just run this sshd
with the helper script:
sudo ./run_sshd
This is simply a wrapper to run sshd
in a clean environment and with
debugging options:
env -i LD_LIBRARY_PATH="$PWD" "$PWD/sd_sshd" -D -d -oListenAddress=127.0.0.1 -p2222
Then you can use xzbot to trigger the backdoor. It will use a custom ed448 key generated with 0 as seed.
make xzbot
./xzbot -cmd 'id > /root/xz'
The backdoor is installed before the program starts, when the linker is
executing checks. It's abusing the
IFUNC functionality in a
resolver function that is supposed to be calling __get_cpuid
to check for cpu
features, but instead the build system modified the code to call _get_cpuid
which is inside the backdoor binary, and does much more.
Since this repository contains a simplified version of liblzma it's clear what they were trying to do in commit f8c8e5a.
The backdoor works because many people made mistakes:
- The complexity of xz trying to build for a bajillion systems with two build systems and few development resources enabled a malicious developer to inject a couple of lines in a script with tens of thousans of lines of code that is generated for tarballs and nobody looks at.
- The developers of systemd did not design it with modularity in mind, so the singular libsystemd library provides a ton of functionality most people won't use and has nothing to do with an init system, like process compressed system logs. Therefore everyone that links to libsystemd links to liblzma as well.
- Maintainers patched
sshd
to link to libsystemd for a minor feature.
The backdoor binary is incredibly complex and it will take a while to properly understand all what it does.
I created this project to simplify the process of analysis.
The injection scripts are very intricate and do numerous checks, such as
ensuring the environment is for packaging as deb or rpm on x86. However, our
primary concern is simply to activate the backdoor, and for that we only need
to call _get_cpuid
from two
ifunc resolvers.
Although I could provide a patch for this purpose and include the backdoor binary in the build process for you to execute manually, this is not necessary. The backdoor doesn't rely on the entire liblzma; it merely uses it as a launchpad.
All it requires are the ifunc resolvers along with lzma_alloc
and
lzma_free
.
Unfortunately creating a mock liblzma triggers errors about unresolved symbols from libsystemd (despite not using them in this context).
Therefore we also craft a mock libsystemd.
The backdoor doesn't interact with libsystemd at all; its sole requirement is
that the sshd
binary is linked to it. As libsystemd is linked to liblzma, the
dynamic loader invokes the ifunc resolvers before sshd
is loaded.
Additionally, it seems that libsystemd must also link to libgcrypt.
Now the backdoor is primed for activation, but at this stage, we've merely
facilitated the creation of an sshd
instance that's readily exploitable by
malicious actors.
We need to replace the attacker's ed448 key with our own. Following the instructions in xzbot's ed448-patch, I created equivalent assembly code for improved readability, and then used binutils' objcopy to integrate it into the correct section in the ELF binary.
We can now locally trigger the backdoor using xzbot with seed=0, but only with
the observed requirements outlined in the initial Openwall
post. The most
inconvenient one is that argv0 must be /usr/sbin/sshd
.
According to another researcher, the place in the code where these requirements
are checked is lzma_file_info_decodea
. By introducing a ret
instruction
there, we can execute our patched sd_sshd
from the current directory and with
debugging enabled.
So:
- Patch
sshd
to use libsystemd - Create a mock libsystemd that links to liblzma and libgcrypt
- Create a minimal mock liblzma containing the backdoor
- Patch the backdoor with a custom ed448 key and bypass checks
Then we can run our sshd
locally and use xzbot to activate the backdoor
easily.