XZ Utils: Deconstructing the IFUNC Backdoor

XZ Utils: Deconstructing the IFUNC Backdoor

Overview & Threat Landscape#

On March 29, 2024, the open-source security landscape experienced a watershed moment when PostgreSQL developer Andres Freund published an advisory to the Openwall security list detailing CVE-2024-3094—a meticulously orchestrated supply chain backdoor embedded directly into upstream XZ Utils (liblzma) versions 5.6.0 and 5.6.1. Assigned the maximum CVSS score of 10.0 and classified under CWE-506 (Embedded Malicious Code), the vulnerability represents one of the most sophisticated, multi-year advanced persistent threat (APT) operations ever uncovered in the open-source ecosystem.

The threat actor, operating under the pseudonym "Jia Tan" (JiaT75), spent over two years cultivating trust within the open-source community. Beginning in 2021, the persona contributed legitimate code, fixed edge-case bugs, and leveraged coordinated sockpuppet pressure on the primary maintainer, Lasse Collin, to eventually secure co-maintainer privileges and release-management authority over the core repository.

What elevates CVE-2024-3094 beyond conventional vulnerabilities is its revolutionary attack calculus:

  • Separation of Repository and Tarball Logic: The malicious payload was never committed in plain sight to the official Git repository. Instead, raw encrypted test files (bad-3-corrupt_lzma2.xz and good-large_compressed.lzma) were stored in the test suite as ostensibly benign compression fixtures. The de-obfuscation and injection scripts were injected exclusively into official release tarballs via modified Autotools scripts (m4/build-to-host.m4), exploiting the reality that security auditing predominantly focuses on Git commit histories rather than packaged distribution tarballs.
  • Exploitation of Transitive Linkage in OpenSSH: The ultimate objective of the backdoor was the OpenSSH daemon (sshd). While upstream OpenSSH from OpenBSD does not link against liblzma, major enterprise Linux distributions—including Debian, Ubuntu, Fedora, and openSUSE—patched sshd to link against libsystemd.so to support systemd status notifications (sd_notify). Because libsystemd linked against liblzma.so for compressed journal and core-dump support, liblzma was pulled directly into the memory address space of the privileged root sshd listener on TCP port 22.
  • Abuse of GNU Indirect Functions (IFUNC): Rather than patching application code directly, the backdoor hijacked the dynamic linking mechanism of glibc. It abused IFUNC resolvers—which execute early during program initialization to optimize CPU instruction selection—to rewrite the Global Offset Table (GOT) of sshd before runtime memory protections (GNU_RELRO) were finalized.
  • Asymmetric Cryptographic Activation: The backdoor did not open a reverse shell, create unauthorized users, or establish a secondary listening port. Instead, it hooked OpenSSL's RSA_public_decrypt function inside sshd. When an attacker presented a specially crafted SSH authentication certificate signed with their private key, the backdoor decrypted the payload, verified the signature using an embedded public key, and passed commands directly to system(). Unauthenticated probes or scanning from security researchers simply failed validation and fell through cleanly to standard OpenSSH authentication, remaining entirely undetectable.

[!WARNING] Had this backdoor propagated into stable enterprise Linux releases (such as Debian 13, RHEL 10, or Ubuntu 24.04 LTS), threat actors would have possessed an unauthenticated, cryptographically signed remote code execution capability against virtually every internet-facing Linux server globally.


Vulnerability & Attack Root-Cause Analysis#

To deconstruct the backdoor, we must examine the intersection of build-time script injection, dynamic linker mechanics in glibc, and runtime memory hooking.

Build-Time Injection Pipeline#

The build-time delivery mechanism executed during the standard ./configure && make workflow of downstream distribution package builds. The macro m4/build-to-host.m4 contained an intentional obfuscation routine that inspected the build environment.

The compilation gate checked for specific targets:

  1. Target architecture must be x86_64.
  2. Operating system must be Linux running the GNU C library (glibc).
  3. Compiler must be GCC using the GNU linker (ld).
  4. Build must be packaged into an RPM or DEB archive (preventing local developer builds from triggering the payload).

