This repository demonstrates the use of LD_PRELOAD and discusses some security issues.
By default, Linux uses the source IP address of the interface that the packet is sent on, you can use bind.so
to override this behavior.
This shared library will override the connect
call to libc
and calls bind
on the socket passed to it before connect
is called.
It can be used to set the source IP address of the connection if the binary doesn't support it by itself.
The idea to this was out of necessity; I had to check if a PostgreSQL connection could be made from a specific IP address, as psql
doesn't support this.
Using LD_PRELOAD
was the easiest, non-invasive way (to the system) to do this.
This is just an example and has some limitations:
- Only IPv4 addresses are supported
- UDP might not work as expected
- Can break DNS resolution as the new source address is used for DNS queries as well (use IP address instead of hostname)
- Works only on dynamically linked binaries
- Might not work if the binary calls
bind
itself - Only works on Linux
- Some things I forgot
How to use:
LD_PRELOAD=${PWD}/bind.so BIND_ADDR=10.0.0.2 psql -h 192.168.1.100 -U postgres
This changes the source IP address of the connection to 10.0.0.2
.
Dynamically linked binaries need some mechanism to load shared libraries.
The man page for ld.so
(man 8 ld.so
) describes dynamically linked binaries as follows:
The dynamic linker can be run either indirectly by running some dynami‐
cally linked program or shared object (in which case no command-line
options to the dynamic linker can be passed and, in the ELF case, the
dynamic linker which is stored in the .interp section of the program is
executed) or directly by running:
/lib/ld-linux.so.* [OPTIONS] [PROGRAM [ARGUMENTS]]
...
With the LD_PRELOAD
environment variable you can inject a shared library into a process and override
functions in libraries that are already loaded. You don't need to recompile the program or the libraries,
and you even don't need to know the name of the library (or the path to it).
- You don't need to know the name of the library (or the path to it).
- You don't need to recompile the program or the libraries.
- You don't need special privileges to use LD_PRELOAD.
- You don't need to be a root user.
- The dynamic library used for LD_PRELOAD doesn't need the executable bit set.
- Binaries with suid or sgid bits set ignore LD_PRELOAD for security reasons, but setting suid or sgid bits causes other security issues.
I've done some post-compromise analysis for customers and seen a lot of attackers using this technique.
If the attacker can get into your www-root directory (open (S)FTP, web application flaws, ...), they can inject a shared library that will
be loaded before the original shared library.
Some web applications will use system binaries like ping
or host
which can be misused for this purpose.
Some Linux rootkits also use this technique to hide themselves. You won't find any malware on a running Linux system, ps
, top
, htop
and others will not show any malicious processes or activity, but the malware is still there.
ls
will not show any files or traces belonging to the malware.
To find an infection, you have to check the filesystem while the system is NOT running. You have to boot into a live CD or USB stick and check the filesystem with a tool like rkhunter
. The /tmp
directory is also a good place to look for malicious files.
- If possible, use a read-only filesystem for the web application.
- Use AppArmor or SELinux to restrict access to the filesystem or to sanitize the environment before executing a binary.
- Only use statically linked binaries.
- In containers, use a read-only filesystem and drop all capabilities.
These are just examples, but you should be aware of these issues. A more complete list would go beyond the scope of this README.
- Override
malloc
to use a custom allocator - Decrypt code from code "encryption" tools (already done this with some Perl scripts)
- Hook into
libssl.so
to log cleartext TLS traffic - The possibilities are endless