Skip to content

PersistenceAnti-forensics

/etc/ld.so.preload and LD_PRELOAD: Linux Linker Hijacking

The dynamic linker preload file, LD_PRELOAD and ld.so.conf: how userland rootkits inject libraries into every process and how to detect them.

Location
/etc/ld.so.preload
Proves
Whether a shared library was forced into dynamically linked processes, which one, and since when
Timestamps
File mtime/ctime/crtime of the preload file and the library; no internal timestamps
Access
root to write; world-readable; may be hidden from live tools by the rootkit itself
Retention
Until deleted; LD_PRELOAD in process environments lasts until the process exits
Collection
UAC, debugfs, cp -a, AVML

What it is

Every dynamically linked Linux program starts by running the dynamic linker (ld.so / ld-linux*.so), which loads the shared libraries the program needs. Before those, it loads any libraries listed in the LD_PRELOAD environment variable and then those listed in /etc/ld.so.preload. A preloaded library can override libc functions such as readdir, fopen or execve, which is exactly how userland rootkits hide files, processes and network connections (MITRE ATT&CK T1574.006). MITRE lists Hildegard and Rocke among the families that modified /etc/ld.so.preload.

The related files /etc/ld.so.conf, /etc/ld.so.conf.d/*.conf and /etc/ld.so.cache control where libraries are searched for. They are less direct but can be abused to make a malicious copy of a library win the lookup.

Where it lives

ArtifactPathNormal state
System-wide preload list/etc/ld.so.preloadAbsent on mainstream Debian, Ubuntu and RHEL-family installs
Per-process preloadLD_PRELOAD in /proc/<pid>/environNormally unset
Where it gets set/etc/environment, shell startup files, systemd Environment=, ~/.ssh/environmentRarely contains LD_PRELOAD
Library search config/etc/ld.so.conf (usually include /etc/ld.so.conf.d/*.conf), /etc/ld.so.conf.d/*.confPackage-owned entries only
Compiled cache/etc/ld.so.cacheRebuilt by ldconfig
Loaded libraries (live)/proc/<pid>/mapsOnly expected libraries

Paths are the same across distribution families; library directories differ (/usr/lib/x86_64-linux-gnu on Debian/Ubuntu, /usr/lib64 on RHEL/Fedora).

What it proves

  • A library listed in /etc/ld.so.preload was loaded into every dynamically linked program started after the file was created, including sshd sessions and root shells.
  • LD_PRELOAD in a process environment shows that process, and usually its children, ran with the injected library.
  • The library's functions define the rootkit's reach: hooks on directory listing mean file hiding, hooks on authentication functions point to credential capture.
  • Not proven: statically linked binaries do not use the dynamic linker and are unaffected. The file's presence alone does not tell you what the library does; that needs reverse engineering.

Key fields

ItemMeaning
/etc/ld.so.preload contentWhitespace-separated list of shared object paths
Load orderLD_PRELOAD first, then the --preload option of ld.so, then /etc/ld.so.preload
Secure-execution modeFor setuid/setgid or capability binaries (AT_SECURE), LD_PRELOAD entries containing a slash are ignored, and only setuid libraries in standard directories load
LD_LIBRARY_PATH, LD_AUDITOther linker variables abused the same way; restricted in secure-execution mode (LD_LIBRARY_PATH is ignored; LD_AUDIT, like LD_PRELOAD, only loads set-user-ID libraries from standard directories)
/etc/ld.so.conf.d/*.confOne directory per line; an attacker-added directory listed early can shadow real libraries

Timestamps

No internal timestamps. Use mtime, ctime and birth time of /etc/ld.so.preload, the library it names, and /etc/ld.so.cache (rewritten each time ldconfig runs). The creation time of the preload file is usually the best estimate of when hooking started. Compare with process start times (see /proc live artifacts): processes started earlier do not carry the library unless restarted.

Retention

The file persists until deleted. Removing it does not unload the library from processes already running. LD_PRELOAD set in a process environment disappears when the process exits, so capture it live or from memory.

Collection

A preload rootkit can hide its own configuration from tools that use libc, so read the file below the libc layer:

  • From a disk image or read-only mount on a clean analysis host (preferred).
  • On a live host with a statically linked tool, or directly from the file system: UAC includes an artifact that dumps /etc/ld.so.preload with debugfs on ext file systems or xfs_db on XFS, and records its stat data even when the file is hidden.
  • Capture memory (AVML or LiME) so preloaded libraries can be extracted per process.
# live, ext4 root on /dev/sda1: read the file through debugfs rather than libc
debugfs -R 'cat /etc/ld.so.preload' /dev/sda1
debugfs -R 'stat /etc/ld.so.preload' /dev/sda1
# live, static busybox from trusted media
/media/ir/busybox cat /etc/ld.so.preload
/media/ir/busybox grep -l LD_PRELOAD /proc/[0-9]*/environ

Parsing

R=/mnt/evidence
ls -la --time-style=full-iso "$R"/etc/ld.so.preload && cat "$R"/etc/ld.so.preload
grep -rn 'LD_PRELOAD\|LD_LIBRARY_PATH\|LD_AUDIT' "$R"/etc "$R"/root "$R"/home/*/.[a-z]* 2>/dev/null
cat "$R"/etc/ld.so.conf "$R"/etc/ld.so.conf.d/*.conf
ldconfig -p -C "$R"/etc/ld.so.cache | less     # cache contents from the image

In memory, Volatility 3 linux.envars shows LD_PRELOAD per process, linux.proc.Maps lists mapped libraries, and linux.elfs can dump them for analysis. Velociraptor's Linux.Sys.Maps gives the same view on a live fleet.

Investigator tips

  • Treat any /etc/ld.so.preload on a server as a priority finding until explained. If a product is said to need it, confirm the library's package ownership with dpkg -S or rpm -qf and check the vendor documentation.
  • If ls /etc on the live host does not show the file but a static tool or debugfs does, you have strong evidence of an active rootkit.
  • The same library path appearing in /proc/<pid>/maps of many unrelated processes is the live signature of system-wide preloading.
  • Malicious libraries are often named to blend in with system libraries; check file type, owner, ctime and whether any package ships them.
  • ldd on an untrusted binary may execute it; inspect with readelf -d or objdump -p instead.
  • Hunt for the planting event: new /etc/ld.so.preload shortly before a restart of sshd or other daemons is a pattern seen in real cases. Check systemd units and the journal for that restart.
  • Preload rootkits cannot hide from the kernel; when userland and memory analysis disagree, trust memory. If both look clean but behaviour is odd, look at kernel modules.

See also