The injection script extracted slices of bytes from test/files/bad-3-corrupt_lzma2.xz using sed and tr substitutions to reconstruct a multi-stage shell script. This script subsequently carved an object file (liblzma_la-crc64_fast.o) from test/files/good-large_compressed.lzma and modified the Makefile to link it into the final liblzma.so shared library.

BASH
# Excerpt of the de-obfuscation pipeline extracted from m4/build-to-host.m4
gl_cv_posix_shell="${srcdir}/tests/files/bad-3-corrupt_lzma2.xz"
eval $(grep -a -F "### evil code start ###" "$gl_cv_posix_shell" | tr "	 -_" " 	_-")

## Carving the compiled backdoor object file
sed -r '/^#/d' tests/files/good-large_compressed.lzma | \
    xz -F raw --lzma1 -cd | \
    eval $extract_cmd > liblzma_la-crc64_fast.o

GNU Indirect Functions (IFUNC) Hijacking#

GNU Indirect Functions (IFUNC) are a compiler and dynamic linker feature in ELF binaries. When an application calls a function optimized for specific CPU extensions (e.g., CRC-32 or CRC-64 algorithms using AVX2 or SSE4.2), the dynamic linker (ld.so) invokes an IFUNC resolver function at load time. The resolver queries CPU capabilities via cpuid and returns the function pointer of the optimal implementation.

In liblzma, crc32_resolve() and crc64_resolve() were defined as IFUNC resolvers. The injected malicious code replaced these resolvers to seize control of process execution:

C
// Conceptual representation of the legitimate IFUNC resolver in liblzma
typedef uint64_t (*crc64_func_t)(const uint8_t *buf, size_t size, uint64_t crc);

crc64_func_t crc64_resolve(void) {
    if (__builtin_cpu_supports("clmul")) {
        return &crc64_clmul;
    }
    return &crc64_generic;
}

When sshd launches and dynamic libraries are mapped into virtual memory, ld.so invokes crc64_resolve(). At this precise moment:

  1. The dynamic linker has resolved external symbols across loaded shared objects.
  2. The Global Offset Table (GOT) remains writable because relocation processing is still active and full Relocation Read-Only (GNU_RELRO) protection has not yet been applied by ld.so.
  3. The malicious resolver executes before main() or any security sandboxing (such as seccomp filters or privilege separation) is initialized.

Once execution transfers to the injected resolver, the backdoor traverses the dynamic linker's internal data structures:

  1. It accesses the internal struct link_map list maintained by glibc to locate all loaded shared libraries.
  2. It locates the base address of libcrypto.so (the OpenSSL cryptographic library loaded by sshd).
  3. It resolves the address of the target symbol: RSA_public_decrypt.
  4. It patches the Global Offset Table (GOT) entry for RSA_public_decrypt inside sshd, redirecting future calls to a hidden payload handler inside liblzma.
C
// OpenSSH auth2-pubkey.c calling RSA_public_decrypt
int user_key_verify(struct ssh *ssh, const struct sshkey *key, ...) {
    ...
    ret = RSA_public_decrypt(signature_len, signature, decrypted, rsa_key, RSA_PKCS1_PADDING);
    ...
}

The threat actor embeds their payload within the public key or certificate's N modulus and signature bytes. The payload layout consists of:

  1. Magic Header: Identifies the incoming packet as a trigger to the hook.
  2. Attacker Signature: Cryptographic signature generated with the attacker's private key.
  3. Encrypted Command Blob: Encrypted using ChaCha20 symmetric cipher, containing the bash command string to execute.
  4. Target Modulus: Ensures standard OpenSSL ASN.1 parsers do not discard the packet prior to invoking the verification routine.

Step 2: In-Memory Decryption and Execution#

When backdoor_rsa_decrypt_hook intercepts the call, it performs the following checks:

