/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
Tools
Compare all tools- debugfsCLI · built into Linux
- UACCLI · open source
- Volatility 3CLI · open source
- VelociraptorPlatform · open source
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
| Artifact | Path | Normal state |
|---|---|---|
| System-wide preload list | /etc/ld.so.preload | Absent on mainstream Debian, Ubuntu and RHEL-family installs |
| Per-process preload | LD_PRELOAD in /proc/<pid>/environ | Normally unset |
| Where it gets set | /etc/environment, shell startup files, systemd Environment=, ~/.ssh/environment | Rarely contains LD_PRELOAD |
| Library search config | /etc/ld.so.conf (usually include /etc/ld.so.conf.d/*.conf), /etc/ld.so.conf.d/*.conf | Package-owned entries only |
| Compiled cache | /etc/ld.so.cache | Rebuilt by ldconfig |
| Loaded libraries (live) | /proc/<pid>/maps | Only 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.preloadwas loaded into every dynamically linked program started after the file was created, including sshd sessions and root shells. LD_PRELOADin 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
| Item | Meaning |
|---|---|
/etc/ld.so.preload content | Whitespace-separated list of shared object paths |
| Load order | LD_PRELOAD first, then the --preload option of ld.so, then /etc/ld.so.preload |
| Secure-execution mode | For 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_AUDIT | Other 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/*.conf | One 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.preloadwithdebugfson ext file systems orxfs_dbon XFS, and records itsstatdata 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.preloadon a server as a priority finding until explained. If a product is said to need it, confirm the library's package ownership withdpkg -Sorrpm -qfand check the vendor documentation. - If
ls /etcon the live host does not show the file but a static tool ordebugfsdoes, you have strong evidence of an active rootkit. - The same library path appearing in
/proc/<pid>/mapsof 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.
lddon an untrusted binary may execute it; inspect withreadelf -dorobjdump -pinstead.- Hunt for the planting event: new
/etc/ld.so.preloadshortly 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.