Introduction
Linux has long dominated the server computing landscape, and the rapid adoption of cloud technologies by organisations around the world has only contributed to this. As a result, many recent high-profile malware campaigns have targeted Linux servers (often in cloud environments) and used them to illicitly mine cryptocurrency, conduct denial of service (DoS) attacks, steal sensitive data and carry out other nefarious activities.
At Cado Labs, we regularly analyse malware campaigns targeting Linux and have decided to document some common and not-so-common attack techniques observed in these campaigns. We hope this will help defenders identify and mitigate these techniques in their Linux environments. In this blog we’re going to analyse a common method of execution flow hijacking on Linux: dynamic linker hijacking.
Background on Dynamic Linker Hijacking
Dynamic linking, a feature present in virtually all modern operating systems, allows certain commonly-used libraries (referred to as shared objects in Linux, dylibs in macOS, and Dynamic Linked Libraries/DLLs in Windows) to be loaded by an application at runtime. When a developer compiles an executable, they specify which libraries should be loaded and the operating system’s linker retrieves these and loads them when the application is launched. This allows the code contained within the libraries to be shared across applications and saves the developer writing this code and shipping it themselves.
Typical examples of shared libraries include things like compression and cryptographic libraries, features which are commonly used by applications but are difficult and time-consuming to implement - after all, why reinvent the wheel?
However, if we think like an attacker, shared libraries present an opportunity to hijack the execution flow of an application and run malicious code in the context of a legitimate process. Generally, dynamic linker hijacking involves tricking the dynamic linker into loading a malicious library implanted by the attacker.
This can be used to supplement existing payloads by implementing features like process hiding, as we’ll discuss later. It also brings stealth benefits to a malware campaign and although many modern operating systems are hardened to prevent arbitrary libraries from being loaded, this is still a highly-effective technique and one which is often seen in the wild.
Using Linux’s LD Preload Feature for Dynamic Linker Hijacking
In Linux, the dynamic linkers are referred to as ld.so and ld-linux.so. The latter is commonly used in contemporary Linux distributions as it handles dynamic linking for executables in the ELF binary format - the current default format on Linux.
A number of environment variables (envars) can be used during the execution of the dynamic linker, the most important of which (for our purposes) is LD_PRELOAD. From the ld.so man page:
[LD_PRELOAD is…] A list of additional, user-specified, ELF shared objects
to be loaded before all others. This feature can be used to selectively override
functions in other shared objects.
Essentially, this means that shortly after invocation, the dynamic linker will read the contents of $LD_PRELOAD and load any shared objects located at paths defined in the envar before any other (potentially benign) shared objects are loaded. Since it’s easy for a malicious shell script or other executable to set the value of LD_PRELOAD, you can see how this could be leveraged by malware to run additional payloads.
The LD_PRELOAD envar is not the only place where users can specify shared objects to be loaded first. The dynamic linker also consults the file /etc/ld.so.preload which can also contain user-specified paths to shared objects. In the case that paths are defined both in the envar and in this file, the envar takes precedence. Additionally, the ld.so.preload file causes a system-wide configuration change, resulting in shared objects being preloaded by any binary on the system.
Linux Dynamic Linker Hijacking in Action
Dynamic linker hijacking is often utilised by Linux malware to install rootkits on the target system. One popular open source rootkit is libprocesshider.so. As the name suggests, libprocesshider is used to hide processes from Linux performance monitoring tools, such as top, ps, lsof and so on. It achieves this by hooking syscalls used for enumerating the /proc/<PID> directories and preventing them from doing so (more info on hiding linux processes for fun). Naturally, this is desirable for virtually all categories of malware, as malicious processes may be able to execute unimpeded if they are undetectable by the bundled monitoring tools.
In order to hook the appropriate syscall, the library needs to be loaded first… In steps the dynamic linker’s LD Preload feature!
To demonstrate this in action, we examined a recent sample of cryptomining malware:
As can be seen on lines 18 - 20, a function named hide first uses sed to remove any lines containing ‘libprocesshider’ from the /etc/ld.so.preload file. A path containing the compiled libprocesshider shared object (library) is then added back into the ld.so.preload file to ensure that it’s loaded correctly.
According to the libprocesshider documentation, once the path to libprocesshider is added to LD_PRELOAD or the ld.so.preload file, the specified executable will first load libprocesshider and details of the process will be hidden from process monitoring tools.
Further down the script, we can see the hide function being invoked:
If you examine lines 211 to 214 in the screenshot above, you’ll see a file at the path /var/tmp/java_c/java_c is executed prior to the hide function being invoked. Despite the name, this is the binary for the XMRig Monero mining software, commonly seen in cryptojacking campaigns.
Once the hide function is invoked, the miner process is hidden - as the process name is hardcoded in the version of libprocesshider.so that the script downloads. This means that although the miner is likely executing with high utilisation of system resources (something that would typically draw attention) this would be near-impossible for an administrator to identify using system monitoring tools alone.
As a result, the miner executes uninterrupted and successfully generates profits for the malware developer.
How to Detect Dynamic Linker Hijacking in your Environment
Although effective, fortunately this technique is relatively easy to detect on Linux. To check the contents of the LD_PRELOAD envar, the export command can be used. If you suspect your system has been compromised and this envar is set then it’s likely that a malicious library has been used. The unset command can be used to delete the value of the envar and reveal the malware if a process hiding library was used.
Similarly, the /etc/ld.so.preload file shouldn’t exist in a vanilla installation of Linux. If this file exists and it contains paths to arbitrary executables, this is again indicative of malicious libraries being used. Simply delete the file to prevent the libraries being loaded in future and remove the libraries themselves.
Indicators of Compromise
Filename | SHA256 |
p.sh (shell script used in example) | 53047c6f255ceee5ec989d73a36fa97ac6035325ea1a81e959b585220188fd11 |
libprocesshider.so (hash is specific to example) | 0e6d37099dd89c7eed44063420bd05a2d7b0865a0f690e12457fbec68f9b67a8 |