C
// Disassembled representation of the hook verification handler
int backdoor_rsa_decrypt_hook(int flen, const unsigned char *from, 
                              unsigned char *to, RSA *rsa, int padding) {
    
    // Check if input buffer contains attacker trigger structure
    if (is_attacker_magic(from, flen)) {
        // Validate payload signature using hardcoded public key
        if (ed448_verify(from + HEADER_OFFSET, attacker_pubkey) == VALID) {
            char command_buf[1024];
            
            // Decrypt command payload using ChaCha20
            chacha20_decrypt(from + DATA_OFFSET, command_buf, sizeof(command_buf));
            
            // Execute arbitrary payload directly with root privileges
            system(command_buf);
            
            // Return error code to sshd to close session without core dump
            return -1;
        }
    }
    
    // Fall back transparently to legitimate OpenSSL handler
    return real_rsa_public_decrypt(flen, from, to, rsa, padding);
}

Step 3: Forensic Evasion Mechanics#

The exploit exhibits several distinct evasive behaviors:

  • Zero Process Spawning Anomaly: If the injected command is designed to execute memory-only scripts or communicate via existing file descriptors, standard process monitoring tools (ps, top) observe only the legitimate sshd process.
  • Authentication Log Suppression: Because the session fails out cleanly inside user_key_verify() before PAM or sshd audit handlers generate an authentication record, /var/log/auth.log or journalctl -u ssh records only a benign connection reset or premature disconnect:
PLAINTEXT
Connection closed by 192.0.2.45 port 49152 [preauth]
  • Asymmetric Protection Against Discovery: Because the trigger requires a valid Ed448 signature signed by the attacker's private key, security researchers discovering the backdoor binary cannot replay captured packets or generate triggers to confirm RCE capability without reverse-engineering the entire cryptographic schema.

Fast Cyber Defense Morning Takeaways#

The technical autopsy of CVE-2024-3094 yields fundamental lessons for enterprise architecture, software supply chain security, and digital forensics:

  1. The Danger of Unchecked Transitive Dependencies: A compression utility designed for archiving files should never reside in the address space of the most critical remote access daemon in an enterprise. Linux distributions must audit and decouple convenience library linkages from critical security services.
  2. Build-Time vs. Source-Time Divergence: Automated code review and static application security testing (SAST) that scan only Git repositories are fundamentally blind to tarball-level injections. Continuous integration pipelines must enforce binary reproducibility between version-controlled sources and packaged artifacts.
  3. The Architectural Risk of Early-Stage Dynamic Hooks: The fact that IFUNC resolvers run before GNU_RELRO memory write protections are enforced represents a critical architectural window in dynamic linkers. Hardening binary loaders to prevent cross-library GOT manipulation is an urgent defensive requirement.

Bridge to Evening Defense Guide#

In tonight's companion defensive engineering edition, we will build and deploy a comprehensive detection, forensics, and hardening matrix:

  • Memory Forensics & GOT Integrity Checking: Writing volatile memory inspection scripts to detect patched GOT/PLT entries in running sshd processes.
  • eBPF Linker Auditing: Deploying custom eBPF probes to intercept unexpected IFUNC symbol resolutions and cross-DSO memory writes at execution time.
  • Systemd & OpenSSH Decoupling: Hardening configurations to strip libsystemd from sshd using socket-activation isolation and building minimal, statically linked management daemons.
  • Supply Chain Verification Frameworks: Implementing SLSA (Supply-chain Levels for Software Artifacts) Level 3 checks and hermetic build verification across Linux infrastructure.

Authoritative References#

  1. Openwall Security Advisory: backdoor in upstream xz/liblzma leading to ssh server compromise (CVE-2024-3094) by Andres Freund — Openwall OSS-Security
  2. Cybersecurity and Infrastructure Security Agency (CISA): Urgent: Reported Supply Chain Compromise Affecting XZ Utils (CVE-2024-3094) — CISA Cybersecurity Alerts
  3. Red Hat Security Advisory: Urgent security alert for Fedora 40 and Fedora Rawhide users regarding CVE-2024-3094 — Red Hat Security
  4. Akamai Threat Research: Dissecting the XZ Utils Backdoor: Analysis of the Payload and Cryptographic Trigger — Akamai Blog

Comments