CoursesAdvanced Linux securityPersistence & rootkits

Userland stealth: LD_PRELOAD & more

Hooking without touching the kernel.

Advanced12 min · lesson 7 of 17

Beyond obvious persistence, attackers hide their activity, and the stealthier userland technique is library hooking. On Linux, LD_PRELOAD forces a program to load a chosen shared library first, so the attacker’s library can intercept and rewrite the behavior of standard functions — making files invisible to ls, hiding processes from ps, or filtering the attacker’s connections out of netstat. It is a userland rootkit: no kernel changes, just a malicious library shimmed in front of libc.

terminal
# two places attackers wire LD_PRELOAD for persistence + stealth:
$ cat /etc/ld.so.preload # a library here is preloaded into EVERY program — huge red flag
$ env | grep LD_PRELOAD # or set in a profile/service environment
# detection: /etc/ld.so.preload should almost always be absent or empty — baseline and alert

Why hooked tools cannot be trusted

The insidious part is that once ls, ps, and netstat are hooked, the tools you would use to investigate are lying to you — the attacker’s files and processes are simply edited out of the output. This is the central problem of host forensics: on a potentially compromised host, the built-in tools may be subverted, so you cannot fully trust what they tell you. The defensive answers are to detect the hooking mechanism itself (an unexpected /etc/ld.so.preload, an odd LD_PRELOAD in a service environment) and to gather evidence with methods that bypass the hooked userland.

See around the hooks

You defeat userland hiding by not asking the hooked tools. The kernel’s own interfaces — reading /proc directly, or using a statically-linked known-good busybox you brought yourself — see the real processes and files, because LD_PRELOAD only affects dynamically-linked programs that use the hooked functions. Cross-checking the output of ps against a direct read of /proc, or comparing what ss shows against the kernel’s socket tables, surfaces the discrepancy that reveals the hook. A gap between two views of the same system is itself the detection.

terminal
# cross-check hooked userland against the kernel’s ground truth
$ ls /proc/[0-9]* -d | wc -l # real process count from the kernel
$ ps -e --no-headers | wc -l # what (possibly hooked) ps reports
# a mismatch = something is hiding processes. Same idea for files (ls vs find via /proc)
# and connections (ss vs /proc/net/tcp).
On a compromised host, trust nothing you did not bring
The moment you suspect a userland rootkit, the host’s own binaries are suspect — ls, ps, netstat, even the shell may be lying. Investigate with a static, known-good toolkit you carry in (a statically-linked busybox, tools from read-only media), read the kernel directly via /proc where possible, and better yet, rely on off-host telemetry (the audit pipeline and eBPF you are about to build) that recorded the truth before the tools were subverted. Evidence collected through a compromised userland is evidence the attacker got to edit.