Exploitation via LD_PRELOAD
LD_PRELOAD is an environment variable which,
when defined on the system, forces the dynamic linking loader to
preload a particular shared library
before any others.
As a result, functions that are defined in this library
are used before any with the same method signature
that are defined in other libraries.
LD_PRELOAD faces a similar limitation to the LD_LIBRARY_PATH
exploit vector we covered previously.
Sudo will explicitly ignore the LD_PRELOAD environment variable for a user
unless the user’s real ID
is the same as their effective UID.
This is important,
as it will hinder the privilege escalation approach described
earlier in this module.
ere are potential bypasses as we’ll explain later.
Libraries specified by LD_PRELOAD
are loaded before any others the program will use.
As a result, methods we define in a library
loaded by LD_PRELOAD
will override methods loaded later on.
Overriding methods in this way is a technique called
function hooking.
In this module we'll leverage this technique
to load our own malicious shared library
Because the original libraries
meaning the program will run as intended
which will help us keep a low profile
For this attack vector,
we first need to find an application that the victim is likely to use frequently.
One potential option is the cp utility,
which is used to copy files between locations on the system.
This utility is often used with
sudo
which could improve our attacks effectiveness
We can run the ltrace tool on the cp command
to get a list of library function calls it uses during normal operation.
There are many calls,
but one that stands out is geteuid.
This function is a good candidate
because it seems to only be called once during the application run,
which limits how frequently our code will be executed.
Using this function will limit redundant shells
According to the function’s man page,
it takes no parameters and returns the user’s UID number.
Let’s try to hook this call through our own malicious shared library.
In our library, we'll simply redefine the geteuid function
We don’t need to define a constructor function
as we did in the previous examples.
This is because we want to fire our payload
when a library function is being called,
rather than when the library is loaded.
Also, this will allow us to “patch” what the library is doing
and still retain its original behavior.
This time, we’ll include a reverse shell
so we can enjoy the full effect of our efforts.
Let’s walk through our code piece by piece.
First, as with other C programs, the include statements lists
standard libraries the program will use.
is worth noting as it defines functions for interacting
with the dynamic linking loader.
The next portion of our code is the shellcode
which is stored in the buf character array.
Following the shellcode declaration, we'll define our geteuid function,
the signature matches the original.
It has no parameters,
and returns a value of uid_t,
which in this case is simply an integer.
The next line defines a pointer,
which we’ll use to point to the old geteuid function.
We’re using the typeof keyword to determine the pointer type dynamically.
As a reminder from the AV section,
a pointer is just a variable that points to a place in memory.
In this case, it points to the memory location
where the old geteuid function is stored.
This provides us access to the original function
so that we can call it later on.
This will allow us to retain the original functionality of the program.
Next, we use the dlsym function
to get the memory address of the original version of the geteuid function
The dlsym function finds a symbol for a dynamic library in memory.
When calling it, we give it the name of the symbol we’re trying to find,
in this case “geteuid”.
This tells it to find the next occurrence of “geteuid” in memory
outside of the current library.
Calling this will skip our version of the function and find the next one,
which should be the original version loaded by the program the user called.
At this point, it’s important to note
that if we keep our original shared library code format,
we're going to run into a problem.
If we use it as-is,
when we run our target application,
it will stop
and wait for our shell to return
before continuing.
This means that the function will stall
and the cp program will stall as well.
This will certainly raise suspicion.
Ideally, we want the program to return right away,
but still run our shell in the background.
In order to do this, we need to create a new process for our shell.
We can do this with
fork,
which creates a new process by duplicating the parent process.
This line in the code determines whether or not the result of the fork call is zero
If it is, we are running inside the newly created child process,
and can run our shell as we did with our earlier AV bypass shell application.
Otherwise, it will return the expected value of geteuid
to the original calling program
it can continue as intended.
The final two lines provide a meaningless return value
in case the code reaches that point,
which realistically should never happen.
The code within the fork branch
checks that the shellcode resides on an executable memory page
before executing it.
The reason for this additional step is that the -f PIC compilation flag
relocates our shellcode to the library .data section
in order to make it position independent.
Specifically, the code gets the size of a memory page
so it knows how much memory to access.
It then changes the page of memory that contains our shellcode
and makes it executable
using mprotect.
It does this by setting its access properties to
PROT_READ and PROT_EXEC,
which makes our code readable and executable.
If changing the memory permissions fails, the program will exit
with a return code of “-1”.
We’ll save our code,
and compile and link it
as we did in our previous examples.
Now that our library is compiled, let’s run a test.
We’ll run cp once without our library,
and then once with the LD_PRELOAD environment variable set,
hooking the function call.
It worked!
Our metasploit listener shows our shell successfully connected.
This is a great step, but we're still executing as the offsec user.
We haven't elevated our privileges.
Before continuing we'll unset LD_PRELOAD,
which could have adverse effects on other system actions will perform.
As we mentioned previously, the dynamic linker ignores LD_PRELOAD
when the user’s effective UID does not match its real UID,
for example when running commands as sudo.
We might be lucky enough to have the right setting in /etc/sudoers,
but that's not very likely.
The env_keep setting specifically allows certain environment variables
be passed into the sudo session
when calls are made.
By default this is turned off.
We could try our previous approach of defining a sudo alias for the user
but a quick test indicates that our code isn't executed.
In this case, we need to xplicitly set LD_PRELOAD
when calling sudo,
we can do in the alias in .bashrc.
Note that if we were to execute this attack in the normal user’s context,
we would want to set the environment variable in the user’s .bashrc
or .bash_profilefile,
similar to what we did with LD_LIBRARY_PATH.
After reloading our .bashrc file
as we did before with source,
we can run the cp command again.
Next, we check our Metasploit console,
and find that we’ve received a session
as root.
Excellent!
We’ve escalated privileges and our victim is completely
unaware.
As demonstrated,
LD_PRELOAD can be an effective exploitation method
in certain scenarios.